diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 1fb20f9b5c..0000000000 --- a/.babelrc +++ /dev/null @@ -1,23 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "loose": true, - "exclude": [ - "transform-es2015-typeof-symbol" - ], - "targets": { - "browsers": [ - "last 2 versions", - "IE >= 9" - ] - } - } - ] - ], - "plugins": [ - "transform-object-rest-spread", - "transform-react-jsx" - ] -} diff --git a/.editorconfig b/.editorconfig index c85259b6a3..0024e9d87a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,7 @@ insert_final_newline = true [{*.json,.*rc,*.yml}] indent_style = space indent_size = 2 +insert_final_newline = false [*.md] trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore index feb23905f2..84828550a4 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ -test/ts/ +dist +benchmarks diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index fc7417e67e..0000000000 --- a/.flowconfig +++ /dev/null @@ -1,10 +0,0 @@ -[ignore] -/dist/.* - -[include] - -[libs] - -[lints] - -[options] diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..77b81739f7 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@preactjs.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..bbe0c7d644 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: [preactjs] +open_collective: preact diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..89bee4e623 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,25 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' +--- + +- [ ] Check if updating to the latest Preact version resolves the issue + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** + +If possible, please provide a link to a StackBlitz/CodeSandbox/Codepen project or a GitHub repository that demonstrates the issue. You can use the following template on StackBlitz to get started: https://stackblitz.com/edit/create-preact-starter + +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. See error + +**Expected behavior** +What should have happened when following the steps above? diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..64094b0068 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,13 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: feature request +assignees: '' +--- + +**Describe the feature you'd love to see** +A clear and concise description of what you'd love to see added to Preact. + +**Additional context (optional)** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml new file mode 100644 index 0000000000..f6dae57481 --- /dev/null +++ b/.github/workflows/benchmarks.yml @@ -0,0 +1,91 @@ +name: Benchmarks + +on: + workflow_dispatch: + workflow_call: + +jobs: + prepare: + name: Prepare environment + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Download locally built preact package + uses: actions/download-artifact@v4 + with: + name: npm-package + - run: mv preact.tgz preact-local.tgz + - name: Download base package + uses: andrewiggins/download-base-artifact@v3 + with: + artifact: npm-package + workflow: ci.yml + required: true + - run: mv preact.tgz preact-main.tgz + - name: Upload locally build & base preact package + uses: actions/upload-artifact@v4 + with: + name: bench-environment + path: | + preact-local.tgz + preact-main.tgz + + bench_todo: + name: Bench todo + uses: ./.github/workflows/run-bench.yml + needs: prepare + with: + benchmark: todo/todo + timeout: 10 + + bench_text_update: + name: Bench text-update + uses: ./.github/workflows/run-bench.yml + needs: prepare + with: + benchmark: text-update/text-update + timeout: 10 + + bench_many_updates: + name: Bench many-updates + uses: ./.github/workflows/run-bench.yml + needs: prepare + with: + benchmark: many-updates/many-updates + timeout: 10 + + bench_replace1k: + name: Bench replace1k + uses: ./.github/workflows/run-bench.yml + needs: prepare + with: + benchmark: table-app/replace1k + + bench_update10th1k: + name: Bench 03_update10th1k_x16 + uses: ./.github/workflows/run-bench.yml + needs: prepare + with: + benchmark: table-app/update10th1k + + bench_create10k: + name: Bench create10k + uses: ./.github/workflows/run-bench.yml + needs: prepare + with: + benchmark: table-app/create10k + + bench_hydrate1k: + name: Bench hydrate1k + uses: ./.github/workflows/run-bench.yml + needs: prepare + with: + benchmark: table-app/hydrate1k + + bench_filter_list: + name: Bench filter-list + uses: ./.github/workflows/run-bench.yml + needs: prepare + with: + benchmark: filter-list/filter-list + timeout: 10 \ No newline at end of file diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml new file mode 100644 index 0000000000..98604af796 --- /dev/null +++ b/.github/workflows/build-test.yml @@ -0,0 +1,55 @@ +name: Build & Test + +on: + workflow_dispatch: + workflow_call: + inputs: + ref: + description: 'Branch or tag ref to check out' + type: string + required: false + default: '' + artifact_name: + description: 'Name of the artifact to upload' + type: string + required: false + default: 'npm-package' + +jobs: + build_test: + name: Build & Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || '' }} + - uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - run: npm ci + - name: test + env: + CI: true + COVERAGE: true + FLAKEY: false + # Not using `npm test` since it rebuilds source which npm ci has already done + run: | + npm run lint + npm run test:unit + - name: Coveralls GitHub Action + uses: coverallsapp/github-action@v2.3.0 + timeout-minutes: 2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Package + # Use --ignore-scripts here to avoid re-building again before pack + run: | + npm pack --ignore-scripts + mv preact-*.tgz preact.tgz + - name: Upload npm package + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact_name || 'npm-package' }} + path: preact.tgz \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..a1950e1f5d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: CI + +on: + workflow_dispatch: + pull_request: + branches: + - '**' + push: + branches: + - main + - restructure + - v11 + +jobs: + filter_jobs: + name: Filter jobs + runs-on: ubuntu-latest + outputs: + jsChanged: ${{ steps.filter.outputs.jsChanged }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + # Should be kept in sync with the filter in the PR Reporter workflow + filters: | + jsChanged: '**/src/**/*.js' + + compressed_size: + name: Compressed Size + needs: filter_jobs + if: ${{ needs.filter_jobs.outputs.jsChanged == 'true' }} + uses: ./.github/workflows/size.yml + + build_test: + name: Build & Test + needs: filter_jobs + uses: ./.github/workflows/build-test.yml + + benchmarks: + name: Benchmarks + needs: build_test + if: ${{ needs.filter_jobs.outputs.jsChanged == 'true' }} + uses: ./.github/workflows/benchmarks.yml \ No newline at end of file diff --git a/.github/workflows/pr-reporter.yml b/.github/workflows/pr-reporter.yml new file mode 100644 index 0000000000..6c3ebd6191 --- /dev/null +++ b/.github/workflows/pr-reporter.yml @@ -0,0 +1,76 @@ +name: Report Results to PR + +on: + # The pull_request event can't write comments for PRs from forks so using this + # workflow_run workflow as a workaround + workflow_run: + workflows: ['CI'] + branches: ['**'] + types: + - completed + - requested + +jobs: + filter_jobs: + name: Filter jobs + runs-on: ubuntu-latest + if: | + github.event.workflow_run.event == 'pull_request' + outputs: + jsChanged: ${{ steps.filter.outputs.jsChanged }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + ref: ${{ github.event.workflow_run.head_branch }} + # Should be kept in sync with the filter in the CI workflow + filters: | + jsChanged: '**/src/**/*.js' + + report_running: + name: Report benchmarks are in-progress + needs: filter_jobs + runs-on: ubuntu-latest + # Only add the "benchmarks are running" text when a workflow_run is + # requested (a.k.a starting) + if: | + needs.filter_jobs.outputs.jsChanged == 'true' && + github.event.action == 'requested' + steps: + - name: Report Tachometer Running + uses: andrewiggins/tachometer-reporter-action@v2 + with: + # Set initialize to true so this action just creates the comment or + # adds the "benchmarks are running" text + initialize: true + + report_results: + name: Report benchmark results + needs: filter_jobs + runs-on: ubuntu-latest + # Only run this job if the event action was "completed" and the triggering + # workflow_run was successful + if: | + needs.filter_jobs.outputs.jsChanged == 'true' && + github.event.action == 'completed' && + github.event.workflow_run.conclusion == 'success' + steps: + # Download the artifact from the triggering workflow that contains the + # Tachometer results to report + - uses: dawidd6/action-download-artifact@v2 + with: + workflow: ${{ github.event.workflow.id }} + run_id: ${{ github.event.workflow_run.id }} + name_is_regexp: true + name: results-* + path: results + + # Create/update the comment with the latest results + - name: Report Tachometer Results + uses: andrewiggins/tachometer-reporter-action@v2 + with: + path: results/**/*.json + base-bench-name: preact-main + pr-bench-name: preact-local + summarize: 'duration, usedJSHeapSize' \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..7c7af10801 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,29 @@ +name: Release +on: create + +jobs: + build: + if: github.ref_type == 'tag' + uses: preactjs/preact/.github/workflows/build-test.yml@main + + release: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: npm-package + - name: Create draft release + id: create-release + uses: actions/github-script@v6 + with: + script: | + const script = require('./scripts/release/create-gh-release.js') + return script({ github, context }) + - name: Upload release artifact + uses: actions/github-script@v6 + with: + script: | + const script = require('./scripts/release/upload-gh-asset.js') + return script({ require, github, context, glob, release: ${{ steps.create-release.outputs.result }} }) \ No newline at end of file diff --git a/.github/workflows/run-bench.yml b/.github/workflows/run-bench.yml new file mode 100644 index 0000000000..f741724499 --- /dev/null +++ b/.github/workflows/run-bench.yml @@ -0,0 +1,122 @@ +name: Benchmark Worker + +# Expectations: +# +# This workflow expects calling workflows to have uploaded an artifact named +# "bench-environment" that contains any built artifacts required to run the +# benchmark. This typically is the dist/ folder that running `npm run build` +# produces and/or a tarball of a previous build to bench the local build against + +on: + workflow_call: + inputs: + benchmark: + description: 'The name of the benchmark to run. Should be name of an HTML file without the .html extension' + type: string + required: true + trace: + description: 'Whether to capture browser traces for this benchmark run' + type: boolean + required: false + default: false + timeout: + description: 'How many minutes to give the benchmark to run before timing out and failing' + type: number + required: false + default: 20 + +jobs: + run_bench: + name: Bench ${{ inputs.benchmark }} + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.timeout }} + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + + # Setup pnpm + - name: Install pnpm + uses: pnpm/action-setup@v3 + with: + version: 8 + run_install: false + - name: Get pnpm store directory + id: pnpm-cache + run: | + echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + # Install benchmark dependencies + - uses: actions/download-artifact@v4 + with: + name: bench-environment + - name: Move tarballs from env to correct location + run: | + ls -al + mv preact-local.tgz benchmarks/dependencies/preact/local-pinned/preact-local-pinned.tgz + ls -al benchmarks/dependencies/preact/local-pinned + mv preact-main.tgz benchmarks/dependencies/preact/main/preact-main.tgz + ls -al benchmarks/dependencies/preact/main + - name: Install deps + working-directory: benchmarks + # Set the CHROMEDRIVER_FILEPATH so the chromedriver npm package uses the + # correct binary when its installed + run: | + export CHROMEDRIVER_FILEPATH=$(which chromedriver) + pnpm install + # Install local dependencies with --no-frozen-lockfile to ensure local tarballs + # are installed regardless of if they match the integrity hash stored in the lockfile + pnpm install --no-frozen-lockfile --filter ./dependencies + + # Run benchmark + - name: Run benchmark + working-directory: benchmarks + run: | + export CHROMEDRIVER_FILEPATH=$(which chromedriver) + pnpm run bench apps/${{ inputs.benchmark }}.html -d preact@local-pinned -d preact@main --trace=${{ inputs.trace }} + + # Prepare output + - name: Anaylze logs if present + working-directory: benchmarks + run: '[ -d out/logs ] && pnpm run analyze ${{ inputs.benchmark }} || echo "No logs to analyze"' + - name: Tar logs if present + working-directory: benchmarks + run: | + if [ -d out/logs ]; then + LOGS_FILE=out/${{ inputs.benchmark }}_logs.tgz + mkdir -p $(dirname $LOGS_FILE) + tar -zcvf $LOGS_FILE out/logs + else + echo "No logs found" + fi + + # Upload results and logs + - name: Calculate log artifact name + id: log-artifact-name + run: | + NAME=$(echo "${{ inputs.benchmark }}" | sed -r 's/[\/]+/_/g') + echo "clean_name=$NAME" >> $GITHUB_OUTPUT + echo "artifact_name=logs_$NAME" >> $GITHUB_OUTPUT + - name: Upload results + uses: actions/upload-artifact@v4 + with: + name: results-${{ steps.log-artifact-name.outputs.clean_name }} + path: benchmarks/out/results/${{ inputs.benchmark }}.json + - name: Upload logs + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.log-artifact-name.outputs.artifact_name }} + path: benchmarks/out/${{ inputs.benchmark }}_logs.tgz + if-no-files-found: ignore \ No newline at end of file diff --git a/.github/workflows/single-bench.yml b/.github/workflows/single-bench.yml new file mode 100644 index 0000000000..76e99147a4 --- /dev/null +++ b/.github/workflows/single-bench.yml @@ -0,0 +1,90 @@ +name: Benchmark Debug + +on: + workflow_dispatch: + inputs: + benchmark: + description: 'Which benchmark to run' + type: choice + options: + - table-app/replace1k + - table-app/update10th1k + - table-app/create10k + - table-app/hydrate1k + - filter_list/filter-list + - many-updates/many-updates + - text-update/text-update + - todo/todo + required: true + base: + description: 'The branch name, tag, or commit sha of the version of preact to benchmark against.' + type: string + default: main + required: false + trace: + description: 'Whether to capture browser traces for this benchmark run' + type: boolean + default: true + required: false + # A bug in GitHub actions prevents us from passing numbers (as either + # number or string type) to called workflows. So disabling this for now. + # See: https://github.com/orgs/community/discussions/67182 + # + # timeout: + # description: 'How many minutes to give the benchmark to run before timing out and failing' + # type: number + # default: 20 + # required: false + +jobs: + build_local: + name: Build local package + uses: ./.github/workflows/ci.yml + + build_base: + name: Build base package + uses: ./.github/workflows/ci.yml + with: + ref: ${{ inputs.base }} + artifact_name: base-npm-package + + prepare: + name: Prepare environment + runs-on: ubuntu-latest + needs: + - build_local + - build_base + timeout-minutes: 5 + steps: + - name: Download locally built preact package + uses: actions/download-artifact@v4 + with: + name: npm-package + - run: mv preact.tgz preact-local.tgz + - name: Clear working directory + run: | + ls -al + rm -rf * + echo "====================" + ls -al + - name: Download base package + uses: actions/download-artifact@v4 + with: + name: base-npm-package + - run: mv preact.tgz preact-main.tgz + - name: Upload locally built & base preact package + uses: actions/upload-artifact@v4 + with: + name: bench-environment + path: | + preact-local.tgz + preact-main.tgz + + benchmark: + name: Bench ${{ inputs.benchmark }} + uses: ./.github/workflows/run-bench.yml + needs: prepare + with: + benchmark: ${{ inputs.benchmark }} + trace: ${{ inputs.trace }} + # timeout: ${{ inputs.timeout }} \ No newline at end of file diff --git a/.github/workflows/size.yml b/.github/workflows/size.yml new file mode 100644 index 0000000000..0dff893cb0 --- /dev/null +++ b/.github/workflows/size.yml @@ -0,0 +1,21 @@ +name: Compressed Size + +on: + workflow_call: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - uses: preactjs/compressed-size-action@v2 + with: + repo-token: '${{ secrets.GITHUB_TOKEN }}' + # Our `prepare` script already builds the app post-install, + # building it again would be redundant + build-script: 'npm run --if-present noop' \ No newline at end of file diff --git a/.gitignore b/.gitignore index a8b70855ae..d7642df739 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,15 @@ -/node_modules -/npm-debug.log .DS_Store -/dist -/_dev -/coverage -package-lock.json -/test/ts/**/*.js - -# Additional bundles -/devtools.js -/devtools.js.map -/debug.js -/debug.js.map - -# Editor and IDE configuration -.vscode/ -.idea/ +node_modules +npm-debug.log +dist +*/package-lock.json +yarn.lock +.vscode +.idea +test/ts/**/*.js +coverage +*.sw[op] +*.log +package/ +preact-*.tgz +preact.tgz diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..7aed486ab4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "benchmarks"] + path = benchmarks + url = https://github.com/preactjs/benchmarks diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000000..2312dc587f --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..05831db05c --- /dev/null +++ b/.prettierignore @@ -0,0 +1,25 @@ +.DS_Store +node_modules +npm-debug.log +dist +*/package-lock.json +yarn.lock +.vscode +.idea +test/ts/**/*.js +coverage +*.sw[op] +*.log +package/ +preact-*.tgz +preact.tgz + +# Copied from benches/.gitignore so prettier can ignore these paths too +benches/dist/ +benches/results/ +benches/logs/ +benches/logs-saved/ +benches/node_modules/ +benches/proxy-packages/*/package-lock.json + +package-lock.json diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 23a476e364..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -sudo: false - -language: node_js - -node_js: - - "8" - - "10" - -cache: - directories: - - node_modules - -# Make chrome browser available for testing -before_install: - - export CHROME_BIN=chromium-browser - - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start - -install: - - npm install - -addons: - sauce_connect: true - -script: - - npm run build - - COVERAGE=true ALLOW_SAUCELABS=false npm run test - - if [ "$TRAVIS_NODE_VERSION" = "8" ]; then - COVERAGE=false FLAKEY=false PERFORMANCE=false ALLOW_SAUCELABS=true npm run test:karma; - ./node_modules/coveralls/bin/coveralls.js < ./coverage/lcov.info; - fi diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 9e81e67c88..77b81739f7 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo 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 +- 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 +- 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..df8652ca7c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,273 @@ +# Contributing + +This document is intended for developers interest in making contributions to Preact and document our internal processes like releasing a new version. + +## Getting Started + +This steps will help you to set up your development environment. That includes all dependencies we use to build Preact and developer tooling like git commit hooks. + +1. Clone the git repository: `git clone git@github.com:preactjs/preact.git` +2. Go into the cloned folder: `cd preact/` +3. Install all dependencies: `npm install` + +## The Repo Structure + +This repository contains Preact itself, as well as several addons like the debugging package for example. This is reflected in the directory structure of this repository. Each package has a `src/` folder where the source code can be found, a `test` folder for all sorts of tests that check if the code in `src/` is correct, and a `dist/` folder where you can find the bundled artifacts. Note that the `dist/` folder may not be present initially. It will be created as soon as you run any of the build scripts inside `package.json`. More on that later ;) + +A quick overview of our repository: + +```bash +# The repo root (folder where you cloned the repo into) +/ + src/ # Source code of our core + test/ # Unit tests for core + dist/ # Build artifacts for publishing on npm (may not be present) + + # Sub-package, can be imported via `preact/compat` by users. + # Compat stands for react-compatibility layer which tries to mirror the + # react API as close as possible (mostly legacy APIs) + compat/ + src/ # Source code of the compat addon + test/ # Tests related to the compat addon + dist/ # Build artifacts for publishing on npm (may not be present) + + # Sub-package, can be imported via `preact/hooks` by users. + # The hooks API is an effect based API to deal with component lifcycles. + # It's similar to hooks in React + hooks/ + src/ # Source code of the hooks addon + test/ # Tests related to the hooks addon + dist/ # Build artifacts for publishing on npm (may not be present) + + # Sub-package, can be imported via `preact/debug` by users. + # Includes debugging warnings and error messages for common mistakes found + # in Preact application. Also hosts the devtools bridge + debug/ + src/ # Source code of the debug addon + test/ # Tests related to the debug addon + dist/ # Build artifacts for publishing on npm (may not be present) + + # Sub-package, can be imported via `preact/test-utils` by users. + # Provides helpers to make testing Preact applications easier + test-utils/ + src/ # Source code of the test-utils addon + test/ # Tests related to the test-utils addon + dist/ # Build artifacts for publishing on npm (may not be present) + + # A demo application that we use to debug tricky errors and play with new + # features. + demo/ + + # Contains build scripts and dependencies for development + package.json +``` + +_Note: The code for rendering Preact on the server lives in another repo and is a completely separate npm package. It can be found here: [https://github.com/preactjs/preact-render-to-string](https://github.com/preactjs/preact-render-to-string)_ + +### What does `mangle.json` do? + +It's a special file that can be used to specify how `terser` (previously known as `uglify`) will minify variable names. Because each sub-package has it's own distribution files we need to ensure that the variable names stay consistent across bundles. + +## What does `options.js` do? + +Unique to Preact we do support several ways to hook into our renderer. All our addons use that to inject code at different stages of a render process. They are documented in our typings in `internal.d.ts`. The core itself doesn't make use of them, which is why the file only contains an empty `object`. + +## Important Branches + +We merge every PR into the `main` branch which is the one that we'll use to publish code to npm. For the previous Preact release line we have a branch called `8` which is in maintenance mode. As a new contributor you won't have to deal with that ;) + +## Creating your first Pull-Request + +We try to make it as easy as possible to contribute to Preact and make heavy use of GitHub's "Draft PR" feature which tags Pull-Requests (short = PR) as work in progress. PRs tend to be published as soon as there is an idea that the developer deems worthwhile to include into Preact and has written some rough code. The PR doesn't have to be perfect or anything really ;) + +Once a PR or a Draft PR has been created our community typically joins the discussion about the proposed change. Sometimes that includes ideas for test cases or even different ways to go about implementing a feature. Often this also includes ideas on how to make the code smaller. We usually refer to the latter as "code-golfing" or just "golfing". + +When everything is good to go someone will approve the PR and the changes will be merged into the `main` branch and we usually cut a release a few days/ a week later. + +_The big takeaway for you here is, that we will guide you along the way. We're here to help to make a PR ready for approval!_ + +The short summary is: + +1. Make changes and submit a PR +2. Modify change according to feedback (if there is any) +3. PR will be merged into `main` +4. A new release will be cut (every 2-3 weeks). + +## Commonly used scripts for contributions + +Scripts can be executed via `npm run [script]` or `yarn [script]` respectively. + +- `build` - compiles all packages ready for publishing to npm +- `build:core` - builds just Preact itself +- `build:debug` - builds the debug addon only +- `build:hooks` - builds the hook addon only +- `build:test-utils` - builds the test-utils addon only +- `test:ts` - Run all tests for TypeScript definitions +- `test:karma` - Run all unit/integration tests. +- `test:karma:watch` - Same as above, but it will automatically re-run the test suite if a code change was detected. + +But to be fair, the only ones we use ourselves are `build` and `test:karma:watch`. The other ones are mainly used on our CI pipeline and we rarely use them. + +_Note: Both `test:karma` and `test:karma:watch` listen to the environment variable `COVERAGE=true`. Disabling code coverage can significantly speed up the time it takes to complete the test suite._ + +_Note2: The test suite is based on `karma` and `mocha`. Individual tests can be executed by appending `.only`:_ + +```jsx +it.only('should test something', () => { + expect(1).to.equal(1); +}); +``` + +## Common terminology and variable names + +- `vnode` -> shorthand for `virtual-node` which is an object that specifies how a Component or DOM-node looks like +- `commit` -> A commit is the moment in time when you flush all changes to the DOM +- `c` -> The variable `c` always refers to a `component` instance throughout our code base. +- `diff/diffing` -> Diffing describes the process of comparing two "things". In our case we compare the previous `vnode` tree with the new one and apply the delta to the DOM. +- `root` -> The topmost node of a `vnode` tree + +## Tips for getting to know the code base + +- Check the JSDoc block right above the function definition to understand what it does. It contains a short description of each function argument and what it does. +- Check the callsites of a function to understand how it's used. Modern editors/IDEs allow you to quickly find those, or use the plain old search feature instead. + +## Benchmarks + +We have a benchmark suite that we use to measure the performance of Preact. Our benchmark suite lives in our [preactjs/benchmarks repository](https://github.com/preactjs/benchmarks), but is included here as Git submodule. To run the benchmarks, first ensure [PNPM](https://pnpm.io/installation) is installed on your system and initialize and setup the submodule (it uses `pnpm` as a package manager): + +```bash +pnpm -v # Make sure pnpm is installed +git submodule update --init --recursive +cd benchmarks +pnpm i +``` + +Then you can run the benchmarks: + +```bash +# In the benchmarks folder +pnpm run bench +``` + +Checkout the README in the benchmarks folder for more information on running benchmarks. + +> **Note:** When switching branches, git submodules are not automatically updated to the commit of the new branch - it stays at the commit of the previous branch. This can be a feature! It allows you to work in different branches with the latest versions of the benchmarks - especially if you have made changes to the benchmarks. +> +> However if you want to switch branches and also update the benchmarks to the latest commit of the new branch, you can run `git submodule update --recursive` after switching branches, or run `git checkout --recurse-submodules` when checking out a new branch. + +## FAQ + +### Why does the JSDoc use TypeScript syntax to specify types? + +Several members of the team are very fond of TypeScript and we wanted to leverage as many of its advantages, like improved autocompletion, for Preact. We even attempted to port Preact to TypeScript a few times, but we ran into many issues with the DOM typings. Those would force us to fill our codebase with many `any` castings, making our code very noisy. + +Luckily TypeScript has a mode where it can somewhat reliably typecheck JavaScript code by reusing the types defined in JSDoc blocks. It's not perfect and it often has trouble inferring the correct types the further one strays away from the function arguments, but it's good enough that it helps us a lot with autocompletion. Another plus is that we can make sure that our TypeScript definitons are correct at the same time. + +Check out the [official TypeScript documentation](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html) for more information. + +_Note that we have separate tests for our TypeScript definition files. We only use `ts-check` for local development and don't check it anywhere else like on the CI._ + +### Why does the code base often use `let` instead of `const`? + +There is no real reason for that other a historical one. Back before auto-formatting via prettier was a thing and minifiers weren't as advanced as they are today we used a pretty terse code-style. The code-style deliberately was aimed at making code look as concise and short as possible. The `let` keyword is a bit shorter than `const` to write, so we only used that. This was done only for stylistic reasons. + +This helped our minds to not lose sight of focusing on size, but made it difficult for newcomers to start contributing to Preact. For that reason alone we switched to `prettier` and loosened our rule regarding usage of `let` or `const`. Today we use both, but you can still find many existing places where `let` is still in use. + +In the end there is no effect on size regardless if you use `const`, `let` or use both. Our code is downtranspiled to `ES5` for npm so both will be replaced with `var` anyways. Therefore it doesn't really matter at all which one is used in our codebase. + +This will only become important once shipping modern JavaScript code on npm becomes a thing and bundlers follow suit. + +## How to create a good bug report + +To be able to fix issues we need to see them on our machine. This is only possible when we can reproduce the error. The easiest way to do that is narrow down the problem to specific components or combination of them. This can be done by removing as much unrelated code as possible. + +The perfect way to do that is to make a [codesandbox](https://codesandbox.io/). That way you can easily share the problematic code and ensure that others can see the same issue you are seeing. + +For us a [codesandbox](https://codesandbox.io/) says more than a 1000 words :tada: + +## I have more questions on how to contribute to Preact. How can I reach you? + +We closely watch our issues and have a pretty active [Slack workspace](https://chat.preactjs.com/). Nearly all our communication happens via these two forms of communication. + +## Releasing Preact (Maintainers only) + +This guide is intended for core team members that have the necessary +rights to publish new releases on npm. + +1. Make a PR where **only** the version number is incremented in `package.json` and everywhere else. A simple search and replace works. (note: We follow `SemVer` conventions) +2. Wait until the PR is approved and merged. +3. Switch back to the `main` branch and pull the merged PR +4. Create and push a tag for the new version you want to publish: + 1. `git tag 10.0.0` + 2. `git push --tags` +5. Wait for the Release workflow to complete + - It'll create a draft release and upload the built npm package as an asset to the release +6. [Fill in the release notes](#writing-release-notes) in GitHub and publish them +7. Run the publish script with the tag you created + 1. `node ./scripts/release/publish.mjs 10.0.0` + 2. Make sure you have 2FA enabled in npm, otherwise the above command will fail. + 3. If you're doing a pre-release add `--npm-tag next` to the `publish.mjs` command to publish it under a different tag (default is `latest`) +8. Tweet it out + +## Legacy Releases (8.x) + +> **ATTENTION:** Make sure that you've cleared the project correctly +> when switching from a 10.x branch. + +0. Run `rm -rf dist node_modules && npm i` to make sure to have the correct dependencies. +1. [Write the release notes](#writing-release-notes) and keep them as a draft in GitHub + 1. I'd recommend writing them in an offline editor because each edit to a draft will change the URL in GitHub. +2. Make a PR where **only** the version number is incremented in `package.json` (note: We follow `SemVer` conventions) +3. Wait until the PR is approved and merged. +4. Switch back to the `main` branch and pull the merged PR +5. Run `npm run build && npm publish` + 1. Make sure you have 2FA enabled in npm, otherwise the above command will fail. + 2. If you're doing a pre-release add `--tag next` to the `npm publish` command to publish it under a different tag (default is `latest`) +6. Publish the release notes and create the correct git tag. +7. Tweet it out + +## Writing release notes + +The release notes have become a sort of tiny blog post about what's +happening in preact-land. The title usually has this format: + +```txt +Version Name +``` + +Example: + +```txt +10.0.0-beta.1 Los Compresseros +``` + +The name is optional, we just have fun finding creative names :wink: + +To keep them interesting we try to be as +concise as possible and to just reflect where we are. There are some +rules we follow while writing them: + +- Be nice, use a positive tone. Avoid negative words +- Show, don't just tell. +- Be honest. +- Don't write too much, keep it simple and short. +- Avoid making promises and don't overpromise. That leads to unhappy users +- Avoid framework comparisons if possible +- Highlight awesome community contributions (or great issue reports) +- If in doubt, praise the users. + +After this section we typically follow with a changelog part that's +divided into 4 groups in order of importance for the user: + +- Features +- Bug Fixes +- Typings +- Maintenance + +We generate it via this handy cli program: [changelogged](https://github.com/marvinhagemeister/changelogged). It will collect and format +the descriptions of all PRs that have been merged between two tags. +The usual command is `changelogged 10.0.0-rc.2..HEAD` similar to how +you'd diff two points in time with git. This will get you 90% there, +but you still need to divide it into groups. It's also a good idea +to unify the formatting of the descriptions, so that they're easier +to read and don't look like a mess. diff --git a/README.md b/README.md index 14c5dfa047..5e3ff1d9b6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

- -![Preact](https://raw.githubusercontent.com/developit/preact/8b0bcc927995c188eca83cba30fbc83491cc0b2f/logo.svg?sanitize=true "Preact") + +![Preact](https://raw.githubusercontent.com/preactjs/preact/8b0bcc927995c188eca83cba30fbc83491cc0b2f/logo.svg?sanitize=true 'Preact')

@@ -9,151 +9,35 @@ **All the power of Virtual DOM components, without the overhead:** -- Familiar React API & patterns: [ES6 Class] and [Functional Components] -- Extensive React compatibility via a simple [preact-compat] alias -- Everything you need: JSX, VDOM, React DevTools, HMR, SSR.. -- A highly optimized diff algorithm and seamless Server Side Rendering +- Familiar React API & patterns: ES6 Class, hooks, and Functional Components +- Extensive React compatibility via a simple [preact/compat] alias +- Everything you need: JSX, VDOM, [DevTools], HMR, SSR. +- Highly optimized diff algorithm and seamless hydration from Server Side Rendering +- Supports all modern browsers and IE11 - Transparent asynchronous rendering with a pluggable scheduler -- 🆕💥 **Instant no-config app bundling with [Preact CLI](https://github.com/developit/preact-cli)** ### 💁 More information at the [Preact Website ➞](https://preactjs.com) - ---- - - - -- [Demos](#demos) -- [Libraries & Add-ons](#libraries--add-ons) -- [Getting Started](#getting-started) - - [Import what you need](#import-what-you-need) - - [Rendering JSX](#rendering-jsx) - - [Components](#components) - - [Props & State](#props--state) -- [Linked State](#linked-state) -- [Examples](#examples) -- [Extensions](#extensions) -- [Debug Mode](#debug-mode) -- [Backers](#backers) -- [Sponsors](#sponsors) -- [License](#license) - - - - -# Preact + + + + + + +
[![npm](https://img.shields.io/npm/v/preact.svg)](http://npm.im/preact) -[![CDNJS](https://img.shields.io/cdnjs/v/preact.svg)](https://cdnjs.com/libraries/preact) -[![Preact Slack Community](https://preact-slack.now.sh/badge.svg)](https://preact-slack.now.sh) +[![Preact Slack Community](https://img.shields.io/badge/Slack%20Community-preact.slack.com-blue)](https://chat.preactjs.com) [![OpenCollective Backers](https://opencollective.com/preact/backers/badge.svg)](#backers) [![OpenCollective Sponsors](https://opencollective.com/preact/sponsors/badge.svg)](#sponsors) -[![travis](https://travis-ci.org/developit/preact.svg?branch=master)](https://travis-ci.org/developit/preact) -[![coveralls](https://img.shields.io/coveralls/developit/preact/master.svg)](https://coveralls.io/github/developit/preact) -[![gzip size](http://img.badgesize.io/https://unpkg.com/preact/dist/preact.min.js?compression=gzip)](https://unpkg.com/preact/dist/preact.min.js) -[![install size](https://packagephobia.now.sh/badge?p=preact)](https://packagephobia.now.sh/result?p=preact) - -Preact supports modern browsers and IE9+: -[![Browsers](https://saucelabs.com/browser-matrix/preact.svg)](https://saucelabs.com/u/preact) - - ---- +[![coveralls](https://img.shields.io/coveralls/preactjs/preact/main.svg)](https://coveralls.io/github/preactjs/preact) +[![gzip size](http://img.badgesize.io/https://unpkg.com/preact/dist/preact.min.js?compression=gzip&label=gzip)](https://unpkg.com/preact/dist/preact.min.js) +[![brotli size](http://img.badgesize.io/https://unpkg.com/preact/dist/preact.min.js?compression=brotli&label=brotli)](https://unpkg.com/preact/dist/preact.min.js) +
-## Demos - -#### Real-World Apps - -- [**Preact Hacker News**](https://hn.kristoferbaxter.com) _([GitHub Project](https://github.com/kristoferbaxter/preact-hn))_ -- [**Play.cash**](https://play.cash) :notes: _([GitHub Project](https://github.com/feross/play.cash))_ -- [**BitMidi**](https://bitmidi.com/) 🎹 Wayback machine for free MIDI files _([GitHub Project](https://github.com/feross/bitmidi.com))_ -- [**Ultimate Guitar**](https://www.ultimate-guitar.com) 🎸speed boosted by Preact. -- [**ESBench**](http://esbench.com) is built using Preact. -- [**BigWebQuiz**](https://bigwebquiz.com) _([GitHub Project](https://github.com/jakearchibald/big-web-quiz))_ -- [**Nectarine.rocks**](http://nectarine.rocks) _([GitHub Project](https://github.com/developit/nectarine))_ :peach: -- [**TodoMVC**](https://preact-todomvc.surge.sh) _([GitHub Project](https://github.com/developit/preact-todomvc))_ -- [**OSS.Ninja**](https://oss.ninja) _([GitHub Project](https://github.com/developit/oss.ninja))_ -- [**GuriVR**](https://gurivr.com) _([GitHub Project](https://github.com/opennewslabs/guri-vr))_ -- [**Color Picker**](https://colors.now.sh) _([GitHub Project](https://github.com/lukeed/colors-app))_ :art: -- [**Offline Gallery**](https://use-the-platform.com/offline-gallery/) _([GitHub Project](https://github.com/vaneenige/offline-gallery/))_ :balloon: -- [**Periodic Weather**](https://use-the-platform.com/periodic-weather/) _([GitHub Project](https://github.com/vaneenige/periodic-weather/))_ :sunny: -- [**Rugby News Board**](http://nbrugby.com) _[(GitHub Project)](https://github.com/rugby-board/rugby-board-node)_ -- [**Preact Gallery**](https://preact.gallery/) an 8KB photo gallery PWA built using Preact. -- [**Rainbow Explorer**](https://use-the-platform.com/rainbow-explorer/) Preact app to translate real life color to digital color _([Github project](https://github.com/vaneenige/rainbow-explorer))_. -- [**YASCC**](https://carlosqsilva.github.io/YASCC/#/) Yet Another SoundCloud Client _([Github project](https://github.com/carlosqsilva/YASCC))_. -- [**Journalize**](https://preact-journal.herokuapp.com/) 14k offline-capable journaling PWA using preact. _([Github project](https://github.com/jpodwys/preact-journal))_. - - -#### Runnable Examples - -- [**Flickr Browser**](http://codepen.io/developit/full/VvMZwK/) (@ CodePen) -- [**Animating Text**](http://codepen.io/developit/full/LpNOdm/) (@ CodePen) -- [**60FPS Rainbow Spiral**](http://codepen.io/developit/full/xGoagz/) (@ CodePen) -- [**Simple Clock**](http://jsfiddle.net/developit/u9m5x0L7/embedded/result,js/) (@ JSFiddle) -- [**3D + ThreeJS**](http://codepen.io/developit/pen/PPMNjd?editors=0010) (@ CodePen) -- [**Stock Ticker**](http://codepen.io/developit/pen/wMYoBb?editors=0010) (@ CodePen) -- [*Create your Own!*](https://jsfiddle.net/developit/rs6zrh5f/embedded/result/) (@ JSFiddle) - -### Starter Projects - -- [**Preact Boilerplate**](https://preact-boilerplate.surge.sh) _([GitHub Project](https://github.com/developit/preact-boilerplate))_ :zap: -- [**Preact Offline Starter**](https://preact-starter.now.sh) _([GitHub Project](https://github.com/lukeed/preact-starter))_ :100: -- [**Preact PWA**](https://preact-pwa-yfxiijbzit.now.sh/) _([GitHub Project](https://github.com/ezekielchentnik/preact-pwa))_ :hamburger: -- [**Parcel + Preact + Unistore Starter**](https://github.com/hwclass/parcel-preact-unistore-starter) -- [**Preact Mobx Starter**](https://awaw00.github.io/preact-mobx-starter/) _([GitHub Project](https://github.com/awaw00/preact-mobx-starter))_ :sunny: -- [**Preact Redux Example**](https://github.com/developit/preact-redux-example) :star: -- [**Preact Redux/RxJS/Reselect Example**](https://github.com/continuata/preact-seed) -- [**V2EX Preact**](https://github.com/yanni4night/v2ex-preact) -- [**Preact Coffeescript**](https://github.com/crisward/preact-coffee) -- [**Preact + TypeScript + Webpack**](https://github.com/k1r0s/bleeding-preact-starter) -- [**0 config => Preact + Poi**](https://github.com/k1r0s/preact-poi-starter) -- [**Zero configuration => Preact + Typescript + Parcel**](https://github.com/aalises/preact-typescript-parcel-starter) - ---- - -## Libraries & Add-ons - -- :raised_hands: [**preact-compat**](https://git.io/preact-compat): use any React library with Preact *([full example](http://git.io/preact-compat-example))* -- :twisted_rightwards_arrows: [**preact-context**](https://github.com/valotas/preact-context): React's `createContext` api for Preact -- :page_facing_up: [**preact-render-to-string**](https://git.io/preact-render-to-string): Universal rendering. -- :eyes: [**preact-render-spy**](https://github.com/mzgoddard/preact-render-spy): Enzyme-lite: Renderer with access to the produced virtual dom for testing. -- :loop: [**preact-render-to-json**](https://git.io/preact-render-to-json): Render for Jest Snapshot testing. -- :earth_americas: [**preact-router**](https://git.io/preact-router): URL routing for your components -- :bookmark_tabs: [**preact-markup**](https://git.io/preact-markup): Render HTML & Custom Elements as JSX & Components -- :satellite: [**preact-portal**](https://git.io/preact-portal): Render Preact components into (a) SPACE :milky_way: -- :pencil: [**preact-richtextarea**](https://git.io/preact-richtextarea): Simple HTML editor component -- :bookmark: [**preact-token-input**](https://github.com/developit/preact-token-input): Text field that tokenizes input, for things like tags -- :card_index: [**preact-virtual-list**](https://github.com/developit/preact-virtual-list): Easily render lists with millions of rows ([demo](https://jsfiddle.net/developit/qqan9pdo/)) -- :repeat: [**preact-cycle**](https://git.io/preact-cycle): Functional-reactive paradigm for Preact -- :triangular_ruler: [**preact-layout**](https://download.github.io/preact-layout/): Small and simple layout library -- :thought_balloon: [**preact-socrates**](https://github.com/matthewmueller/preact-socrates): Preact plugin for [Socrates](http://github.com/matthewmueller/socrates) -- :rowboat: [**preact-flyd**](https://github.com/xialvjun/preact-flyd): Use [flyd](https://github.com/paldepind/flyd) FRP streams in Preact + JSX -- :speech_balloon: [**preact-i18nline**](https://github.com/download/preact-i18nline): Integrates the ecosystem around [i18n-js](https://github.com/everydayhero/i18n-js) with Preact via [i18nline](https://github.com/download/i18nline). -- :microscope: [**preact-jsx-chai**](https://git.io/preact-jsx-chai): JSX assertion testing _(no DOM, right in Node)_ -- :tophat: [**preact-classless-component**](https://github.com/ld0rman/preact-classless-component): create preact components without the class keyword -- :hammer: [**preact-hyperscript**](https://github.com/queckezz/preact-hyperscript): Hyperscript-like syntax for creating elements -- :white_check_mark: [**shallow-compare**](https://github.com/tkh44/shallow-compare): simplified `shouldComponentUpdate` helper. -- :shaved_ice: [**preact-codemod**](https://github.com/vutran/preact-codemod): Transform your React code to Preact. -- :construction_worker: [**preact-helmet**](https://github.com/download/preact-helmet): A document head manager for Preact -- :necktie: [**preact-delegate**](https://github.com/NekR/preact-delegate): Delegate DOM events -- :art: [**preact-stylesheet-decorator**](https://github.com/k1r0s/preact-stylesheet-decorator): Add Scoped Stylesheets to your Preact Components -- :electric_plug: [**preact-routlet**](https://github.com/k1r0s/preact-routlet): Simple `Component Driven` Routing for Preact using ES7 Decorators -- :fax: [**preact-bind-group**](https://github.com/k1r0s/preact-bind-group): Preact Forms made easy, Group Events into a Single Callback -- :hatching_chick: [**preact-habitat**](https://github.com/zouhir/preact-habitat): Declarative Preact widgets renderer in any CMS or DOM host ([demo](https://codepen.io/zouhir/pen/brrOPB)). -- :tada: [**proppy-preact**](https://github.com/fahad19/proppy): Functional props composition for Preact components - -#### UI Component Libraries - -> Want to prototype something or speed up your development? Try one of these toolkits: - -- [**preact-material-components**](https://github.com/prateekbh/preact-material-components): Material Design Components for Preact ([website](https://material.preactjs.com/)) -- [**preact-mdc**](https://github.com/BerndWessels/preact-mdc): Material Design Components for Preact ([demo](https://github.com/BerndWessels/preact-mdc-demo)) -- [**preact-mui**](https://git.io/v1aVO): The MUI CSS Preact library. -- [**preact-photon**](https://git.io/preact-photon): build beautiful desktop UI with [photon](http://photonkit.com) -- [**preact-mdl**](https://git.io/preact-mdl): [Material Design Lite](https://getmdl.io) for Preact -- [**preact-weui**](https://github.com/afeiship/preact-weui): [Weui](https://github.com/afeiship/preact-weui) for Preact - +You can find some awesome libraries in the [awesome-preact list](https://github.com/preactjs/awesome-preact) :sunglasses: --- @@ -161,352 +45,100 @@ Preact supports modern browsers and IE9+: > 💁 _**Note:** You [don't need ES2015 to use Preact](https://github.com/developit/preact-in-es3)... but give it a try!_ -The easiest way to get started with Preact is to install [Preact CLI](https://github.com/developit/preact-cli). This simple command-line tool wraps up the best possible Webpack and Babel setup for you, and even keeps you up-to-date as the underlying tools change. Best of all, it's easy to understand! It builds your app in a single command (`preact build`), doesn't need any configuration, and bakes in best-practises 🙌. - -The following guide assumes you have some sort of ES2015 build set up using babel and/or webpack/browserify/gulp/grunt/etc. - -You can also start with [preact-boilerplate] or a [CodePen Template](http://codepen.io/developit/pen/pgaROe?editors=0010). +#### Tutorial: Building UI with Preact +With Preact, you create user interfaces by assembling trees of components and elements. Components are functions or classes that return a description of what their tree should output. These descriptions are typically written in [JSX](https://facebook.github.io/jsx/) (shown underneath), or [HTM](https://github.com/developit/htm) which leverages standard JavaScript Tagged Templates. Both syntaxes can express trees of elements with "props" (similar to HTML attributes) and children. -### Import what you need - -The `preact` module provides both named and default exports, so you can either import everything under a namespace of your choosing, or just what you need as locals: - -##### Named: - -```js -import { h, render, Component } from 'preact'; - -// Tell Babel to transform JSX into h() calls: -/** @jsx h */ -``` - -##### Default: - -```js -import preact from 'preact'; - -// Tell Babel to transform JSX into preact.h() calls: -/** @jsx preact.h */ -``` - -> Named imports work well for highly structured applications, whereas the default import is quick and never needs to be updated when using different parts of the library. -> -> Instead of declaring the `@jsx` pragma in your code, it's best to configure it globally in a `.babelrc`: -> -> **For Babel 5 and prior:** -> -> ```json -> { "jsxPragma": "h" } -> ``` -> -> **For Babel 6:** -> -> ```json -> { -> "plugins": [ -> ["transform-react-jsx", { "pragma":"h" }] -> ] -> } -> ``` -> -> **For Babel 7:** -> -> ```json -> { -> "plugins": [ -> ["@babel/plugin-transform-react-jsx", { "pragma":"h" }] -> ] -> } -> ``` -> **For using Preact along with TypeScript add to `tsconfig.json`:** -> -> ```json -> { -> "jsx": "react", -> "jsxFactory": "h", -> } -> ``` - - -### Rendering JSX - -Out of the box, Preact provides an `h()` function that turns your JSX into Virtual DOM elements _([here's how](http://jasonformat.com/wtf-is-jsx))_. It also provides a `render()` function that creates a DOM tree from that Virtual DOM. - -To render some JSX, just import those two functions and use them like so: +To get started using Preact, first look at the render() function. This function accepts a tree description and creates the structure described. Next, it appends this structure to a parent DOM element provided as the second argument. Future calls to render() will reuse the existing tree and update it in-place in the DOM. Internally, render() will calculate the difference from previous outputted structures in an attempt to perform as few DOM operations as possible. ```js import { h, render } from 'preact'; +// Tells babel to use h for JSX. It's better to configure this globally. +// See https://babeljs.io/docs/en/babel-plugin-transform-react-jsx#usage +// In tsconfig you can specify this with the jsxFactory +/** @jsx h */ -render(( -
- Hello, world! - -
-), document.body); -``` - -This should seem pretty straightforward if you've used hyperscript or one of its many friends. If you're not, the short of it is that the `h()` function import gets used in the final, transpiled code as a drop in replacement for `React.createElement()`, and so needs to be imported even if you don't explicitly use it in the code you write. Also note that if you're the kind of person who likes writing your React code in "pure JavaScript" (you know who you are) you will need to use `h()` wherever you would otherwise use `React.createElement()`. - -Rendering hyperscript with a virtual DOM is pointless, though. We want to render components and have them updated when data changes - that's where the power of virtual DOM diffing shines. :star2: - - -### Components - -Preact exports a generic `Component` class, which can be extended to build encapsulated, self-updating pieces of a User Interface. Components support all of the standard React [lifecycle methods], like `shouldComponentUpdate()` and `componentWillReceiveProps()`. Providing specific implementations of these methods is the preferred mechanism for controlling _when_ and _how_ components update. - -Components also have a `render()` method, but unlike React this method is passed `(props, state)` as arguments. This provides an ergonomic means to destructure `props` and `state` into local variables to be referenced from JSX. - -Let's take a look at a very simple `Clock` component, which shows the current time. - -```js -import { h, render, Component } from 'preact'; - -class Clock extends Component { - render() { - let time = new Date(); - return ; - } -} - -// render an instance of Clock into : -render(, document.body); -``` - - -That's great. Running this produces the following HTML DOM structure: - -```html -10:28:57 PM -``` - -In order to have the clock's time update every second, we need to know when `` gets mounted to the DOM. _If you've used HTML5 Custom Elements, this is similar to the `attachedCallback` and `detachedCallback` lifecycle methods._ Preact invokes the following lifecycle methods if they are defined for a Component: - -| Lifecycle method | When it gets called | -|-----------------------------|--------------------------------------------------| -| `componentWillMount` | before the component gets mounted to the DOM | -| `componentDidMount` | after the component gets mounted to the DOM | -| `componentWillUnmount` | prior to removal from the DOM | -| `componentWillReceiveProps` | before new props get accepted | -| `shouldComponentUpdate` | before `render()`. Return `false` to skip render | -| `componentWillUpdate` | before `render()` | -| `componentDidUpdate` | after `render()` | - - - -So, we want to have a 1-second timer start once the Component gets added to the DOM, and stop if it is removed. We'll create the timer and store a reference to it in `componentDidMount()`, and stop the timer in `componentWillUnmount()`. On each timer tick, we'll update the component's `state` object with a new time value. Doing this will automatically re-render the component. - -```js -import { h, render, Component } from 'preact'; - -class Clock extends Component { - constructor() { - super(); - // set initial time: - this.state = { - time: Date.now() - }; - } - - componentDidMount() { - // update time every second - this.timer = setInterval(() => { - this.setState({ time: Date.now() }); - }, 1000); - } - - componentWillUnmount() { - // stop when not renderable - clearInterval(this.timer); - } - - render(props, state) { - let time = new Date(state.time).toLocaleTimeString(); - return { time }; - } -} - -// render an instance of Clock into : -render(, document.body); -``` - -Now we have [a ticking clock](http://jsfiddle.net/developit/u9m5x0L7/embedded/result,js/)! - - -### Props & State - -The concept (and nomenclature) for `props` and `state` is the same as in React. `props` are passed to a component by defining attributes in JSX, `state` is internal state. Changing either triggers a re-render, though by default Preact re-renders Components asynchronously for `state` changes and synchronously for `props` changes. You can tell Preact to render `prop` changes asynchronously by setting `options.syncComponentUpdates` to `false`. - - ---- - - -## Linked State - -One area Preact takes a little further than React is in optimizing state changes. A common pattern in ES2015 React code is to use Arrow functions within a `render()` method in order to update state in response to events. Creating functions enclosed in a scope on every render is inefficient and forces the garbage collector to do more work than is necessary. - -One solution to this is to bind component methods declaratively. -Here is an example using [decko](http://git.io/decko): - -```js -class Foo extends Component { - @bind - updateText(e) { - this.setState({ text: e.target.value }); - } - render({ }, { text }) { - return ; - } -} -``` - -While this achieves much better runtime performance, it's still a lot of unnecessary code to wire up state to UI. - -Fortunately there is a solution, in the form of a module called [linkstate](https://github.com/developit/linkstate). Calling `linkState(component, 'text')` returns a function that accepts an Event and uses its associated value to update the given property in your component's state. Calls to `linkState()` with the same arguments are cached, so there is no performance penalty. Here is the previous example rewritten using _Linked State_: - -```js -import linkState from 'linkstate'; - -class Foo extends Component { - render({ }, { text }) { - return ; - } -} -``` - -Simple and effective. It handles linking state from any input type, or an optional second parameter can be used to explicitly provide a keypath to the new state value. - -> **Note:** In Preact 7 and prior, `linkState()` was built right into Component. In 8.0, it was moved to a separate module. You can restore the 7.x behavior by using linkstate as a polyfill - see [the linkstate docs](https://github.com/developit/linkstate#usage). - - - -## Examples - -Here is a somewhat verbose Preact `` component: - -```js -class Link extends Component { - render(props, state) { - return {props.children}; - } -} -``` - -Since this is ES6/ES2015, we can further simplify: - -```js -class Link extends Component { - render({ href, children }) { - return ; - } -} - -// or, for wide-open props support: -class Link extends Component { - render(props) { - return ; - } -} - -// or, as a stateless functional component: -const Link = ({ children, ...props }) => ( - { children } +// create our tree and append it to document.body: +render( +
+

Hello

+
, + document.body ); -``` - -## Extensions - -It is likely that some projects based on Preact would wish to extend Component with great new functionality. - -Perhaps automatic connection to stores for a Flux-like architecture, or mixed-in context bindings to make it feel more like `React.createClass()`. Just use ES2015 inheritance: - -```js -class BoundComponent extends Component { - constructor(props) { - super(props); - this.bind(); - } - bind() { - this.binds = {}; - for (let i in this) { - this.binds[i] = this[i].bind(this); - } - } -} - -// example usage -class Link extends BoundComponent { - click() { - open(this.href); - } - render() { - let { click } = this.binds; - return { children }; - } -} +// update the tree in-place: +render( +
+

Hello World!

+
, + document.body +); +// ^ this second invocation of render(...) will use a single DOM call to update the text of the

``` - -The possibilities are pretty endless here. You could even add support for rudimentary mixins: +Hooray! render() has taken our structure and output a User Interface! This approach demonstrates a simple case, but would be difficult to use as an application grows in complexity. Each change would be forced to calculate the difference between the current and updated structure for the entire application. Components can help here – by dividing the User Interface into nested Components each can calculate their difference from their mounted point. Here's an example: ```js -class MixedComponent extends Component { - constructor() { - super(); - (this.mixins || []).forEach( m => Object.assign(this, m) ); - } -} -``` - -## Debug Mode - -You can inspect and modify the state of your Preact UI components at runtime using the -[React Developer Tools](https://github.com/facebook/react-devtools) browser extension. - -1. Install the [React Developer Tools](https://github.com/facebook/react-devtools) extension -2. Import the "preact/debug" module in your app -3. Set `process.env.NODE_ENV` to 'development' -4. Reload and go to the 'React' tab in the browser's development tools - +import { render, h } from 'preact'; +import { useState } from 'preact/hooks'; -```js -import { h, Component, render } from 'preact'; - -// Enable debug mode. You can reduce the size of your app by only including this -// module in development builds. eg. In Webpack, wrap this with an `if (module.hot) {...}` -// check. -require('preact/debug'); -``` +/** @jsx h */ -### Runtime Error Checking +const App = () => { + const [input, setInput] = useState(''); -To enable debug mode, you need to set `process.env.NODE_ENV=development`. You can do this -with webpack via a builtin plugin. + return ( +
+

Do you agree to the statement: "Preact is awesome"?

+ setInput(e.target.value)} /> +
+ ); +}; -```js -// webpack.config.js - -// Set NODE_ENV=development to enable error checking -new webpack.DefinePlugin({ - 'process.env': { - 'NODE_ENV': JSON.stringify('development') - } -}); +render(, document.body); ``` -When enabled, warnings are logged to the console when undefined components or string refs -are detected. - -### Developer Tools +--- -If you only want to include devtool integration, without runtime error checking, you can -replace `preact/debug` in the above example with `preact/devtools`. This option doesn't -require setting `NODE_ENV=development`. +## Sponsors +Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor](https://opencollective.com/preact#sponsor)] + + + + + + + + + + + + + + + + + +             + + + + + + + + + + + + + + ## Backers + Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/preact#backer)] @@ -540,53 +172,14 @@ Support us with a monthly donation and help us continue our activities. [[Become - -## Sponsors -Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor](https://opencollective.com/preact#sponsor)] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +--- ## License MIT +[![Preact](https://i.imgur.com/YqCHvEW.gif)](https://preactjs.com) - -[![Preact](http://i.imgur.com/YqCHvEW.gif)](https://preactjs.com) - - -[preact-compat]: https://github.com/developit/preact-compat -[ES6 Class]: https://facebook.github.io/react/docs/reusable-components.html#es6-classes -[Functional Components]: https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#stateless-functional-components +[preact/compat]: https://github.com/preactjs/preact/tree/main/compat [hyperscript]: https://github.com/dominictarr/hyperscript -[preact-boilerplate]: https://github.com/developit/preact-boilerplate -[lifecycle methods]: https://facebook.github.io/react/docs/component-specs.html +[DevTools]: https://github.com/preactjs/preact-devtools diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000000..9601cdf30b --- /dev/null +++ b/babel.config.js @@ -0,0 +1,46 @@ +module.exports = function (api) { + api.cache(true); + + const noModules = String(process.env.BABEL_NO_MODULES) === 'true'; + + const rename = {}; + const mangle = require('./mangle.json'); + for (let prop in mangle.props.props) { + let name = prop; + if (name[0] === '$') { + name = name.slice(1); + } + + rename[name] = mangle.props.props[prop]; + } + + return { + presets: [ + [ + '@babel/preset-env', + { + loose: true, + // Don't transform modules when using esbuild + modules: noModules ? false : 'auto', + exclude: ['@babel/plugin-transform-typeof-symbol'], + targets: { + browsers: ['last 2 versions', 'IE >= 9'] + } + } + ] + ], + plugins: [ + '@babel/plugin-proposal-object-rest-spread', + '@babel/plugin-transform-react-jsx', + 'babel-plugin-transform-async-to-promises', + ['babel-plugin-transform-rename-properties', { rename }] + ], + include: ['**/src/**/*.js', '**/test/**/*.js'], + overrides: [ + { + test: /(component-stack|debug)\.test\.js$/, + plugins: ['@babel/plugin-transform-react-jsx-source'] + } + ] + }; +}; diff --git a/benches/.gitignore b/benches/.gitignore new file mode 100644 index 0000000000..507e011806 --- /dev/null +++ b/benches/.gitignore @@ -0,0 +1,6 @@ +dist/ +results/ +logs/ +logs-saved/ +node_modules/ +proxy-packages/*/package-lock.json diff --git a/benches/LICENSE b/benches/LICENSE new file mode 100644 index 0000000000..da5389a93c --- /dev/null +++ b/benches/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-present Jason Miller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/benches/README.md b/benches/README.md new file mode 100644 index 0000000000..066b5fae34 --- /dev/null +++ b/benches/README.md @@ -0,0 +1,72 @@ +# Preact Benchmarks + +This directory contains benchmarks for Preact, run using the [`polymer/tachometer`](https://github.com/polymer/tachometer) project. + +## Getting Started + +To run benchmark suite, use `npm bench`. + +To debug benches locally, use `npm start`. + +### bench + +Use the `npm bench` command to run some (or all, the default) benchmarks locally. + +```text +> node ./scripts bench "--help" + + Description + Run the benchmarks matching the given globs. + The root for the globs is the "src" directory. + Specify "all" to run all benchmarks. + To get more help on options, see polymer/tachometer help. + Result table is printed to stdout and written to a csv and json file in the results directory. + + Usage + $ ./scripts bench [globs] [options] + + Options + -n, --sample-size Minimum number of times to run each benchmark (default 50) + --horizon The degrees of difference to try and resolve when auto-sampling ("N%" or "Nms", comma-delimited) (default 10%) + --timeout The maximum number of minutes to spend auto-sampling (default 3) + -h, --help Displays this message + + Examples + $ ./scripts bench text* + $ ./scripts bench **/*.html + $ ./scripts bench all + +``` + +The `bench` command generates a tachometer config file for each benchmark matching the given globs. It then runs tachometer on each config file. If you would create one config file containing all the benchmarks, then Tachometer would produce one table comparing all package versions to each other across benchmarks which wouldn't be as useful. By generating a config per benchmark, Tachometer will output a table per benchmark comparing how each package version performed on just that benchmark. + +### start + +Use the `npm start` command to start the benchmark server but not run the benchmarks. This command is useful to debug or profile how a benchmark is doing. Tachometer starts a web server for each package version we run (note the port difference per package version in the sample output below). It provides a sample URL at which to point your browser to run a benchmark. You can run any benchmark in the `src` directory, not just the one in the sample URL. + +```bash +> npm start + +...snipped some debug output... + +Visit these URLs in any browser: + +text_update [@preact8] +http://127.0.0.1:8080/src/text_update.html + +text_update [@preact10] +http://127.0.0.1:8081/src/text_update.html + +text_update [@preactLocal] +http://127.0.0.1:8082/src/text_update.html +``` + +## Contributing + +To contribute a new benchmark, look at the existing benchmarks (the HTML files in `src`) to get an idea of what a benchmark can look like. Then read up on how [`polymer/tachometer`](https://github.com/polymer/tachometer) works to understand some of the options available to you. + +Add an HTML file containing the benchmark you'd like to run. Use `npm start` (documented above) to test and debug your benchmark. Then run `npm bench YOUR_BENCH.html` to run it. Note while initialling developing it may be easier to limit the amount of samples taken while benching. Use the options documented for the `npm bench` command to customize the sample size and auto-sample timeout. + +Currently this infra is only setup to run benchmarks against different preact versions and requires that your benchmark use the `bench.start()` and `bench.stop()` methods. + +The `src/util.js` file contains some utility functions for running your benchmark. For, example the `afterFrame/afterFrameAsync` functions can be used to run `bench.stop()` after the browser as painted the next frame. The `testElement/testElementText` functions can be used to verify that the benchmark implementation rendered the expected result properly. diff --git a/benches/TODO.md b/benches/TODO.md new file mode 100644 index 0000000000..b617c549c4 --- /dev/null +++ b/benches/TODO.md @@ -0,0 +1,10 @@ +- Add `preact-release` proxy + - to capture slowdowns overtime +- Report `initial-run` metric to PR + - to capture unoptimized runtime which would be an important metric to understand perf characteristic before optimizations kick in +- Add warmup reporting to all benchmarks +- Add `preact-compat` proxy +- Add UIBench +- Add bench mimicking speedometer +- Add a realworld-like bench? +- Add a specialized bench that hits certain code paths other's miss (e.g. style attribute handling?) diff --git a/benches/jsconfig.json b/benches/jsconfig.json new file mode 100644 index 0000000000..6ae4de3071 --- /dev/null +++ b/benches/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "es2022", + "checkJs": true, + "moduleResolution": "node" + }, + "exclude": ["node_modules", "dist"] +} diff --git a/benches/package-lock.json b/benches/package-lock.json new file mode 100644 index 0000000000..1e473eadaf --- /dev/null +++ b/benches/package-lock.json @@ -0,0 +1,7320 @@ +{ + "name": "preact-benchmarks", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "preact-benchmarks", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "afterframe": "^1.0.1" + }, + "devDependencies": { + "del": "^7.0.0", + "escalade": "^3.1.1", + "escape-string-regexp": "^5.0.0", + "globby": "^13.1.2", + "prompts": "^2.4.2", + "puppeteer": "^21.4.1", + "sade": "^1.8.1", + "strip-ansi": "^7.0.1", + "tachometer": "^0.7.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.11.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "node_modules/@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.8.0.tgz", + "integrity": "sha512-TkRHIV6k2D8OlUe8RtG+5jgOF/H98Myx0M6AOafC8DdNVOFiBSFa5cpRDtpm8LXOa9sVwe0+e6Q3FC56X/DZfg==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.1", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.3.0" + } + }, + "node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@puppeteer/browsers/node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "dependencies": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "node_modules/@puppeteer/browsers/node_modules/tar-stream": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", + "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.3.0.tgz", + "integrity": "sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, + "node_modules/@types/babel__generator": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", + "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/execa": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@types/execa/-/execa-0.9.0.tgz", + "integrity": "sha512-mgfd93RhzjYBUHHV532turHC2j4l/qxsF/PbfDmprHDEUHmNZGlDn1CEsulGK3AfsPdhkWzZQT/S/k0UGhLGsA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "14.11.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz", + "integrity": "sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==", + "dev": true + }, + "node_modules/@types/parse5": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz", + "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==", + "dev": true + }, + "node_modules/@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/afterframe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/afterframe/-/afterframe-1.0.1.tgz", + "integrity": "sha512-0AOAf3V8eplIEx7WCTwL6aMhVuW4OPWMUiIV8DB5nvdjutGQcG+EyRXVgCjSXyVE7wIXu3O+KS+eFcf7XTwanA==" + }, + "node_modules/aggregate-error": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", + "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==", + "dev": true, + "dependencies": { + "clean-stack": "^4.0.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ajv": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.0.tgz", + "integrity": "sha512-svS9uILze/cXbH0z2myCK2Brqprx/+JJYK5pHicT/GQiBfzzhUVAIT6MwqJg8y4xV/zoGsUeuPuwtoiKSGE15g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "node_modules/ansi-escape-sequences": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-6.2.1.tgz", + "integrity": "sha512-0gK95MrLXv+Vy5h4eKGvSX1yXopBqSYBi3/w4hekUxs/hHakF6asH9Gg7UXbb7IH9weAlVIrUzVOITNBr8Imag==", + "dev": true, + "dependencies": { + "array-back": "^6.2.2" + }, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/ansi-escape-sequences/node_modules/array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "dev": true, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-back": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz", + "integrity": "sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "node_modules/basic-ftp": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz", + "integrity": "sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", + "dev": true + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.2.tgz", + "integrity": "sha512-KxjQZM3UIo7/J6W4sLpwFvu1GB3Whv8NtZ8ZrUL284eiQjiXeeqWTdhixNrp/NLZ/JNuFBo6BD4ZaO8ZJ5BN8Q==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "^4.0.1", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.0", + "keyv": "^4.5.0", + "mimic-response": "^4.0.0", + "normalize-url": "^7.2.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/chromium-bidi": { + "version": "0.4.32", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.32.tgz", + "integrity": "sha512-RJnw0PW3sNdx1WclINVfVVx8JUH+tWTHZNpnEzlcM+Qgvf40dUH34U7gJq+cc/0LE+rbPxeT6ldqWrCbUf4jeg==", + "dev": true, + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "9.0.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/clean-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz", + "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/co-body": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.0.0.tgz", + "integrity": "sha512-9ZIcixguuuKIptnY8yemEOuhb71L/lLf+Rl5JfJEUiDNJk0e02MBt7BPxR2GEh5mw8dPthQYR4jPI/BnS1MQgw==", + "dev": true, + "dependencies": { + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/command-line-args": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.1.tgz", + "integrity": "sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==", + "dev": true, + "dependencies": { + "array-back": "^3.0.1", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/command-line-args/node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/command-line-usage": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.0.tgz", + "integrity": "sha512-Ew1clU4pkUeo6AFVDFxCbnN7GIZfXl48HIOQeFQnkO3oOqvpI7wdqtLRwv9iOCZ/7A+z4csVZeiDdEcj8g6Wiw==", + "dev": true, + "dependencies": { + "array-back": "^4.0.0", + "chalk": "^2.4.2", + "table-layout": "^1.0.0", + "typical": "^5.2.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/command-line-usage/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookies": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", + "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "dev": true, + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cookies/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/copy-to": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz", + "integrity": "sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU=", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/cross-spawn/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/csv-stringify": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.2.0.tgz", + "integrity": "sha512-dcUbQLRTTDcgQxgEU8V9IctkaCwHZjZfzUZ5ZB3RY8Y+pXtdtl5iVQHfGzANytFFkRKanYzBXrkfpNdGR7eviA==", + "dev": true + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz", + "integrity": "sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/del": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-7.0.0.tgz", + "integrity": "sha512-tQbV/4u5WVB8HMJr08pgw0b6nG4RGt/tj+7Numvq+zqcvUFeMaIWWOUFltiU+6go8BSO2/ogsB4EasDaj0y68Q==", + "dev": true, + "dependencies": { + "globby": "^13.1.2", + "graceful-fs": "^4.2.10", + "is-glob": "^4.0.3", + "is-path-cwd": "^3.0.0", + "is-path-inside": "^4.0.0", + "p-map": "^5.5.0", + "rimraf": "^3.0.2", + "slash": "^4.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "node_modules/devtools-protocol": { + "version": "0.0.818844", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz", + "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==", + "dev": true, + "peer": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/find-replace/node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.3.tgz", + "integrity": "sha512-KqU0nnPMgIJcCOFTNJFEA8epcseEaoox4XZffTgy8jlI6pL/5EFyR54NRG7CnCJN0biY7q52DO3MH6/sJ/TKlQ==", + "dev": true, + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.2.tgz", + "integrity": "sha512-5KLucCJobh8vBY1K07EFV4+cPZH3mrV9YeAruUseCQKHB58SGjjT2l9/eA9LD082IiuMjSlFJEcdJ27TXvbZNw==", + "dev": true, + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.0", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/get-uri/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/get-uri/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/get-uri/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", + "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got": { + "version": "12.5.2", + "resolved": "https://registry.npmjs.org/got/-/got-12.5.2.tgz", + "integrity": "sha512-guHGMSEcsA5m1oPRweXUJnug0vuvlkX9wx5hzOka+ZBrBUOJHU0Z1JcNu3QE5IPGnA5aXUsQHdWOD4eJg9/v3A==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.1", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/http-assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", + "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", + "dev": true, + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.7.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-assert/node_modules/http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "node_modules/http-errors": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http2-wrapper": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.1.11.tgz", + "integrity": "sha512-aNAk5JzLturWEUiuhAN73Jcbq96R7rTitAoXV54FYMatvihnpD2+6PUgU4ce3D/m5VDbw+F5CsyKSF176ptitQ==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflation": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz", + "integrity": "sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-3.0.0.tgz", + "integrity": "sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonschema": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", + "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dev": true, + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/jstat": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/jstat/-/jstat-1.9.4.tgz", + "integrity": "sha512-IiTPlI7pcrsq41EpDzrghlA1fhiC9GXxNqO4k5ogsjsM1XAWQ8zESH/bZsExLVgQsYpXE+7c11kEbbuxTLUpJQ==", + "dev": true + }, + "node_modules/jszip": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz", + "integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==", + "dev": true, + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/keyv": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.0.tgz", + "integrity": "sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/koa": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.13.0.tgz", + "integrity": "sha512-i/XJVOfPw7npbMv67+bOeXr3gPqOAw6uh5wFyNs3QvJ47tUx3M3V9rIE0//WytY42MKz4l/MXKyGkQ2LQTfLUQ==", + "dev": true, + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.8.0", + "debug": "~3.1.0", + "delegates": "^1.0.0", + "depd": "^1.1.2", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^1.2.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/koa-bodyparser": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/koa-bodyparser/-/koa-bodyparser-4.3.0.tgz", + "integrity": "sha512-uyV8G29KAGwZc4q/0WUAjH+Tsmuv9ImfBUF2oZVyZtaeo0husInagyn/JH85xMSxM0hEk/mbCII5ubLDuqW/Rw==", + "dev": true, + "dependencies": { + "co-body": "^6.0.0", + "copy-to": "^2.0.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "node_modules/koa-convert": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", + "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "dev": true, + "dependencies": { + "co": "^4.6.0", + "koa-compose": "^3.0.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/koa-convert/node_modules/koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "dev": true, + "dependencies": { + "any-promise": "^1.1.0" + } + }, + "node_modules/koa-mount": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/koa-mount/-/koa-mount-4.0.0.tgz", + "integrity": "sha512-rm71jaA/P+6HeCpoRhmCv8KVBIi0tfGuO/dMKicbQnQW/YJntJ6MnnspkodoA4QstMVEZArsCphmd0bJEtoMjQ==", + "dev": true, + "dependencies": { + "debug": "^4.0.1", + "koa-compose": "^4.1.0" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/koa-mount/node_modules/debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/koa-node-resolve": { + "version": "1.0.0-pre.9", + "resolved": "https://registry.npmjs.org/koa-node-resolve/-/koa-node-resolve-1.0.0-pre.9.tgz", + "integrity": "sha512-WKgqe5TGVD6zuR3NrKnmbb/NNHIbWOCezQVqqnyQLdtLLXWgiothlUQT23S5qQGE0Z623jp6jxpMjvAqyrcZFQ==", + "dev": true, + "dependencies": { + "@babel/generator": "^7.4.4", + "@babel/parser": "^7.4.5", + "@babel/traverse": "^7.4.5", + "@types/babel__generator": "^7.6.1", + "@types/parse5": "^5.0.0", + "get-stream": "^5.1.0", + "parse5": "^5.1.0", + "resolve": "^1.11.0" + } + }, + "node_modules/koa-node-resolve/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/koa-send": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz", + "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "resolve-path": "^1.4.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/koa-send/node_modules/debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "dev": true, + "dependencies": { + "debug": "^3.1.0", + "koa-send": "^5.0.0" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.1.1.tgz", + "integrity": "sha512-vJXaRMJgRVD3+cUZs3Mncj2mxpt5mP0EmNOsxRSZRMlbqjvxzDEOIUWXGmavo0ZC9+tNZCBLQ66reA11nbpHZg==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=", + "dev": true + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=", + "dev": true + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=", + "dev": true + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "dev": true + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", + "dev": true + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, + "dependencies": { + "mime-db": "1.44.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/mri": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.5.tgz", + "integrity": "sha512-d2RKzMD4JNyHMbnbWnznPaa8vbdlq/4pNZ3IgdaGrVbBhebBsGUUE/6qorTMYNS6TwuH3ilfOlD2bf4Igh8CKg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/normalize-url": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-7.2.0.tgz", + "integrity": "sha512-uhXOdZry0L6M2UIo9BTt7FdpBDiAGN/7oItedQwPKh8jh31ZlvC8U9Xl/EJ3aijDHaywXTW3QbZ6LuCocur1YA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", + "dev": true + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz", + "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==", + "dev": true, + "dependencies": { + "aggregate-error": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "dev": true, + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", + "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "dev": true, + "dependencies": { + "degenerator": "^5.0.0", + "ip": "^1.1.8", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-install": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-install/-/pkg-install-1.0.0.tgz", + "integrity": "sha512-UGI8bfhrDb1KN01RZ7Bq08GRQc8rmVjxQ2up0g4mUHPCYDTK1FzQ0PMmLOBCHg3yaIijZ2U3Fn9ofLa4N392Ug==", + "dev": true, + "dependencies": { + "@types/execa": "^0.9.0", + "@types/node": "^11.9.4", + "execa": "^1.0.0" + } + }, + "node_modules/pkg-install/node_modules/@types/node": { + "version": "11.15.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.15.27.tgz", + "integrity": "sha512-LbLwyGC/ukDV0EbHFP1OCfs2V5h3vUS8ZXJJjS2L5YYg8rNkJe6Tl/yv+L+g94sbHllyXUCfUCn5+sZLBegvyw==", + "dev": true + }, + "node_modules/pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-4.0.0.tgz", + "integrity": "sha512-N4zdA4sfOe6yCv+ulPCmpnIBQ5I60xfhDr1otdBBhKte9QtEf3bhfrfkW7dTb+IQ0iEx4ZDzas0kc1o5rdWpYg==", + "dev": true, + "dependencies": { + "find-up": "^6.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proxy-agent": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.1.tgz", + "integrity": "sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "21.4.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-21.4.1.tgz", + "integrity": "sha512-opJqQeYMjAB3ICG8lCF3wtSs9k05dozmrEMrHgo3ZWbISiy8qbv/yAJz/6Io221qSh3yURfVf6Z7crrlzKZjLQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "1.8.0", + "cosmiconfig": "8.3.6", + "puppeteer-core": "21.4.1" + }, + "engines": { + "node": ">=16.3.0" + } + }, + "node_modules/puppeteer/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/puppeteer/node_modules/devtools-protocol": { + "version": "0.0.1191157", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1191157.tgz", + "integrity": "sha512-Fu2mUhX7zkzLHMJZk5wQTiHdl1eJrhK0GypUoSzogUt51MmYEv/46pCz4PtGGFlr0f2ZyYDzzx5CPtbEkuvcTA==", + "dev": true + }, + "node_modules/puppeteer/node_modules/puppeteer-core": { + "version": "21.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-21.4.1.tgz", + "integrity": "sha512-Lh0e+oGhUquxVOi1U701gTfFLFvw5gDBFh3CWpnfAvtItmyZKUce4R54VNfOJfi+KKnzhVPdB/lDrg65gdRIng==", + "dev": true, + "dependencies": { + "@puppeteer/browsers": "1.8.0", + "chromium-bidi": "0.4.32", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1191157", + "ws": "8.14.2" + }, + "engines": { + "node": ">=16.3.0" + } + }, + "node_modules/puppeteer/node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/raw-body": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", + "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.3", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/reduce-flatten": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", + "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.6" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=", + "dev": true, + "dependencies": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/resolve-path/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/resolve-path/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "dev": true, + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/selenium-webdriver": { + "version": "4.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.8.tgz", + "integrity": "sha512-yPSaiWySZTEbxuuWQMDqdXh3H3N4Aiw/bSUjpkKMPWWCysfPqUncrq6FewBqdxWD1wQKzy5yWaQMGsgTY/0rCQ==", + "dev": true, + "dependencies": { + "jszip": "^3.5.0", + "rimraf": "^2.7.1", + "tmp": "^0.1.0", + "ws": "^7.3.1" + }, + "engines": { + "node": ">= 10.15.0" + } + }, + "node_modules/semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", + "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks/node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/streamx": { + "version": "2.15.2", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.2.tgz", + "integrity": "sha512-b62pAV/aeMjUoRN2C/9F0n+G8AfcJjNC0zw/ZmOHeFsIe4m4GzjVW9m6VHXVjk536NbdU9JRwKMJRfkc+zUFTg==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/systeminformation": { + "version": "5.12.10", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.12.10.tgz", + "integrity": "sha512-rLShZFpz14aWqg3nNCS8zw74WicRV6c3R17Ob7oO37zSypZATT4M2Dgjr4nE6PYGlome9ObU0A2uqXiAlaBbuA==", + "dev": true, + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, + "node_modules/table": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", + "dev": true, + "dependencies": { + "ajv": "^7.0.2", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table-layout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.1.tgz", + "integrity": "sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==", + "dev": true, + "dependencies": { + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/table-layout/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tachometer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/tachometer/-/tachometer-0.7.0.tgz", + "integrity": "sha512-163DdzoNkjZlr/m3IpaPMYBOTUc54hzfGSUw7pv9ZoO3OkjDcoGqJGImcrfEDNsw3DD0J/FM5AWjEi2/0W4YZA==", + "dev": true, + "dependencies": { + "ansi-escape-sequences": "^6.0.1", + "command-line-args": "^5.0.2", + "command-line-usage": "^6.1.0", + "csv-stringify": "^6.2.0", + "fs-extra": "^10.0.0", + "get-stream": "^6.0.0", + "got": "^12.1.0", + "jsonschema": "^1.4.0", + "jsonwebtoken": "^8.5.1", + "jstat": "^1.9.2", + "koa": "^2.11.0", + "koa-bodyparser": "^4.2.1", + "koa-mount": "^4.0.0", + "koa-node-resolve": "^1.0.0-pre.8", + "koa-send": "^5.0.0", + "koa-static": "^5.0.0", + "pkg-install": "^1.0.0", + "pkg-up": "^4.0.0", + "progress": "^2.0.3", + "sanitize-filename": "^1.6.3", + "selenium-webdriver": "^4.0.0-alpha.8", + "semver": "^7.1.1", + "source-map-support": "^0.5.16", + "strip-ansi": "^7.0.1", + "systeminformation": "^5.3.3", + "table": "^6.0.7", + "ua-parser-js": "^1.0.2" + }, + "bin": { + "tach": "bin/tach.js", + "tachometer": "bin/tach.js" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "dev": true, + "dependencies": { + "rimraf": "^2.6.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", + "dev": true, + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true, + "engines": { + "node": ">=0.6.x" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ua-parser-js": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", + "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-9.0.0.tgz", + "integrity": "sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==", + "dev": true + }, + "node_modules/utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/wordwrapjs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.0.tgz", + "integrity": "sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ==", + "dev": true, + "dependencies": { + "reduce-flatten": "^2.0.0", + "typical": "^5.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/wordwrapjs/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/ws": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", + "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==", + "dev": true, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/ylru": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", + "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@puppeteer/browsers": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.8.0.tgz", + "integrity": "sha512-TkRHIV6k2D8OlUe8RtG+5jgOF/H98Myx0M6AOafC8DdNVOFiBSFa5cpRDtpm8LXOa9sVwe0+e6Q3FC56X/DZfg==", + "dev": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.1", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.2" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "requires": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "tar-stream": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", + "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "dev": true, + "requires": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + } + } + }, + "@sindresorhus/is": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.3.0.tgz", + "integrity": "sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "requires": { + "defer-to-connect": "^2.0.1" + } + }, + "@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, + "@types/babel__generator": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", + "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/execa": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@types/execa/-/execa-0.9.0.tgz", + "integrity": "sha512-mgfd93RhzjYBUHHV532turHC2j4l/qxsF/PbfDmprHDEUHmNZGlDn1CEsulGK3AfsPdhkWzZQT/S/k0UGhLGsA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "dev": true + }, + "@types/node": { + "version": "14.11.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz", + "integrity": "sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==", + "dev": true + }, + "@types/parse5": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz", + "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==", + "dev": true + }, + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "afterframe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/afterframe/-/afterframe-1.0.1.tgz", + "integrity": "sha512-0AOAf3V8eplIEx7WCTwL6aMhVuW4OPWMUiIV8DB5nvdjutGQcG+EyRXVgCjSXyVE7wIXu3O+KS+eFcf7XTwanA==" + }, + "aggregate-error": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", + "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==", + "dev": true, + "requires": { + "clean-stack": "^4.0.0", + "indent-string": "^5.0.0" + } + }, + "ajv": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.0.tgz", + "integrity": "sha512-svS9uILze/cXbH0z2myCK2Brqprx/+JJYK5pHicT/GQiBfzzhUVAIT6MwqJg8y4xV/zoGsUeuPuwtoiKSGE15g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ansi-escape-sequences": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-6.2.1.tgz", + "integrity": "sha512-0gK95MrLXv+Vy5h4eKGvSX1yXopBqSYBi3/w4hekUxs/hHakF6asH9Gg7UXbb7IH9weAlVIrUzVOITNBr8Imag==", + "dev": true, + "requires": { + "array-back": "^6.2.2" + }, + "dependencies": { + "array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-back": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz", + "integrity": "sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==", + "dev": true + }, + "ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "requires": { + "tslib": "^2.0.1" + } + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "basic-ftp": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz", + "integrity": "sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, + "cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "requires": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + } + }, + "cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true + }, + "cacheable-request": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.2.tgz", + "integrity": "sha512-KxjQZM3UIo7/J6W4sLpwFvu1GB3Whv8NtZ8ZrUL284eiQjiXeeqWTdhixNrp/NLZ/JNuFBo6BD4ZaO8ZJ5BN8Q==", + "dev": true, + "requires": { + "@types/http-cache-semantics": "^4.0.1", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.0", + "keyv": "^4.5.0", + "mimic-response": "^4.0.0", + "normalize-url": "^7.2.0", + "responselike": "^3.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + } + } + }, + "chromium-bidi": { + "version": "0.4.32", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.32.tgz", + "integrity": "sha512-RJnw0PW3sNdx1WclINVfVVx8JUH+tWTHZNpnEzlcM+Qgvf40dUH34U7gJq+cc/0LE+rbPxeT6ldqWrCbUf4jeg==", + "dev": true, + "requires": { + "mitt": "3.0.1", + "urlpattern-polyfill": "9.0.0" + } + }, + "clean-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz", + "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==", + "dev": true, + "requires": { + "escape-string-regexp": "5.0.0" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "co-body": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.0.0.tgz", + "integrity": "sha512-9ZIcixguuuKIptnY8yemEOuhb71L/lLf+Rl5JfJEUiDNJk0e02MBt7BPxR2GEh5mw8dPthQYR4jPI/BnS1MQgw==", + "dev": true, + "requires": { + "inflation": "^2.0.0", + "qs": "^6.5.2", + "raw-body": "^2.3.3", + "type-is": "^1.6.16" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "command-line-args": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.1.tgz", + "integrity": "sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==", + "dev": true, + "requires": { + "array-back": "^3.0.1", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "dependencies": { + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true + } + } + }, + "command-line-usage": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.0.tgz", + "integrity": "sha512-Ew1clU4pkUeo6AFVDFxCbnN7GIZfXl48HIOQeFQnkO3oOqvpI7wdqtLRwv9iOCZ/7A+z4csVZeiDdEcj8g6Wiw==", + "dev": true, + "requires": { + "array-back": "^4.0.0", + "chalk": "^2.4.2", + "table-layout": "^1.0.0", + "typical": "^5.2.0" + }, + "dependencies": { + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "cookies": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", + "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "dev": true, + "requires": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + } + } + }, + "copy-to": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz", + "integrity": "sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "requires": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + } + }, + "cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "requires": { + "node-fetch": "^2.6.12" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "csv-stringify": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.2.0.tgz", + "integrity": "sha512-dcUbQLRTTDcgQxgEU8V9IctkaCwHZjZfzUZ5ZB3RY8Y+pXtdtl5iVQHfGzANytFFkRKanYzBXrkfpNdGR7eviA==", + "dev": true + }, + "data-uri-to-buffer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz", + "integrity": "sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true + } + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true + }, + "degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "requires": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + } + }, + "del": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-7.0.0.tgz", + "integrity": "sha512-tQbV/4u5WVB8HMJr08pgw0b6nG4RGt/tj+7Numvq+zqcvUFeMaIWWOUFltiU+6go8BSO2/ogsB4EasDaj0y68Q==", + "dev": true, + "requires": { + "globby": "^13.1.2", + "graceful-fs": "^4.2.10", + "is-glob": "^4.0.3", + "is-path-cwd": "^3.0.0", + "is-path-inside": "^4.0.0", + "p-map": "^5.5.0", + "rimraf": "^3.0.2", + "slash": "^4.0.0" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "devtools-protocol": { + "version": "0.0.818844", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz", + "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==", + "dev": true, + "peer": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true + }, + "escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "requires": { + "array-back": "^3.0.1" + }, + "dependencies": { + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true + } + } + }, + "find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "requires": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + } + }, + "form-data-encoder": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.3.tgz", + "integrity": "sha512-KqU0nnPMgIJcCOFTNJFEA8epcseEaoox4XZffTgy8jlI6pL/5EFyR54NRG7CnCJN0biY7q52DO3MH6/sJ/TKlQ==", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "get-uri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.2.tgz", + "integrity": "sha512-5KLucCJobh8vBY1K07EFV4+cPZH3mrV9YeAruUseCQKHB58SGjjT2l9/eA9LD082IiuMjSlFJEcdJ27TXvbZNw==", + "dev": true, + "requires": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.0", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", + "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", + "dev": true, + "requires": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "got": { + "version": "12.5.2", + "resolved": "https://registry.npmjs.org/got/-/got-12.5.2.tgz", + "integrity": "sha512-guHGMSEcsA5m1oPRweXUJnug0vuvlkX9wx5hzOka+ZBrBUOJHU0Z1JcNu3QE5IPGnA5aXUsQHdWOD4eJg9/v3A==", + "dev": true, + "requires": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.1", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "http-assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", + "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", + "dev": true, + "requires": { + "deep-equal": "~1.0.1", + "http-errors": "~1.7.2" + }, + "dependencies": { + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + } + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "http-errors": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + } + } + }, + "http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "dependencies": { + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "http2-wrapper": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.1.11.tgz", + "integrity": "sha512-aNAk5JzLturWEUiuhAN73Jcbq96R7rTitAoXV54FYMatvihnpD2+6PUgU4ce3D/m5VDbw+F5CsyKSF176ptitQ==", + "dev": true, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true + }, + "inflation": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz", + "integrity": "sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-path-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-3.0.0.tgz", + "integrity": "sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==", + "dev": true + }, + "is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsonschema": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", + "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", + "dev": true + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dev": true, + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "jstat": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/jstat/-/jstat-1.9.4.tgz", + "integrity": "sha512-IiTPlI7pcrsq41EpDzrghlA1fhiC9GXxNqO4k5ogsjsM1XAWQ8zESH/bZsExLVgQsYpXE+7c11kEbbuxTLUpJQ==", + "dev": true + }, + "jszip": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz", + "integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "requires": { + "tsscmp": "1.0.6" + } + }, + "keyv": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.0.tgz", + "integrity": "sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "koa": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.13.0.tgz", + "integrity": "sha512-i/XJVOfPw7npbMv67+bOeXr3gPqOAw6uh5wFyNs3QvJ47tUx3M3V9rIE0//WytY42MKz4l/MXKyGkQ2LQTfLUQ==", + "dev": true, + "requires": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.8.0", + "debug": "~3.1.0", + "delegates": "^1.0.0", + "depd": "^1.1.2", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^1.2.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + } + }, + "koa-bodyparser": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/koa-bodyparser/-/koa-bodyparser-4.3.0.tgz", + "integrity": "sha512-uyV8G29KAGwZc4q/0WUAjH+Tsmuv9ImfBUF2oZVyZtaeo0husInagyn/JH85xMSxM0hEk/mbCII5ubLDuqW/Rw==", + "dev": true, + "requires": { + "co-body": "^6.0.0", + "copy-to": "^2.0.1" + } + }, + "koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "koa-convert": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", + "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "dev": true, + "requires": { + "co": "^4.6.0", + "koa-compose": "^3.0.0" + }, + "dependencies": { + "koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "dev": true, + "requires": { + "any-promise": "^1.1.0" + } + } + } + }, + "koa-mount": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/koa-mount/-/koa-mount-4.0.0.tgz", + "integrity": "sha512-rm71jaA/P+6HeCpoRhmCv8KVBIi0tfGuO/dMKicbQnQW/YJntJ6MnnspkodoA4QstMVEZArsCphmd0bJEtoMjQ==", + "dev": true, + "requires": { + "debug": "^4.0.1", + "koa-compose": "^4.1.0" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "koa-node-resolve": { + "version": "1.0.0-pre.9", + "resolved": "https://registry.npmjs.org/koa-node-resolve/-/koa-node-resolve-1.0.0-pre.9.tgz", + "integrity": "sha512-WKgqe5TGVD6zuR3NrKnmbb/NNHIbWOCezQVqqnyQLdtLLXWgiothlUQT23S5qQGE0Z623jp6jxpMjvAqyrcZFQ==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.4", + "@babel/parser": "^7.4.5", + "@babel/traverse": "^7.4.5", + "@types/babel__generator": "^7.6.1", + "@types/parse5": "^5.0.0", + "get-stream": "^5.1.0", + "parse5": "^5.1.0", + "resolve": "^1.11.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "koa-send": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz", + "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "resolve-path": "^1.4.0" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "koa-send": "^5.0.0" + } + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "locate-path": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.1.1.tgz", + "integrity": "sha512-vJXaRMJgRVD3+cUZs3Mncj2mxpt5mP0EmNOsxRSZRMlbqjvxzDEOIUWXGmavo0ZC9+tNZCBLQ66reA11nbpHZg==", + "dev": true, + "requires": { + "p-locate": "^6.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=", + "dev": true + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=", + "dev": true + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=", + "dev": true + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "dev": true + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", + "dev": true + }, + "lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "mri": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.5.tgz", + "integrity": "sha512-d2RKzMD4JNyHMbnbWnznPaa8vbdlq/4pNZ3IgdaGrVbBhebBsGUUE/6qorTMYNS6TwuH3ilfOlD2bf4Igh8CKg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + }, + "netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "normalize-url": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-7.2.0.tgz", + "integrity": "sha512-uhXOdZry0L6M2UIo9BTt7FdpBDiAGN/7oItedQwPKh8jh31ZlvC8U9Xl/EJ3aijDHaywXTW3QbZ6LuCocur1YA==", + "dev": true + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", + "dev": true + }, + "p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "requires": { + "p-limit": "^4.0.0" + }, + "dependencies": { + "p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + } + } + }, + "p-map": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz", + "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==", + "dev": true, + "requires": { + "aggregate-error": "^4.0.0" + } + }, + "pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "dev": true, + "requires": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "dependencies": { + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + } + } + }, + "pac-resolver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", + "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "dev": true, + "requires": { + "degenerator": "^5.0.0", + "ip": "^1.1.8", + "netmask": "^2.0.2" + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pkg-install": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-install/-/pkg-install-1.0.0.tgz", + "integrity": "sha512-UGI8bfhrDb1KN01RZ7Bq08GRQc8rmVjxQ2up0g4mUHPCYDTK1FzQ0PMmLOBCHg3yaIijZ2U3Fn9ofLa4N392Ug==", + "dev": true, + "requires": { + "@types/execa": "^0.9.0", + "@types/node": "^11.9.4", + "execa": "^1.0.0" + }, + "dependencies": { + "@types/node": { + "version": "11.15.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.15.27.tgz", + "integrity": "sha512-LbLwyGC/ukDV0EbHFP1OCfs2V5h3vUS8ZXJJjS2L5YYg8rNkJe6Tl/yv+L+g94sbHllyXUCfUCn5+sZLBegvyw==", + "dev": true + } + } + }, + "pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-4.0.0.tgz", + "integrity": "sha512-N4zdA4sfOe6yCv+ulPCmpnIBQ5I60xfhDr1otdBBhKte9QtEf3bhfrfkW7dTb+IQ0iEx4ZDzas0kc1o5rdWpYg==", + "dev": true, + "requires": { + "find-up": "^6.2.0" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "proxy-agent": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.1.tgz", + "integrity": "sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "dependencies": { + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + } + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "puppeteer": { + "version": "21.4.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-21.4.1.tgz", + "integrity": "sha512-opJqQeYMjAB3ICG8lCF3wtSs9k05dozmrEMrHgo3ZWbISiy8qbv/yAJz/6Io221qSh3yURfVf6Z7crrlzKZjLQ==", + "dev": true, + "requires": { + "@puppeteer/browsers": "1.8.0", + "cosmiconfig": "8.3.6", + "puppeteer-core": "21.4.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools-protocol": { + "version": "0.0.1191157", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1191157.tgz", + "integrity": "sha512-Fu2mUhX7zkzLHMJZk5wQTiHdl1eJrhK0GypUoSzogUt51MmYEv/46pCz4PtGGFlr0f2ZyYDzzx5CPtbEkuvcTA==", + "dev": true + }, + "puppeteer-core": { + "version": "21.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-21.4.1.tgz", + "integrity": "sha512-Lh0e+oGhUquxVOi1U701gTfFLFvw5gDBFh3CWpnfAvtItmyZKUce4R54VNfOJfi+KKnzhVPdB/lDrg65gdRIng==", + "dev": true, + "requires": { + "@puppeteer/browsers": "1.8.0", + "chromium-bidi": "0.4.32", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1191157", + "ws": "8.14.2" + } + }, + "ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "dev": true, + "requires": {} + } + } + }, + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, + "raw-body": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", + "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.3", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + } + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "reduce-flatten": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", + "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=", + "dev": true, + "requires": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "requires": { + "lowercase-keys": "^3.0.0" + } + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "requires": { + "mri": "^1.1.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "dev": true, + "requires": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "selenium-webdriver": { + "version": "4.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.8.tgz", + "integrity": "sha512-yPSaiWySZTEbxuuWQMDqdXh3H3N4Aiw/bSUjpkKMPWWCysfPqUncrq6FewBqdxWD1wQKzy5yWaQMGsgTY/0rCQ==", + "dev": true, + "requires": { + "jszip": "^3.5.0", + "rimraf": "^2.7.1", + "tmp": "^0.1.0", + "ws": "^7.3.1" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true + }, + "socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "dependencies": { + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + } + } + }, + "socks-proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", + "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "dependencies": { + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "streamx": { + "version": "2.15.2", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.2.tgz", + "integrity": "sha512-b62pAV/aeMjUoRN2C/9F0n+G8AfcJjNC0zw/ZmOHeFsIe4m4GzjVW9m6VHXVjk536NbdU9JRwKMJRfkc+zUFTg==", + "dev": true, + "requires": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "systeminformation": { + "version": "5.12.10", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.12.10.tgz", + "integrity": "sha512-rLShZFpz14aWqg3nNCS8zw74WicRV6c3R17Ob7oO37zSypZATT4M2Dgjr4nE6PYGlome9ObU0A2uqXiAlaBbuA==", + "dev": true + }, + "table": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", + "dev": true, + "requires": { + "ajv": "^7.0.2", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" + } + }, + "table-layout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.1.tgz", + "integrity": "sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==", + "dev": true, + "requires": { + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" + }, + "dependencies": { + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + } + } + }, + "tachometer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/tachometer/-/tachometer-0.7.0.tgz", + "integrity": "sha512-163DdzoNkjZlr/m3IpaPMYBOTUc54hzfGSUw7pv9ZoO3OkjDcoGqJGImcrfEDNsw3DD0J/FM5AWjEi2/0W4YZA==", + "dev": true, + "requires": { + "ansi-escape-sequences": "^6.0.1", + "command-line-args": "^5.0.2", + "command-line-usage": "^6.1.0", + "csv-stringify": "^6.2.0", + "fs-extra": "^10.0.0", + "get-stream": "^6.0.0", + "got": "^12.1.0", + "jsonschema": "^1.4.0", + "jsonwebtoken": "^8.5.1", + "jstat": "^1.9.2", + "koa": "^2.11.0", + "koa-bodyparser": "^4.2.1", + "koa-mount": "^4.0.0", + "koa-node-resolve": "^1.0.0-pre.8", + "koa-send": "^5.0.0", + "koa-static": "^5.0.0", + "pkg-install": "^1.0.0", + "pkg-up": "^4.0.0", + "progress": "^2.0.3", + "sanitize-filename": "^1.6.3", + "selenium-webdriver": "^4.0.0-alpha.8", + "semver": "^7.1.1", + "source-map-support": "^0.5.16", + "strip-ansi": "^7.0.1", + "systeminformation": "^5.3.3", + "table": "^6.0.7", + "ua-parser-js": "^1.0.2" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "dev": true, + "requires": { + "rimraf": "^2.6.3" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", + "dev": true, + "requires": { + "utf8-byte-length": "^1.0.1" + } + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true + }, + "ua-parser-js": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", + "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", + "dev": true + }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urlpattern-polyfill": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-9.0.0.tgz", + "integrity": "sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==", + "dev": true + }, + "utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrapjs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.0.tgz", + "integrity": "sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ==", + "dev": true, + "requires": { + "reduce-flatten": "^2.0.0", + "typical": "^5.0.0" + }, + "dependencies": { + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + } + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", + "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "ylru": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", + "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", + "dev": true + }, + "yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true + } + } +} diff --git a/benches/package.json b/benches/package.json new file mode 100644 index 0000000000..449b06c780 --- /dev/null +++ b/benches/package.json @@ -0,0 +1,32 @@ +{ + "name": "preact-benchmarks", + "private": true, + "version": "1.0.0", + "type": "module", + "description": "Benchmarks for Preact", + "scripts": { + "start": "node ./scripts config many_updates.html && tach --force-clean-npm-install --config dist/many_updates.config.json --manual", + "analyze": "node ./scripts analyze", + "bench": "node ./scripts bench", + "deopts": "node ./scripts deopts", + "help": "node ./scripts --help" + }, + "license": "MIT", + "dependencies": { + "afterframe": "^1.0.1" + }, + "devDependencies": { + "del": "^7.0.0", + "escalade": "^3.1.1", + "escape-string-regexp": "^5.0.0", + "globby": "^13.1.2", + "prompts": "^2.4.2", + "puppeteer": "^21.4.1", + "sade": "^1.8.1", + "strip-ansi": "^7.0.1", + "tachometer": "^0.7.0" + }, + "volta": { + "extends": "../package.json" + } +} diff --git a/benches/proxy-packages/preact-hooks-proxy/index.js b/benches/proxy-packages/preact-hooks-proxy/index.js new file mode 100644 index 0000000000..5c10304d69 --- /dev/null +++ b/benches/proxy-packages/preact-hooks-proxy/index.js @@ -0,0 +1,19 @@ +import { render, hydrate } from 'preact'; + +export * from 'preact/hooks'; +export * from 'preact'; + +/** + * @param {HTMLElement} rootDom + * @returns {{ render(vnode: JSX.Element): void; hydrate(vnode: JSX.Element): void; }} + */ +export function createRoot(rootDom) { + return { + render(vnode) { + render(vnode, rootDom); + }, + hydrate(vnode) { + hydrate(vnode, rootDom); + } + }; +} diff --git a/benches/proxy-packages/preact-hooks-proxy/package.json b/benches/proxy-packages/preact-hooks-proxy/package.json new file mode 100644 index 0000000000..781164e45a --- /dev/null +++ b/benches/proxy-packages/preact-hooks-proxy/package.json @@ -0,0 +1,10 @@ +{ + "name": "preact-hooks-proxy", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "index.js", + "dependencies": { + "preact": "file:../../../" + } +} diff --git a/benches/proxy-packages/preact-hooks-proxy/scripts.mjs b/benches/proxy-packages/preact-hooks-proxy/scripts.mjs new file mode 100644 index 0000000000..1870439718 --- /dev/null +++ b/benches/proxy-packages/preact-hooks-proxy/scripts.mjs @@ -0,0 +1,16 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; +import { + preinstall as localPreinstall, + postinstall as localPostInstall +} from '../preact-local-proxy/scripts.mjs'; + +// @ts-ignore +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const pkgRoot = (...args) => path.join(__dirname, ...args); + +export const preinstall = () => + localPreinstall(pkgRoot, `[preact-hooks preinstall] `); + +export const postinstall = () => + localPostInstall(pkgRoot, `[preact-hooks postinstall] `); diff --git a/benches/proxy-packages/preact-local-proxy/index.js b/benches/proxy-packages/preact-local-proxy/index.js new file mode 100644 index 0000000000..1b3d7b2e94 --- /dev/null +++ b/benches/proxy-packages/preact-local-proxy/index.js @@ -0,0 +1,18 @@ +import { render, hydrate } from 'preact'; + +export * from 'preact'; + +/** + * @param {HTMLElement} rootDom + * @returns {{ render(vnode: JSX.Element): void; hydrate(vnode: JSX.Element): void; }} + */ +export function createRoot(rootDom) { + return { + render(vnode) { + render(vnode, rootDom); + }, + hydrate(vnode) { + hydrate(vnode, rootDom); + } + }; +} diff --git a/benches/proxy-packages/preact-local-proxy/package.json b/benches/proxy-packages/preact-local-proxy/package.json new file mode 100644 index 0000000000..f4f592cdd0 --- /dev/null +++ b/benches/proxy-packages/preact-local-proxy/package.json @@ -0,0 +1,10 @@ +{ + "name": "preact-local-proxy", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "index.js", + "dependencies": { + "preact": "file:../../../" + } +} diff --git a/benches/proxy-packages/preact-local-proxy/scripts.mjs b/benches/proxy-packages/preact-local-proxy/scripts.mjs new file mode 100644 index 0000000000..7db6b72db1 --- /dev/null +++ b/benches/proxy-packages/preact-local-proxy/scripts.mjs @@ -0,0 +1,61 @@ +import { existsSync } from 'fs'; +import { readFile, writeFile } from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { repoRoot } from '../../scripts/utils.js'; + +// @ts-ignore +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +/** + * Support installing a local build from either a tarball (preact-local.tgz) or + * from the root package.json. If preact-local.tgz exists, update + * preact-local-proxy's package.json to point to that tarball. If it does not, + * then leave preact-local-proxy package.json unmodified, which by default + * points to the repo root package.json. + * + * This feature is necessary to support our CI benchmarks. To avoid rebuilding + * the repo in every benchmark, we copy the prebuilt .tgz file to each job and + * use that to avoid rebuilding. See preactjs/preact#3777 for the motivation. + */ +export async function preinstall( + pkgRoot = (...args) => path.join(__dirname, ...args), + prefix = `[preact-local preinstall] `, + preactLocalTgz = repoRoot('preact-local.tgz') +) { + console.log(`${prefix}Searching for preact-local.tgz at ${preactLocalTgz}`); + if (existsSync(preactLocalTgz)) { + console.log( + `${prefix}preact-local.tgz found! Updating preact-local-proxy/package.json to install that tarball` + ); + + const pkgJsonPath = pkgRoot('package.json'); + const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8')); + pkgJson.dependencies.preact = + 'file:' + path.relative(pkgRoot(), preactLocalTgz); + + await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2), 'utf8'); + } else { + console.log( + `${prefix}preact-local.tgz not found. Leaving preact-local-proxy/package.json unmodified` + ); + } +} + +export async function postinstall( + pkgRoot = (...args) => path.join(__dirname, ...args), + prefix = `[preact-local postinstall] ` +) { + const pkgJsonPath = pkgRoot('package.json'); + const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8')); + + const localBuild = 'file:../../../'; + if (pkgJson.dependencies.preact !== localBuild) { + console.log( + `${prefix}Resetting preact dep back to local build (${localBuild}) from "${pkgJson.dependencies.preact}" now that bench install is done.` + ); + pkgJson.dependencies.preact = localBuild; + } + + await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2), 'utf8'); +} diff --git a/benches/proxy-packages/preact-main-proxy/index.js b/benches/proxy-packages/preact-main-proxy/index.js new file mode 100644 index 0000000000..1b3d7b2e94 --- /dev/null +++ b/benches/proxy-packages/preact-main-proxy/index.js @@ -0,0 +1,18 @@ +import { render, hydrate } from 'preact'; + +export * from 'preact'; + +/** + * @param {HTMLElement} rootDom + * @returns {{ render(vnode: JSX.Element): void; hydrate(vnode: JSX.Element): void; }} + */ +export function createRoot(rootDom) { + return { + render(vnode) { + render(vnode, rootDom); + }, + hydrate(vnode) { + hydrate(vnode, rootDom); + } + }; +} diff --git a/benches/proxy-packages/preact-main-proxy/package.json b/benches/proxy-packages/preact-main-proxy/package.json new file mode 100644 index 0000000000..f34ced6c6a --- /dev/null +++ b/benches/proxy-packages/preact-main-proxy/package.json @@ -0,0 +1,10 @@ +{ + "name": "preact-proxy", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "index.js", + "dependencies": { + "preact": "file:../../../preact-main.tgz" + } +} diff --git a/benches/proxy-packages/preact-v8-proxy/index.js b/benches/proxy-packages/preact-v8-proxy/index.js new file mode 100644 index 0000000000..8c7533066c --- /dev/null +++ b/benches/proxy-packages/preact-v8-proxy/index.js @@ -0,0 +1,23 @@ +import { render } from 'preact'; + +export * from 'preact'; + +/** + * @param {HTMLElement} rootDom + * @returns {{ render(vnode: JSX.Element): void; hydrate(vnode: JSX.Element): void; }} + */ +export function createRoot(rootDom) { + let result; + return { + render(vnode) { + if (result) { + result = render(vnode, rootDom, result); + } else { + result = render(vnode, rootDom); + } + }, + hydrate(vnode) { + render(vnode, rootDom, rootDom.firstElementChild); + } + }; +} diff --git a/benches/proxy-packages/preact-v8-proxy/package.json b/benches/proxy-packages/preact-v8-proxy/package.json new file mode 100644 index 0000000000..7d5c894b52 --- /dev/null +++ b/benches/proxy-packages/preact-v8-proxy/package.json @@ -0,0 +1,10 @@ +{ + "name": "preact-v8-proxy", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "index.js", + "dependencies": { + "preact": "^8.5.3" + } +} diff --git a/benches/scripts/analyze.js b/benches/scripts/analyze.js new file mode 100644 index 0000000000..1606508962 --- /dev/null +++ b/benches/scripts/analyze.js @@ -0,0 +1,363 @@ +import { existsSync } from 'fs'; +import { readFile, readdir } from 'fs/promises'; +import prompts from 'prompts'; +import { baseTraceLogDir, frameworks } from './config.js'; + +import { summaryStats, computeDifferences } from 'tachometer/lib/stats.js'; +import { + automaticResultTable, + verticalTermResultTable +} from 'tachometer/lib/format.js'; + +/** + * @typedef {import('./tracing').TraceEvent} TraceEvent + * @typedef {import('tachometer/lib/stats').SummaryStats} SummaryStats + * @typedef {import('tachometer/lib/stats').ResultStats} ResultStats + * @typedef {import('tachometer/lib/stats').ResultStatsWithDifferences} ResultStatsWithDifferences + */ + +const toTrack = new Set([ + // 'V8.CompileCode', // Might be tachometer code?? But maybe not? + 'V8.MarkCandidatesForOptimization', + 'V8.OptimizeCode', + 'V8.OptimizeConcurrentPrepare', + 'V8.OptimizeNonConcurrent', + // 'V8.OptimizeBackground', // Runs on background thread + 'V8.InstallOptimizedFunctions', + 'V8.DeoptimizeCode', + 'MinorGC', + 'V8.GCDeoptMarkedAllocationSites' +]); + +/** + * @template T + * @param {Map} grouping + * @param {Map} results + */ +function addToGrouping(grouping, results) { + for (let [group, data] of results.entries()) { + if (grouping.has(group)) { + if (Array.isArray(data)) { + grouping.get(group).push(...data); + } else { + grouping.get(group).push(data); + } + } else if (Array.isArray(data)) { + grouping.set(group, data); + } else { + grouping.set(group, [data]); + } + } +} + +/** + * @template K + * @template V + * @param {Map} map + * @param {K} key + * @param {...V} values + */ +function addToMapArray(map, key, ...values) { + if (map.has(key)) { + map.get(key).push(...values); + } else { + map.set(key, values); + } +} + +/** + * @template K + * @template V + * @param {Map} map + * @param {K} key + * @param {number} index + * @param {V} value + */ +function setInMapArray(map, key, index, value) { + if (map.has(key)) { + map.get(key)[index] = value; + } else { + map.set(key, [value]); + } +} + +/** + * @param {ResultStats[]} results + */ +function logDifferences(key, results) { + let withDifferences = computeDifferences(results); + console.log(); + let { unfixed } = automaticResultTable(withDifferences); + // console.log(horizontalTermResultTable(fixed)); + console.log(key); + console.log(verticalTermResultTable(unfixed)); +} + +/** + * @param {string} version + * @param {string[]} logPaths + * @param {(logs: TraceEvent[], logFilePath: string) => number | null} [getThreadId] + * @param {(log: TraceEvent) => boolean} [trackEventsIn] + * @returns {Promise>} + */ +async function getStatsFromLogs(version, logPaths, getThreadId, trackEventsIn) { + /** @type {Map} Sums for each function for each file */ + const data = new Map(); + for (let logPath of logPaths) { + /** @type {TraceEvent[]} */ + const logs = JSON.parse(await readFile(logPath, 'utf8')); + + let tid = getThreadId ? getThreadId(logs, logPath) : null; + if (tid == null) { + console.warn(`Could not find threadId for ${logPath}. Skipping...`); + continue; + } + + /** @type {Array<{ id: string; start: number; end: number; }>} Determine what durations to track events under */ + const parentLogs = []; + for (let log of logs) { + if (trackEventsIn && trackEventsIn(log)) { + if (log.ph == 'X') { + parentLogs.push({ + id: log.name, + start: log.ts, + end: log.ts + log.dur + }); + } else if (log.ph == 'b') { + parentLogs.push({ + id: log.name, + start: log.ts, + end: log.ts + }); + } else if (log.ph == 'e') { + parentLogs.find(l => l.id == log.name).end = log.ts; + } else { + throw new Error(`Unsupported parent log type: ${log.ph}`); + } + } + } + + /** @type {Map} */ + const durationBeginEvents = new Map(); + + /** @type {Map} Sum of time spent in each function for this log file */ + const sumsForFile = new Map(); + for (let log of logs) { + if (tid != null && log.tid !== tid) { + // if (toTrack.has(log.name)) { + // console.log( + // `Skipping ${log.name} on tid ${log.tid} (expected ${tid}) in ${logPath}` + // ); + // } + + continue; + } + + if (log.ph == 'X') { + // Track duration event + if (toTrack.has(log.name)) { + let key = `Sum of ${log.name} time`; + let sum = sumsForFile.get(key)?.[0] ?? 0; + // sumsForFile.set(log.name, sum + log.dur / 1000); + setInMapArray(sumsForFile, key, 0, sum + log.dur / 1000); + + key = `Count of ${log.name}`; + sum = sumsForFile.get(key)?.[0] ?? 0; + // sumsForFile.set(key, sum + 1); + setInMapArray(sumsForFile, key, 0, sum + 1); + + key = `Sum of V8 runtime`; + sum = sumsForFile.get(key)?.[0] ?? 0; + // sumsForFile.set(key, sum + log.dur / 1000); + setInMapArray(sumsForFile, key, 0, sum + log.dur / 1000); + + for (let parentLog of parentLogs) { + if ( + parentLog.start <= log.ts && + log.ts + log.dur <= parentLog.end + ) { + key = `In ${parentLog.id}, Sum of V8 runtime`; + sum = sumsForFile.get(key)?.[0] ?? 0; + setInMapArray(sumsForFile, key, 0, sum + log.dur / 1000); + } + } + } + + if (log.name == 'MinorGC' || log.name == 'MajorGC') { + let key = `${log.name} usedHeapSizeBefore`; + addToMapArray(sumsForFile, key, log.args.usedHeapSizeBefore / 1e6); + + key = `${log.name} usedHeapSizeAfter`; + addToMapArray(sumsForFile, key, log.args.usedHeapSizeAfter / 1e6); + } + } else if ( + (log.ph == 'b' || log.ph == 'e') && + log.cat == 'blink.user_timing' && + log.scope == 'blink.user_timing' + ) { + // TODO: Doesn't handle nested events of same name. Oh well. + if (log.ph == 'b') { + durationBeginEvents.set(log.name, log); + } else { + const beginEvent = durationBeginEvents.get(log.name); + const endEvent = log; + durationBeginEvents.delete(log.name); + + let key = beginEvent.name; + let duration = (endEvent.ts - beginEvent.ts) / 1000; + addToMapArray(sumsForFile, key, duration); + + if (key.startsWith('run-') && key !== 'run-warmup-0') { + // Skip run-warmup-0 since it doesn't do unmounting + addToMapArray(sumsForFile, 'average run duration', duration); + } + } + } + } + + addToGrouping(data, sumsForFile); + } + + const stats = new Map(); + for (let [key, sums] of data) { + stats.set(key, { + result: { + name: '02_replace1k', + version, + measurement: { + name: key, + mode: 'expression', + expression: key, + unit: key.startsWith('Count') + ? '' + : key.includes('usedHeapSize') + ? 'MB' + : null + }, + browser: { + name: 'chrome' + }, + millis: sums + }, + stats: summaryStats(sums) + }); + } + + return stats; +} + +/** + * @param {import('./tracing').TraceEvent[]} logs + * @param {string} logFilePath + * @returns {number | null} + */ +function getDurationThread(logs, logFilePath) { + return logs.find(isDurationLog)?.tid ?? null; +} + +/** + * @param {TraceEvent} log + */ +function isDurationLog(log) { + return ( + (log.ph == 'b' || log.ph == 'e') && + log.cat == 'blink.user_timing' && + log.scope == 'blink.user_timing' && + // Tachometer may kill the tab after seeing the duration measure before + // the tab can log it to the trace file + (log.name == 'run-final' || log.name == 'duration') + ); +} + +/** @param {string} requestedBench */ +export async function analyze(requestedBench) { + // const frameworkNames = await readdir(p('logs')); + const frameworkNames = frameworks.map(f => f.label); + const listAtEnd = [ + 'average run duration', + 'Sum of V8 runtime', + 'In run-final, Sum of V8 runtime', + 'In duration, Sum of V8 runtime', + 'duration' + ]; + + if (!existsSync(baseTraceLogDir())) { + console.log( + `Could not find log directory: "${baseTraceLogDir()}". Did you run the benchmarks?` + ); + return; + } + + const benchmarkNames = await readdir(baseTraceLogDir()); + let selectedBench; + if (benchmarkNames.length == 0) { + console.log(`No benchmarks or results found in "${baseTraceLogDir()}".`); + return; + } else if (requestedBench) { + if (benchmarkNames.includes(requestedBench)) { + selectedBench = requestedBench; + } else { + console.log( + `Could not find benchmark "${requestedBench}". Available benchmarks:` + ); + console.log(benchmarkNames); + return; + } + } else if (benchmarkNames.length == 1) { + selectedBench = benchmarkNames[0]; + } else { + selectedBench = ( + await prompts({ + type: 'select', + name: 'value', + message: "Which benchmark's results would you like to analyze?", + choices: benchmarkNames.map(name => ({ + title: name, + value: name + })) + }) + ).value; + } + + /** @type {Map} */ + const resultStatsMap = new Map(); + for (let framework of frameworkNames) { + const logDir = baseTraceLogDir(selectedBench, framework); + + let logFilePaths; + try { + logFilePaths = (await readdir(logDir)).map(fn => + baseTraceLogDir(selectedBench, framework, fn) + ); + } catch (e) { + // If directory doesn't exist or we fail to read it, just skip + continue; + } + + const resultStats = await getStatsFromLogs( + framework, + logFilePaths, + getDurationThread, + isDurationLog + ); + addToGrouping(resultStatsMap, resultStats); + + // console.log(`${framework}:`); + // console.log(resultStats); + } + + // Compute differences and print table + for (let [key, results] of resultStatsMap.entries()) { + if (listAtEnd.includes(key)) { + continue; + } + + logDifferences(key, results); + } + + for (let key of listAtEnd) { + if (resultStatsMap.has(key)) { + logDifferences(key, resultStatsMap.get(key)); + } + } +} diff --git a/benches/scripts/bench.js b/benches/scripts/bench.js new file mode 100644 index 0000000000..a5513280e0 --- /dev/null +++ b/benches/scripts/bench.js @@ -0,0 +1,70 @@ +import { spawnSync } from 'child_process'; +import { mkdir } from 'fs/promises'; +import { + globSrc, + benchesRoot, + allBenches, + resultsPath, + IS_CI +} from './utils.js'; +import { generateConfig } from './config.js'; + +export const defaultBenchOptions = { + browser: 'chrome-headless', + // Tachometer default is 50, but locally let's only do 10 + 'sample-size': !IS_CI ? 10 : 50, + // Tachometer default is 10% but let's do 5% to save some GitHub action + // minutes by reducing the likelihood of needing auto-sampling. See + // https://github.com/Polymer/tachometer#auto-sampling + horizon: '5%', + // Tachometer default is 3 minutes, but let's shrink it to 1 here to save some + // GitHub Action minutes + timeout: 1, + 'window-size': '1024,768', + framework: IS_CI ? ['preact-main', 'preact-local', 'preact-hooks'] : null, + trace: false +}; + +/** + * @param {string} bench1 + * @param {{ _: string[]; } & TachometerOptions} opts + */ +export async function runBenches(bench1 = 'all', opts) { + const globs = bench1 === 'all' ? allBenches : [bench1].concat(opts._); + const benchesToRun = await globSrc(globs); + + if (benchesToRun.length == 0) { + console.log('No benchmarks found matching patterns:', globs); + } else { + console.log('Running benchmarks:', benchesToRun.join(', ')); + console.log(); + } + + const configFileTasks = benchesToRun.map(async (benchPath, i) => { + return generateConfig(benchesRoot('src', benchPath), { + ...opts, + prepare: i === 0 // Only run prepare script for first config + }); + }); + + await mkdir(resultsPath(), { recursive: true }); + + const configFiles = await Promise.all(configFileTasks); + for (const { name, configPath } of configFiles) { + const args = [ + benchesRoot('node_modules/tachometer/bin/tach.js'), + '--force-clean-npm-install', + '--config', + configPath, + '--json-file', + benchesRoot('results', name + '.json') + ]; + + console.log('\n$', process.execPath, ...args); + + spawnSync(process.execPath, args, { + cwd: benchesRoot(), + stdio: 'inherit' + }); + } +} diff --git a/benches/scripts/config.js b/benches/scripts/config.js new file mode 100644 index 0000000000..cbc2809df0 --- /dev/null +++ b/benches/scripts/config.js @@ -0,0 +1,328 @@ +import * as path from 'path'; +import { deleteAsync } from 'del'; +import { writeFile, stat, mkdir } from 'fs/promises'; +import { repoRoot, benchesRoot, toUrl } from './utils.js'; +import { defaultBenchOptions } from './bench.js'; +import { prepare } from './prepare.js'; + +const measureName = 'duration'; // Must match measureName in '../src/util.js' +const warnings = new Set([]); +const TACH_SCHEMA = + 'https://raw.githubusercontent.com/Polymer/tachometer/master/config.schema.json'; + +const configDir = (...args) => benchesRoot('dist', ...args); + +export const baseTraceLogDir = (...args) => + path.join(benchesRoot('logs'), ...args); + +/** + * @param {ConfigFileBenchmark["packageVersions"]["dependencies"]["framework"]} framework + * @returns {Promise} + */ +async function validateFileDep(framework) { + try { + if (typeof framework === 'string') { + await stat(framework.replace(/^file:/, '')); + return true; + } + + return false; + } catch (e) { + // console.log('Stat error:', e); + return false; + } +} + +/** + * @typedef {ConfigFileBenchmark["packageVersions"]} ConfigFilePackageVersion + * @typedef {ConfigFilePackageVersion & { isValid(): Promise; }} BenchConfig + * @type {BenchConfig[]} + */ +export const frameworks = [ + { + label: 'preact-v8', + dependencies: { + framework: 'file:' + repoRoot('benches/proxy-packages/preact-v8-proxy') + }, + isValid() { + return validateFileDep(this.dependencies.framework); + } + }, + { + label: 'preact-main', + dependencies: { + framework: 'file:' + repoRoot('benches/proxy-packages/preact-main-proxy') + }, + async isValid() { + try { + await stat(repoRoot('preact-main.tgz')); + return validateFileDep(this.dependencies.framework); + } catch (e) { + return false; + } + } + }, + { + label: 'preact-local', + dependencies: { + framework: 'file:' + repoRoot('benches/proxy-packages/preact-local-proxy') + }, + isValid() { + return validateFileDep(this.dependencies.framework); + } + }, + { + label: 'preact-hooks', + dependencies: { + framework: 'file:' + repoRoot('benches/proxy-packages/preact-hooks-proxy') + }, + isValid() { + return validateFileDep(this.dependencies.framework); + } + } +]; + +/** + * @param {string} benchPath + * @returns {Pick} + */ +function getBaseBenchmarkConfig(benchPath) { + let name = path.basename(benchPath).replace('.html', ''); + let url = path.posix.relative(toUrl(configDir()), toUrl(benchPath)); + + /** @type {ConfigFileBenchmark["measurement"]} */ + let measurement; + if (name == '02_replace1k') { + // MUST BE KEPT IN SYNC WITH WARMUP COUNT IN 02_replace1k.html + const WARMUP_COUNT = 5; + + // For 02_replace1k, collect additional measurements focusing on the JS + // clock time for each warmup and the final duration. + measurement = [ + { + name: 'duration', + mode: 'performance', + entryName: measureName + }, + { + name: 'usedJSHeapSize', + mode: 'expression', + expression: 'window.usedJSHeapSize' + } + ]; + + for (let i = 0; i < WARMUP_COUNT; i++) { + const entryName = `run-warmup-${i}`; + measurement.push({ + name: entryName, + mode: 'performance', + entryName + }); + } + + measurement.push({ + name: 'run-final', + mode: 'performance', + entryName: 'run-final' + }); + } else { + // Default measurements + measurement = [ + { + name: 'duration', + mode: 'performance', + entryName: measureName + }, + { + name: 'usedJSHeapSize', + mode: 'expression', + expression: 'window.usedJSHeapSize' + } + ]; + } + + return { name, url, measurement }; +} + +export async function generateSingleConfig(benchFile, opts) { + const benchPath = await benchesRoot('src', benchFile); + const results = await stat(benchPath); + if (!results.isFile) { + throw new Error(`Given path is not a file: ${benchPath}`); + } + + await generateConfig(benchPath, { ...defaultBenchOptions, ...opts }); +} + +/** + * @typedef {import('tachometer/lib/configfile').ConfigFile} ConfigFile Expected + * format of a top-level tachometer JSON config file. + * @typedef {ConfigFile["benchmarks"][0]} ConfigFileBenchmark + * @typedef {{ name: string; configPath: string; config: ConfigFile; }} ConfigData + * @param {string} benchPath + * @param {TachometerOptions & { prepare?: boolean }} options + * @returns {Promise} + */ +export async function generateConfig(benchPath, options) { + /** @type {ConfigFileBenchmark["expand"]} */ + let expand; + /** @type {BrowserConfigs} */ + let browser; + + const baseBenchConfig = getBaseBenchmarkConfig(benchPath); + + // See https://www.npmjs.com/package/tachometer#browsers + // and https://www.npmjs.com/package/tachometer#config-file + if (Array.isArray(options.browser)) { + expand = options.browser.map(browserOpt => ({ + browser: parseBrowserOption(browserOpt) + })); + } else { + browser = parseBrowserOption(options.browser); + } + + if (browser.name == 'chrome' && options.trace) { + const traceLogDir = baseTraceLogDir(baseBenchConfig.name); + await deleteAsync('**/*', { cwd: traceLogDir }); + await mkdir(traceLogDir, { recursive: true }); + + browser.trace = { + logDir: traceLogDir + }; + } + + /** @type {BenchConfig[]} */ + let frameworksToRun; + if (!options.framework) { + frameworksToRun = frameworks; + } else if (typeof options.framework === 'string') { + const match = frameworks.find(f => f.label == options.framework); + frameworksToRun = match ? [match] : []; + } else if (Array.isArray(options.framework)) { + frameworksToRun = frameworks.filter(f => + options.framework.includes(f.label) + ); + } else { + throw new Error(`Unrecognized framework option: ${options.framework}`); + } + + if (frameworksToRun.length == 0) { + console.error( + `Framework options did not match any configured frameworks:\n` + + `\tProvided option: ${options.framework}\n` + + `\tAvailable frameworks: [${frameworks + .map(f => JSON.stringify(f.label)) + .join(', ')}]\n` + ); + + throw new Error( + `Framework option did not match any configured frameworks: ${options.framework}` + ); + } + + /** @type {ConfigFile["benchmarks"]} */ + const benchmarks = []; + for (let framework of frameworksToRun) { + let frameworkPath = framework.dependencies.framework; + if (typeof frameworkPath !== 'string') { + throw new Error( + 'Only string/npm dependencies are supported at this time' + ); + } + + if (!(await framework.isValid())) { + const warnMsg = `Could not locate path for ${framework.label}: ${framework.dependencies.framework}. \nSkipping...`; + if (!warnings.has(warnMsg)) { + console.warn(warnMsg); + warnings.add(warnMsg); + } + + continue; + } + + benchmarks.push({ + ...baseBenchConfig, + packageVersions: framework, + browser, + expand + }); + } + + if (options.prepare !== false) { + await prepare(benchmarks.map(b => b.packageVersions.label)); + } + + /** @type {ConfigFile} */ + const config = { + $schema: TACH_SCHEMA, + root: path.relative(configDir(), benchesRoot()), + sampleSize: options['sample-size'], + timeout: options.timeout, + autoSampleConditions: options.horizon.split(','), + benchmarks + }; + + if (config.benchmarks.length == 0) { + if (options.framework) { + const configuredFrameworks = frameworks.map(f => f.label).join(', '); + throw new Error( + `No benchmarks created. Does the specified framework match one of the configured frameworks? ${configuredFrameworks}` + ); + } else { + throw new Error( + `Unknown failure: no benchmarks created. frameworksToRun: ${frameworksToRun}` + ); + } + } + + const configPath = await writeConfig(baseBenchConfig.name, config); + + return { name: baseBenchConfig.name, configPath, config }; +} + +async function writeConfig(name, config) { + const configPath = configDir(name + '.config.json'); + await mkdir(path.dirname(configPath), { recursive: true }); + await writeFile(configPath, JSON.stringify(config, null, 2), 'utf8'); + + return configPath; +} + +/** + * @typedef {Exclude} BrowserConfigs + * @param {string} str + * @returns {BrowserConfigs} + */ +function parseBrowserOption(str) { + // Source: https://github.com/Polymer/tachometer/blob/d4d5116acb2d7df18035ddc36f0a3a1730841a23/src/browser.ts#L100 + let remoteUrl; + const at = str.indexOf('@'); + if (at !== -1) { + remoteUrl = str.substring(at + 1); + str = str.substring(0, at); + } + const headless = str.endsWith('-headless'); + if (headless === true) { + str = str.replace(/-headless$/, ''); + } + + /** @type {import('tachometer/lib/browser').BrowserName} */ + // @ts-ignore + const name = str; + + /** @type {BrowserConfigs} */ + const config = { name, headless }; + if (remoteUrl !== undefined) { + config.remoteUrl = remoteUrl; + } + + // Custom browser options + if (config.name == 'chrome') { + config.addArguments = [ + '--js-flags=--expose-gc', + '--enable-precise-memory-info' + ]; + } + + return config; +} diff --git a/benches/scripts/deopts.js b/benches/scripts/deopts.js new file mode 100644 index 0000000000..838e4459b2 --- /dev/null +++ b/benches/scripts/deopts.js @@ -0,0 +1,242 @@ +/* eslint-disable no-console */ + +import { mkdir } from 'fs/promises'; +import path from 'path'; +import { spawn } from 'child_process'; +import escapeRe from 'escape-string-regexp'; +import puppeteer from 'puppeteer'; +import stripAnsi from 'strip-ansi'; +import { + globSrc, + benchesRoot, + getPkgBinPath, + resultsPath, + IS_CI +} from './utils.js'; +import { generateConfig } from './config.js'; +import { defaultBenchOptions } from './bench.js'; + +export const defaultDeoptsOptions = { + framework: 'preact-local', + timeout: 5, + open: !IS_CI +}; + +const getLogFilePath = (benchmark, framework) => + resultsPath('deopts', `${benchmark}-${framework}-v8.log`); + +/** + * @param {string} pkgName + * @param {string[]} args + * @param {"pipe" | "inherit"} [stdio] + * @returns {Promise} + */ +async function runPackage(pkgName, args, stdio) { + const binPath = await getPkgBinPath(pkgName); + args.unshift(binPath); + + return spawn(process.execPath, args, { stdio }); +} + +/** + * @param {import('child_process').ChildProcess} childProcess + */ +async function onExit(childProcess) { + return new Promise((resolve, reject) => { + childProcess.once('exit', (code, signal) => { + if (code === 0 || signal == 'SIGINT') { + resolve(); + } else { + reject(new Error('Exit with error code: ' + code)); + } + }); + + childProcess.once('error', err => { + reject(err); + }); + }); +} + +/** + * @typedef {{ benchName: string; framework: string; url: string; }} TachURL + * @param {import('child_process').ChildProcess} tachProcess + * @param {import('./config').ConfigData} tachConfig + * @param {number} timeoutMs + * @returns {Promise} + */ +async function getTachometerURLs(tachProcess, tachConfig, timeoutMs = 60e3) { + return new Promise((resolve, reject) => { + let timeout; + if (timeoutMs > 0) { + timeout = setTimeout(() => { + reject( + new Error( + 'Timed out waiting for Tachometer to get set up. Did it output a URL?' + ) + ); + }, timeoutMs); + } + + // Look for lines like: + // many_updates [@preact] + // http://127.0.0.1:56536/src/many_updates.html + const benchesToSearch = tachConfig.config.benchmarks.map(bench => ({ + benchName: bench.name, + framework: bench.packageVersions.label, + regex: new RegExp( + escapeRe(`${bench.name} [@${bench.packageVersions.label}]`) + + `\\s+(http:\\/\\/.*)`, + 'im' + ), + url: null + })); + + /** @type {TachURL[]} */ + const results = []; + let output = ''; + tachProcess.stdout.on('data', function onStdOutChunk(chunk) { + output += stripAnsi(chunk.toString('utf8')); + + for (let bench of benchesToSearch) { + if (bench.url) { + continue; + } + + let match = output.match(bench.regex); + if (match) { + bench.url = match[1]; + results.push(bench); + } + } + + if (results.length == benchesToSearch.length) { + // All URLs found, removeEventListener + tachProcess.off('data', onStdOutChunk); + + clearTimeout(timeout); + resolve(results); + } + }); + }); +} + +/** @type {(ms: number) => Promise} */ +const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); + +/** + * @param {TachURL} tachURL + * @param {DeoptOptions} options + */ +async function runPuppeteer(tachURL, options) { + const logFilePath = getLogFilePath(tachURL.benchName, tachURL.framework); + await mkdir(path.dirname(logFilePath), { recursive: true }); + + const browser = await puppeteer.launch({ + headless: false, + ignoreDefaultArgs: ['about:blank'], + args: [ + '--disable-extensions', + '--no-sandbox', + '--js-flags=' + + [ + '--prof', + '--log-deopt', + '--log-ic', + '--log-maps', + '--log-map-details', + '--log-internal-timer-events', + '--log-code', + '--log-source-code', + '--detailed-line-info', + '--no-logfile-per-isolate', + `--logfile=${logFilePath}` + ].join(','), + tachURL.url + ] + }); + + await browser.pages(); + + console.log(`Loading ${tachURL.url} in puppeteer...`); + await delay(1000); + await browser.close(); + + console.log('Waiting for browser to exit...'); + await delay(1000); +} + +/** + * @param {string} benchGlob + * @param {DeoptOptions} options + */ +export async function runDeopts(benchGlob, options) { + // TODO: + // * Handle multiple benchmarks + + const frameworks = options.framework; + if (!benchGlob) { + benchGlob = 'many_updates.html'; + } + + const benchesToRun = await globSrc(benchGlob); + if (benchesToRun.length > 1) { + console.error('Matched multiple benchmarks. Only running the first one.'); + } + + const benchPath = benchesRoot('src', benchesToRun[0]); + const tachConfig = await generateConfig(benchPath, { + ...defaultBenchOptions, + ...defaultDeoptsOptions, + framework: frameworks + }); + + console.log('Benchmarks running:', benchPath); + console.log('Frameworks running:', frameworks); + + /** @type {Promise} */ + let onTachExit; + /** @type {import('child_process').ChildProcess} */ + let tachProcess; + try { + // Run tachometer in manual mode with generated config + const tachArgs = ['--config', tachConfig.configPath, '--manual']; + tachProcess = await runPackage('tachometer', tachArgs); + tachProcess.stdout.pipe(process.stdout); + tachProcess.stderr.pipe(process.stderr); + onTachExit = onExit(tachProcess); + + // Parse URL from tachometer stdout + const tachURLs = await getTachometerURLs(tachProcess, tachConfig); + + // Run puppeteer for each tachometer URL + console.log(); + for (let tachURL of tachURLs) { + await runPuppeteer(tachURL, options); + } + + console.log( + `\nOpen the following files in VSCode's DeoptExplorer extension to view results:` + ); + console.log( + tachURLs + .map(tachURL => getLogFilePath(tachURL.benchName, tachURL.framework)) + .map(logFilePath => path.relative(benchesRoot(), logFilePath)) + ); + } finally { + if (tachProcess) { + tachProcess.kill('SIGINT'); + + // Log a message is Tachometer takes a while to close + let logMsg = () => console.log('Waiting for Tachometer to exit...'); + let t = setTimeout(logMsg, 2e3); + + try { + await onTachExit; + } catch (error) { + console.error('Error waiting for Tachometer to exit:', error); + } finally { + clearTimeout(t); + } + } + } +} diff --git a/benches/scripts/global.d.ts b/benches/scripts/global.d.ts new file mode 100644 index 0000000000..58b0453931 --- /dev/null +++ b/benches/scripts/global.d.ts @@ -0,0 +1,13 @@ +interface TachometerOptions { + browser: string | string[]; + framework: string | string[]; + 'window-size': string; + 'sample-size': number; + horizon: string; + timeout: number; + trace: boolean; +} + +interface DeoptOptions { + framework: string; +} diff --git a/benches/scripts/index.js b/benches/scripts/index.js new file mode 100644 index 0000000000..619557f5d0 --- /dev/null +++ b/benches/scripts/index.js @@ -0,0 +1,105 @@ +import sade from 'sade'; +import { generateSingleConfig } from './config.js'; +import { defaultDeoptsOptions, runDeopts } from './deopts.js'; +import { defaultBenchOptions, runBenches } from './bench.js'; +import { analyze } from './analyze.js'; + +const prog = sade('./scripts'); + +// Tests: +// - npm start +prog + .command('config [bench]') + .describe('Generate the config for the given benchmark HTML file.') + .option( + '--trace', + 'Enable perf tracing for browsers that support it', + defaultBenchOptions.trace + ) + .action(generateSingleConfig); + +// Tests: +// - many* -n 2 -t 0 +// - many* -n 2 -t 0 -f preact-local -f preact-v8 +// - many* -n 2 -t 0 -f preact-local -f preact-v8 -b chrome +prog + .command('bench [globs]') + .describe( + 'Run the benchmarks matching the given globs. The root for the globs is the "src" directory. Specify "all" to run all benchmarks (default). To get more help on options, see polymer/tachometer help. Result table is printed to stdout and written to a csv and json file in the results directory.' + ) + .example('bench text*') + .example('bench *.html') + .example('bench all') + .example('bench many* -f preact-local -f preact-main') + .option( + '--browser, -b', + 'Which browsers to launch in automatic mode, comma-delimited (chrome, chrome-headless, firefox, firefox-headless, safari, edge, ie)', + defaultBenchOptions.browser + ) + // TODO: Consider parsing and adding to configs + // .option( + // '--window-size', + // '"width,height" in pixels of the browser windows that will be created', + // defaultOptions['window-size'] + // ) + .option( + '--sample-size, -n', + 'Minimum number of times to run each benchmark', + defaultBenchOptions['sample-size'] + ) + .option( + '--horizon, -h', + 'The degrees of difference to try and resolve when auto-sampling ("N%" or "Nms", comma-delimited)', + defaultBenchOptions.horizon + ) + .option( + '--timeout, -t', + 'The maximum number of minutes to spend auto-sampling', + defaultBenchOptions.timeout + ) + .option( + '--framework, -f', + 'Which framework(s) to bench. Specify the flag multiple times to compare specific frameworks. Default is all frameworks', + defaultBenchOptions.framework + ) + .option( + '--trace', + 'Enable perf tracing for browsers that support it', + defaultBenchOptions.trace + ) + .action(runBenches); + +// Tests: +// - (no args) +// - many* +// - many* -f preact-local -f preact-main +prog + .command('deopts [benchmark]') + .describe( + 'Run v8-deopt-viewer against the specified benchmark file (defaults to many_updates.html). If a glob is given, only the first matching file will be run' + ) + .example('deopts many_updates.html') + .example('deopts many*') + .example('deopts many* -f preact-local') + .example('deopts many* -f preact-local -f preact-main') + .option( + '--framework, -f', + 'The framework to run the benchmark with.', + defaultDeoptsOptions.framework + ) + .action(runDeopts); + +// Test +// - (no args) +// - 02_replace1k +prog + .command('analyze [benchmark]') + .describe( + 'Analyze the trace logs created by running benchmarks with the --trace flag' + ) + .example('analyze') + .example('analyze 02_replace1k') + .example('analyze many_updates') + .action(analyze); + +prog.parse(process.argv); diff --git a/benches/scripts/prepare.js b/benches/scripts/prepare.js new file mode 100644 index 0000000000..262d41a571 --- /dev/null +++ b/benches/scripts/prepare.js @@ -0,0 +1,57 @@ +import { readdir } from 'fs/promises'; +import path from 'path'; +import { execFileSync } from 'child_process'; +import { deleteAsync } from 'del'; +import { repoRoot } from './utils.js'; +import { existsSync } from 'fs'; + +const npmCmd = process.platform == 'win32' ? 'npm.cmd' : 'npm'; + +/** + * @param {string[]} frameworks + */ +export async function prepare(frameworks) { + const proxyRoot = repoRoot('benches/proxy-packages'); + const proxyDirs = (await readdir(proxyRoot)).map(dirname => + dirname.replace(/-proxy$/, '') + ); + + for (let framework of frameworks) { + const dirname = proxyDirs.find(dir => dir == framework); + if (dirname == null) { + continue; + } + + const proxyDir = (...args) => + path.join(proxyRoot, dirname + '-proxy', ...args); + + const packageScripts = proxyDir('scripts.mjs'); + const hasScripts = existsSync(packageScripts); + if (hasScripts) { + await import(packageScripts).then(m => m.preinstall?.()); + } + + // It appears from ad-hoc testing (npm v6.14.9 on Windows), npm will cache + // any locally referenced tarball files (e.g. "file:../../../preact.tgz") in + // its global cache. + // + // If a package-lock is present and the `npm ci` or `npm i` command is used, + // then npm will pull the tarball from the cache and not use the local + // tarball file even if the local reference has changed or is deleted. + // + // Because of the above behavior, we'll always delete the package-lock file + // and node_modules folder and use `npm i` to ensure we always get the + // latest packages + console.log(`Preparing ${dirname}: Cleaning ${proxyDir()}...`); + await deleteAsync(['package-lock.json', 'node_modules'], { + cwd: proxyDir() + }); + + console.log(`Preparing ${dirname}: Running "npm i" in ${proxyDir()}...`); + execFileSync(npmCmd, ['i'], { cwd: proxyDir(), stdio: 'inherit' }); + + if (hasScripts) { + await import(packageScripts).then(m => m.postinstall?.()); + } + } +} diff --git a/benches/scripts/tracing.d.ts b/benches/scripts/tracing.d.ts new file mode 100644 index 0000000000..cc3f271b72 --- /dev/null +++ b/benches/scripts/tracing.d.ts @@ -0,0 +1,224 @@ +// From: https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview + +export type TraceEvent = + | DurationEvent + | CompleteEvent + | InstantEvent + | AsyncEvent + | FlowEvent + | SampleEvent + | ProcessNameEvent + | ProcessLabelsEvent + | ProcessSortIndexEvent + | ThreadNameEvent + | ThreadSortIndexEvent + | MarkEvent + | ContextEvent + | ObjectCreatedEvent + | ObjectSnapshotEvent + | ObjectDestroyedEvent; + +interface BaseEvent { + /** The name of the event */ + name: string; + /** + * The event categories. This is a comma separated list of categories for the + * event. + */ + cat: string; + /** The event type (phase?) */ + ph: string; + /** The tracing clock timestamp (microseconds) */ + ts: number; + /** The thread clock timestamp of the event (microseconds) */ + tts?: number; + /** Process ID */ + pid: number; + /** Thread ID */ + tid: number; + /** Any args provided for the event */ + args: Record; +} + +interface StackData { + /** + * Stack frame at the start of the event. ID pointing the corresponding stack + * in the stackFrames map + */ + sf?: number; + /** + * Stack at the start of the event. Usually contains program counter addresses + * as hex strings + */ + stack?: string[]; +} + +/** Mark the beginning or end of a duration of work on a given thread */ +interface DurationEvent extends BaseEvent, StackData { + ph: 'B' | 'E'; +} + +/** Represents a duration of work on a given thread */ +interface CompleteEvent extends BaseEvent, StackData { + ph: 'X'; + /** Tracing clock duration (microseconds) */ + dur: number; + /** Thread clock duration? (microseconds) */ + tdur?: number; + /** Stack frame at the end of this event */ + esf?: number; + /** Stack at the end of this event */ + estack?: string[]; +} + +/** + * Mark something happened but has no duration associated with it. Only threaded + * scoped events can have stack data associated with them. + */ +interface InstantEvent extends BaseEvent, StackData { + ph: 'i' | 'I'; + /** Scope of the event. g = global, p = process, t = thread (default) */ + s?: 'g' | 'p' | 't'; +} + +/** + * Async operations (e.g. frames in a game, network I/O). b = start, n = + * instant, e = end. Events with the same category, id, and scope (if provided) + * are considered events from the same event tree. Nested async events should + * have the same category and id as its parent (but perhaps a different name). + */ +interface AsyncEvent extends BaseEvent { + ph: 'b' | 'n' | 'e'; + id: string; + scope?: string; +} + +/** + * Similar to Async events but allows a duration to be associated with each + * other across threads/processes. Visually, think of a flow event as an arrow + * between two duration events. With flow events, each event will be drawn in + * the thread it is emitted from. The events will be linked together visually + * using lines and arrows. + * + * TODO: Finish filling out + */ +interface FlowEvent extends BaseEvent { + ph: 's' | 't' | 'f'; +} + +interface SampleEvent extends BaseEvent, StackData { + ph: 'P'; +} + +interface ProcessNameEvent extends BaseEvent { + ph: 'M'; + name: 'process_name'; + args: { + name: string; + }; +} + +interface ProcessLabelsEvent extends BaseEvent { + ph: 'M'; + name: 'process_labels'; + args: { + labels: string; + }; +} + +interface ProcessSortIndexEvent extends BaseEvent { + ph: 'M'; + name: 'process_sort_index'; + args: { + sort_index: number; + }; +} + +interface ProcessUptimeEvent extends BaseEvent { + ph: 'M'; + name: 'process_uptime_seconds'; + args: { + uptime: number; + }; +} + +interface ThreadNameEvent extends BaseEvent { + ph: 'M'; + name: 'thread_name'; + args: { + name: string; + }; +} + +interface ThreadSortIndexEvent extends BaseEvent { + ph: 'M'; + name: 'thread_sort_index'; + args: { + sort_index: number; + }; +} + +interface NumCPUsEvent extends BaseEvent { + ph: 'M'; + name: 'num_cpus'; + args: { + number: number; + }; +} + +/** + * Mark events are created whenever a corresponding navigation timing API mark + * is created + */ +interface MarkEvent extends BaseEvent { + ph: 'R'; +} + +/** + * Context events are used to mark sequences of trace events as belonging to a + * particular context (or a tree of contexts). "(" = enter context, ")" = exit + * context. The enter event adds a context to all following trace events on the + * same thread until a corresponding leave event exits that context. Context ids + * refer to context object snapshots. + */ +interface ContextEvent extends BaseEvent { + ph: '(' | ')'; + id?: string; +} + +/** Object was created. Time is inclusive */ +interface ObjectCreatedEvent extends BaseEvent { + ph: 'N'; + id: string; + scope?: string; + args: undefined; +} + +interface ObjectSnapshotEvent extends BaseEvent { + ph: 'O'; + id: string; + scope?: string; + args: { + /** + * By default, an object snapshot inherits the category of its containing + * trace event. However, sometimes the object being snapshotted needs its + * own category. This happens because the place that creates an object + * snapshot's values is often separate form where the objects' constructor + * and destructor is called. Categories for the object creation and deletion + * commands must match the snapshot commands. Thus, the category of any + * object snapshot may be provided with the snapshot itself + */ + cat?: string; + /** Name of base type object */ + base_type?: string; + snapshot: any; + }; +} + +/** Object was destroyed. Time is exclusive */ +interface ObjectDestroyedEvent extends BaseEvent { + ph: 'D'; + id: string; + scope?: string; + args: undefined; +} diff --git a/benches/scripts/utils.js b/benches/scripts/utils.js new file mode 100644 index 0000000000..5f551888ef --- /dev/null +++ b/benches/scripts/utils.js @@ -0,0 +1,59 @@ +import { fileURLToPath } from 'url'; +import { stat, readFile } from 'fs/promises'; +import * as path from 'path'; +import escalade from 'escalade'; +import { globby } from 'globby'; + +export const IS_CI = process.env.CI === 'true'; + +// @ts-ignore +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +export const repoRoot = (...args) => path.join(__dirname, '..', '..', ...args); +export const benchesRoot = (...args) => repoRoot('benches', ...args); +export const resultsPath = (...args) => benchesRoot('results', ...args); + +export const toUrl = str => str.replace(/^[A-Za-z]+:/, '/').replace(/\\/g, '/'); + +export const allBenches = '**/*.html'; +export function globSrc(patterns) { + return globby(patterns, { cwd: benchesRoot('src') }); +} + +export async function getPkgBinPath(pkgName) { + /** @type {string | void} */ + let packageJsonPath; + try { + const pkgMainPath = await import.meta.resolve(pkgName); + packageJsonPath = await escalade(pkgMainPath, (dir, names) => { + if (names.includes('package.json')) { + return 'package.json'; + } + }); + } catch (e) { + // Tachometer doesn't have a valid 'main' entry + packageJsonPath = benchesRoot('node_modules', pkgName, 'package.json'); + } + + if (!packageJsonPath || !(await stat(packageJsonPath)).isFile()) { + throw new Error( + `Could not locate "${pkgName}" package.json at "${packageJsonPath}".` + ); + } + + const pkg = JSON.parse(await readFile(packageJsonPath, 'utf8')); + if (!pkg.bin) { + throw new Error(`${pkgName} package.json does not contain a "bin" entry.`); + } + + let binSubPath = pkg.bin; + if (typeof pkg.bin == 'object') { + binSubPath = pkg.bin[pkgName]; + } + + const binPath = path.join(path.dirname(packageJsonPath), binSubPath); + if (!(await stat(binPath)).isFile()) { + throw new Error(`Bin path for ${pkgName} is not a file: ${binPath}`); + } + + return binPath; +} diff --git a/benches/src/02_replace1k.html b/benches/src/02_replace1k.html new file mode 100644 index 0000000000..e6b6270b6e --- /dev/null +++ b/benches/src/02_replace1k.html @@ -0,0 +1,67 @@ + + + + + + replace all rows + + + + +
+ + + diff --git a/benches/src/03_update10th1k_x16.html b/benches/src/03_update10th1k_x16.html new file mode 100644 index 0000000000..9f770f7e5d --- /dev/null +++ b/benches/src/03_update10th1k_x16.html @@ -0,0 +1,84 @@ + + + + + + partial update + + + + +
+ + + diff --git a/benches/src/07_create10k.html b/benches/src/07_create10k.html new file mode 100644 index 0000000000..728d4727e5 --- /dev/null +++ b/benches/src/07_create10k.html @@ -0,0 +1,49 @@ + + + + + + create many rows + + + + +
+ + + diff --git a/benches/src/filter_list.html b/benches/src/filter_list.html new file mode 100644 index 0000000000..d31e1aff65 --- /dev/null +++ b/benches/src/filter_list.html @@ -0,0 +1,92 @@ + + + + + + Patching HTML + + + +
+ + + diff --git a/benches/src/hydrate1k.html b/benches/src/hydrate1k.html new file mode 100644 index 0000000000..bf43448650 --- /dev/null +++ b/benches/src/hydrate1k.html @@ -0,0 +1,145 @@ + + + + + + hydrate 1k table rows + + + + + + + + diff --git a/benches/src/keyed-children/components.js b/benches/src/keyed-children/components.js new file mode 100644 index 0000000000..0960b89d88 --- /dev/null +++ b/benches/src/keyed-children/components.js @@ -0,0 +1,152 @@ +import { Store } from './store.js'; + +/** + * @param {import('./index').Framework} framework + */ +export function getComponents({ createElement, Component }) { + class Row extends Component { + constructor(props) { + super(props); + this.onDelete = this.onDelete.bind(this); + this.onClick = this.onClick.bind(this); + } + + shouldComponentUpdate(nextProps, nextState) { + return ( + nextProps.data !== this.props.data || + nextProps.styleClass !== this.props.styleClass + ); + } + + onDelete() { + this.props.onDelete(this.props.data.id); + } + + onClick() { + this.props.onClick(this.props.data.id); + } + + render() { + let { styleClass, onClick, onDelete, data } = this.props; + return createElement( + 'tr', + { + className: styleClass + }, + createElement( + 'td', + { + className: 'col-md-1' + }, + data.id + ), + createElement( + 'td', + { + className: 'col-md-4' + }, + createElement( + 'a', + { + onClick: this.onClick + }, + data.label + ) + ), + createElement( + 'td', + { + className: 'col-md-1' + }, + createElement( + 'a', + { + onClick: this.onDelete + }, + createElement('span', { + className: 'glyphicon glyphicon-remove', + 'aria-hidden': 'true' + }) + ) + ), + createElement('td', { + className: 'col-md-6' + }) + ); + } + } + + class Main extends Component { + constructor(props) { + super(props); + this.state = { store: props.store ?? new Store() }; + this.select = this.select.bind(this); + this.delete = this.delete.bind(this); + + // @ts-ignore + window.app = this; + } + run() { + this.state.store.run(); + this.setState({ store: this.state.store }); + } + add() { + this.state.store.add(); + this.setState({ store: this.state.store }); + } + update() { + this.state.store.update(); + this.setState({ store: this.state.store }); + } + select(id) { + this.state.store.select(id); + this.setState({ store: this.state.store }); + } + delete(id) { + this.state.store.delete(id); + this.setState({ store: this.state.store }); + } + runLots() { + this.state.store.runLots(); + this.setState({ store: this.state.store }); + } + clear() { + this.state.store.clear(); + this.setState({ store: this.state.store }); + } + swapRows() { + this.state.store.swapRows(); + this.setState({ store: this.state.store }); + } + render() { + let rows = this.state.store.data.map((d, i) => { + return createElement(Row, { + key: d.id, + data: d, + onClick: this.select, + onDelete: this.delete, + styleClass: d.id === this.state.store.selected ? 'danger' : '' + }); + }); + return createElement( + 'div', + { + className: 'container' + }, + createElement( + 'table', + { + className: 'table table-hover table-striped test-data' + }, + createElement('tbody', {}, rows) + ), + createElement('span', { + className: 'preloadicon glyphicon glyphicon-remove', + 'aria-hidden': 'true' + }) + ); + } + } + + return { Main, Row }; +} diff --git a/benches/src/keyed-children/index.js b/benches/src/keyed-children/index.js new file mode 100644 index 0000000000..ece1265aea --- /dev/null +++ b/benches/src/keyed-children/index.js @@ -0,0 +1,29 @@ +import { getComponents } from './components.js'; + +/** + * @typedef Framework + * @property {(type: any, props?: any, ...children: any) => JSX.Element} createElement + * @property {(root: HTMLElement) => ({ render(vnode: JSX.Element): void; hydrate(vnode: JSX.Element): void; })} createRoot + * @property {any} Component + * + * @param {Framework} framework + * @param {HTMLElement} rootDom + */ +export function render(framework, rootDom) { + const { Main } = getComponents(framework); + framework.createRoot(rootDom).render(framework.createElement(Main)); + + /** @type {Main} */ + // @ts-ignore + const app = window.app; + return { + run: app.run.bind(app), + add: app.add.bind(app), + update: app.update.bind(app), + select: app.select.bind(app), + delete: app.delete.bind(app), + runLots: app.runLots.bind(app), + clear: app.clear.bind(app), + swapRows: app.swapRows.bind(app) + }; +} diff --git a/benches/src/keyed-children/store.js b/benches/src/keyed-children/store.js new file mode 100644 index 0000000000..413208d596 --- /dev/null +++ b/benches/src/keyed-children/store.js @@ -0,0 +1,119 @@ +function _random(max) { + return Math.round(Math.random() * 1000) % max; +} + +export class Store { + constructor() { + this.data = []; + this.selected = undefined; + this.id = 1; + } + buildData(count = 1000) { + var adjectives = [ + 'pretty', + 'large', + 'big', + 'small', + 'tall', + 'short', + 'long', + 'handsome', + 'plain', + 'quaint', + 'clean', + 'elegant', + 'easy', + 'angry', + 'crazy', + 'helpful', + 'mushy', + 'odd', + 'unsightly', + 'adorable', + 'important', + 'inexpensive', + 'cheap', + 'expensive', + 'fancy' + ]; + var colours = [ + 'red', + 'yellow', + 'blue', + 'green', + 'pink', + 'brown', + 'purple', + 'brown', + 'white', + 'black', + 'orange' + ]; + var nouns = [ + 'table', + 'chair', + 'house', + 'bbq', + 'desk', + 'car', + 'pony', + 'cookie', + 'sandwich', + 'burger', + 'pizza', + 'mouse', + 'keyboard' + ]; + var data = []; + for (var i = 0; i < count; i++) + data.push({ + id: this.id++, + label: + adjectives[_random(adjectives.length)] + + ' ' + + colours[_random(colours.length)] + + ' ' + + nouns[_random(nouns.length)] + }); + return data; + } + updateData(mod = 10) { + for (let i = 0; i < this.data.length; i += 10) { + this.data[i] = Object.assign({}, this.data[i], { + label: this.data[i].label + ' !!!' + }); + } + } + delete(id) { + var idx = this.data.findIndex(d => d.id === id); + this.data.splice(idx, 1); + } + run() { + this.data = this.buildData(); + this.selected = undefined; + } + add() { + this.data = this.data.concat(this.buildData(1000)); + } + update() { + this.updateData(); + } + select(id) { + this.selected = id; + } + runLots() { + this.data = this.buildData(10000); + this.selected = undefined; + } + clear() { + this.data = []; + this.selected = undefined; + } + swapRows() { + if (this.data.length > 998) { + var a = this.data[1]; + this.data[1] = this.data[998]; + this.data[998] = a; + } + } +} diff --git a/benches/src/many_updates.html b/benches/src/many_updates.html new file mode 100644 index 0000000000..e59b30421f --- /dev/null +++ b/benches/src/many_updates.html @@ -0,0 +1,112 @@ + + + + + + Patching HTML + + + +
+ + + diff --git a/benches/src/text_update.html b/benches/src/text_update.html new file mode 100644 index 0000000000..6874558160 --- /dev/null +++ b/benches/src/text_update.html @@ -0,0 +1,34 @@ + + + + + + Text Updates + + +
+ + + diff --git a/benches/src/todo.html b/benches/src/todo.html new file mode 100644 index 0000000000..3d34e5420e --- /dev/null +++ b/benches/src/todo.html @@ -0,0 +1,247 @@ + + + + + + ToDo List + + + +
+ + + diff --git a/benches/src/util.js b/benches/src/util.js new file mode 100644 index 0000000000..5eda4f5027 --- /dev/null +++ b/benches/src/util.js @@ -0,0 +1,146 @@ +// import afterFrame from "../node_modules/afterframe/dist/afterframe.module.js"; +import afterFrame from 'afterframe'; + +export { afterFrame }; + +export const measureName = 'duration'; + +const majorTask = () => + new Promise(resolve => { + window.addEventListener('message', resolve, { once: true }); + window.postMessage('major task delay', '*'); + }); + +let promise = null; +export function afterFrameAsync() { + if (promise === null) { + promise = new Promise(resolve => + afterFrame(time => { + promise = null; + resolve(time); + }) + ); + } + + return promise; +} + +export async function measureMemory() { + if ('gc' in window && 'memory' in performance) { + // Report results in MBs + performance.mark('gc-start'); + window.gc(); + performance.measure('gc', 'gc-start'); + + // window.gc synchronously triggers one Major GC. However that MajorGC + // asynchronously triggers additional MajorGCs until the + // usedJSHeapSizeBefore and usedJSHeapSizeAfter are the same. Here, we'll + // wait a moment for some (hopefully all) additional GCs to finish before + // measuring the memory. + await majorTask(); + performance.mark('measure-memory'); + window.usedJSHeapSize = performance.memory.usedJSHeapSize / 1e6; + } else { + window.usedJSHeapSize = 0; + } +} + +export function markRunStart(runId) { + performance.mark(`run-${runId}-start`); +} + +let staticPromise = Promise.resolve(); +export function markRunEnd(runId) { + return staticPromise.then(() => { + performance.mark(`run-${runId}-end`); + performance.measure( + `run-${runId}`, + `run-${runId}-start`, + `run-${runId}-end` + ); + }); +} + +export function getRowIdSel(index) { + return `tbody > tr:nth-child(${index}) > td:first-child`; +} + +export function getRowLinkSel(index) { + return `tbody > tr:nth-child(${index}) > td:nth-child(2) > a`; +} + +/** + * @param {string} selector + * @returns {Element} + */ +export function getBySelector(selector) { + const element = document.querySelector(selector); + if (element == null) { + throw new Error(`Could not find element matching selector: ${selector}`); + } + + return element; +} + +export function testElement(selector) { + const testElement = document.querySelector(selector); + if (testElement == null) { + throw new Error( + 'Test failed. Rendering after one paint was not successful' + ); + } +} + +export function testElementText(selector, expectedText) { + const elm = document.querySelector(selector); + if (elm == null) { + throw new Error('Could not find element matching selector: ' + selector); + } + + if (elm.textContent != expectedText) { + throw new Error( + `Element did not have expected text. Expected: '${expectedText}' Actual: '${elm.textContent}'` + ); + } +} + +export function testElementTextContains(selector, expectedText) { + const elm = getBySelector(selector); + if (!elm.textContent.includes(expectedText)) { + throw new Error( + `Element did not include expected text. Expected to include: '${expectedText}' Actual: '${elm.textContent}'` + ); + } +} + +let count = 0; +const channel = new MessageChannel(); +const callbacks = new Map(); +channel.port1.onmessage = e => { + let id = e.data; + let fn = callbacks.get(id); + callbacks.delete(id); + fn(); +}; +let pm = function (callback) { + let id = ++count; + callbacks.set(id, callback); + this.postMessage(id); +}.bind(channel.port2); + +export function nextTick() { + return new Promise(r => pm(r)); +} + +export function mutateAndLayoutAsync(mutation, times = 1) { + return new Promise(resolve => { + requestAnimationFrame(() => { + for (let i = 0; i < times; i++) { + mutation(i); + } + pm(resolve); + }); + }); +} + +export const sleep = ms => new Promise(r => setTimeout(r, ms)); diff --git a/benchmarks b/benchmarks new file mode 160000 index 0000000000..0e3f96775f --- /dev/null +++ b/benchmarks @@ -0,0 +1 @@ +Subproject commit 0e3f96775fc12229ee91761b15547aa7b297393c diff --git a/biome.json b/biome.json new file mode 100644 index 0000000000..0453af6e25 --- /dev/null +++ b/biome.json @@ -0,0 +1,56 @@ +{ + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "tab", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 80, + "attributePosition": "auto", + "ignore": [ + "**/.DS_Store", + "**/node_modules", + "**/npm-debug.log", + "**/dist", + "*/package-lock.json", + "**/yarn.lock", + "**/.vscode", + "**/.idea", + "test/ts/**/*.js", + "**/coverage", + "**/*.sw[op]", + "**/*.log", + "**/package/", + "**/preact-*.tgz", + "**/preact.tgz", + "benches/dist/", + "benches/results/", + "benches/logs/", + "benches/logs-saved/", + "benches/node_modules/", + "benches/proxy-packages/*/package-lock.json", + "**/package-lock.json" + ] + }, + "organizeImports": { "enabled": true }, + "linter": { "enabled": true, "rules": { "recommended": true } }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "none", + "semicolons": "always", + "arrowParentheses": "asNeeded", + "bracketSpacing": true, + "bracketSameLine": false, + "quoteStyle": "single", + "attributePosition": "auto" + } + }, + "overrides": [ + { + "include": ["*.json", ".*rc", "*.yml"], + "formatter": { "indentWidth": 2, "indentStyle": "space" } + } + ] +} diff --git a/compat/LICENSE b/compat/LICENSE new file mode 100644 index 0000000000..da5389a93c --- /dev/null +++ b/compat/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-present Jason Miller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/compat/client.d.ts b/compat/client.d.ts new file mode 100644 index 0000000000..5daf8ea1e5 --- /dev/null +++ b/compat/client.d.ts @@ -0,0 +1,11 @@ +import * as preact from '../src'; + +export function createRoot(container: preact.ContainerNode): { + render(children: preact.ComponentChild): void; + unmount(): void; +}; + +export function hydrateRoot( + container: preact.ContainerNode, + children: preact.ComponentChild +): typeof createRoot; diff --git a/compat/client.js b/compat/client.js new file mode 100644 index 0000000000..bc48c21bf2 --- /dev/null +++ b/compat/client.js @@ -0,0 +1,21 @@ +const { render, hydrate, unmountComponentAtNode } = require('preact/compat'); + +function createRoot(container) { + return { + // eslint-disable-next-line + render: function (children) { + render(children, container); + }, + // eslint-disable-next-line + unmount: function () { + unmountComponentAtNode(container); + } + }; +} + +exports.createRoot = createRoot; + +exports.hydrateRoot = function (container, children) { + hydrate(children, container); + return createRoot(container); +}; diff --git a/compat/client.mjs b/compat/client.mjs new file mode 100644 index 0000000000..f9d1814e18 --- /dev/null +++ b/compat/client.mjs @@ -0,0 +1,24 @@ +import { render, hydrate, unmountComponentAtNode } from 'preact/compat'; + +export function createRoot(container) { + return { + // eslint-disable-next-line + render: function (children) { + render(children, container); + }, + // eslint-disable-next-line + unmount: function () { + unmountComponentAtNode(container); + } + }; +} + +export function hydrateRoot(container, children) { + hydrate(children, container); + return createRoot(container); +} + +export default { + createRoot, + hydrateRoot +}; diff --git a/compat/jsx-dev-runtime.js b/compat/jsx-dev-runtime.js new file mode 100644 index 0000000000..32054c4908 --- /dev/null +++ b/compat/jsx-dev-runtime.js @@ -0,0 +1,3 @@ +require('preact/compat'); + +module.exports = require('preact/jsx-runtime'); diff --git a/compat/jsx-dev-runtime.mjs b/compat/jsx-dev-runtime.mjs new file mode 100644 index 0000000000..e7e3aef044 --- /dev/null +++ b/compat/jsx-dev-runtime.mjs @@ -0,0 +1,3 @@ +import 'preact/compat'; + +export * from 'preact/jsx-runtime'; diff --git a/compat/jsx-runtime.js b/compat/jsx-runtime.js new file mode 100644 index 0000000000..32054c4908 --- /dev/null +++ b/compat/jsx-runtime.js @@ -0,0 +1,3 @@ +require('preact/compat'); + +module.exports = require('preact/jsx-runtime'); diff --git a/compat/jsx-runtime.mjs b/compat/jsx-runtime.mjs new file mode 100644 index 0000000000..e7e3aef044 --- /dev/null +++ b/compat/jsx-runtime.mjs @@ -0,0 +1,3 @@ +import 'preact/compat'; + +export * from 'preact/jsx-runtime'; diff --git a/compat/mangle.json b/compat/mangle.json new file mode 100644 index 0000000000..8352f38e63 --- /dev/null +++ b/compat/mangle.json @@ -0,0 +1,21 @@ +{ + "help": { + "what is this file?": "It controls protected/private property mangling so that minified builds have consistent property names.", + "why are there duplicate minified properties?": "Most properties are only used on one type of objects, so they can have the same name since they will never collide. Doing this reduces size." + }, + "minify": { + "mangle": { + "properties": { + "regex": "^_[^_]", + "reserved": [ + "__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED", + "__REACT_DEVTOOLS_GLOBAL_HOOK__", + "__PREACT_DEVTOOLS__", + "_renderers", + "__source", + "__self" + ] + } + } + } +} diff --git a/compat/package.json b/compat/package.json new file mode 100644 index 0000000000..bb80a0fa7a --- /dev/null +++ b/compat/package.json @@ -0,0 +1,51 @@ +{ + "name": "preact-compat", + "amdName": "preactCompat", + "version": "4.0.0", + "private": true, + "description": "A React compatibility layer for Preact", + "main": "dist/compat.js", + "module": "dist/compat.module.js", + "umd:main": "dist/compat.umd.js", + "source": "src/index.js", + "types": "src/index.d.ts", + "license": "MIT", + "mangle": { + "regex": "^_" + }, + "peerDependencies": { + "preact": "^10.0.0" + }, + "exports": { + ".": { + "types": "./src/index.d.ts", + "browser": "./dist/compat.module.js", + "umd": "./dist/compat.umd.js", + "import": "./dist/compat.mjs", + "require": "./dist/compat.js" + }, + "./client": { + "types": "./client.d.ts", + "import": "./client.mjs", + "require": "./client.js" + }, + "./server": { + "browser": "./server.browser.js", + "import": "./server.mjs", + "require": "./server.js" + }, + "./jsx-runtime": { + "import": "./jsx-runtime.mjs", + "require": "./jsx-runtime.js" + }, + "./jsx-dev-runtime": { + "import": "./jsx-dev-runtime.mjs", + "require": "./jsx-dev-runtime.js" + }, + "./scheduler": { + "import": "./scheduler.mjs", + "require": "./scheduler.js" + }, + "./package.json": "./package.json" + } +} diff --git a/compat/scheduler.js b/compat/scheduler.js new file mode 100644 index 0000000000..01c4ceba0c --- /dev/null +++ b/compat/scheduler.js @@ -0,0 +1,15 @@ +// see scheduler.mjs + +function unstable_runWithPriority(priority, callback) { + return callback(); +} + +module.exports = { + unstable_ImmediatePriority: 1, + unstable_UserBlockingPriority: 2, + unstable_NormalPriority: 3, + unstable_LowPriority: 4, + unstable_IdlePriority: 5, + unstable_runWithPriority, + unstable_now: performance.now.bind(performance) +}; diff --git a/compat/scheduler.mjs b/compat/scheduler.mjs new file mode 100644 index 0000000000..c51bd4bfda --- /dev/null +++ b/compat/scheduler.mjs @@ -0,0 +1,23 @@ +/* eslint-disable */ + +// This file includes experimental React APIs exported from the "scheduler" +// npm package. Despite being explicitely marked as unstable some libraries +// already make use of them. This file is not a full replacement for the +// scheduler package, but includes the necessary shims to make those libraries +// work with Preact. + +export var unstable_ImmediatePriority = 1; +export var unstable_UserBlockingPriority = 2; +export var unstable_NormalPriority = 3; +export var unstable_LowPriority = 4; +export var unstable_IdlePriority = 5; + +/** + * @param {number} priority + * @param {() => void} callback + */ +export function unstable_runWithPriority(priority, callback) { + return callback(); +} + +export var unstable_now = performance.now.bind(performance); diff --git a/compat/server.browser.js b/compat/server.browser.js new file mode 100644 index 0000000000..51c7a5d332 --- /dev/null +++ b/compat/server.browser.js @@ -0,0 +1,11 @@ +import { renderToString } from 'preact-render-to-string'; + +export { + renderToString, + renderToString as renderToStaticMarkup +} from 'preact-render-to-string'; + +export default { + renderToString, + renderToStaticMarkup: renderToString +}; diff --git a/compat/server.js b/compat/server.js new file mode 100644 index 0000000000..3eaf7206b5 --- /dev/null +++ b/compat/server.js @@ -0,0 +1,26 @@ +/* eslint-disable */ +var renderToString; +try { + const mod = require('preact-render-to-string'); + renderToString = mod.default || mod.renderToString || mod; +} catch (e) { + throw Error( + 'renderToString() error: missing "preact-render-to-string" dependency.' + ); +} + +var renderToPipeableStream; +try { + const mod = require('preact-render-to-string/stream-node'); + renderToPipeableStream = mod.default || mod.renderToPipeableStream || mod; +} catch (e) { + throw Error( + 'renderToPipeableStream() error: update "preact-render-to-string" dependency to at least 6.5.0.' + ); +} + +module.exports = { + renderToString: renderToString, + renderToStaticMarkup: renderToString, + renderToPipeableStream: renderToPipeableStream +}; diff --git a/compat/server.mjs b/compat/server.mjs new file mode 100644 index 0000000000..bd4363947a --- /dev/null +++ b/compat/server.mjs @@ -0,0 +1,15 @@ +import { renderToString } from 'preact-render-to-string'; +import { renderToPipeableStream } from 'preact-render-to-string/stream-node'; + +export { + renderToString, + renderToString as renderToStaticMarkup +} from 'preact-render-to-string'; + +export { renderToPipeableStream } from 'preact-render-to-string/stream-node'; + +export default { + renderToString, + renderToStaticMarkup: renderToString, + renderToPipeableStream +}; diff --git a/compat/src/Children.js b/compat/src/Children.js new file mode 100644 index 0000000000..0295d936e8 --- /dev/null +++ b/compat/src/Children.js @@ -0,0 +1,21 @@ +import { toChildArray } from 'preact'; + +const mapFn = (children, fn) => { + if (children == null) return null; + return toChildArray(toChildArray(children).map(fn)); +}; + +// This API is completely unnecessary for Preact, so it's basically passthrough. +export const Children = { + map: mapFn, + forEach: mapFn, + count(children) { + return children ? toChildArray(children).length : 0; + }, + only(children) { + const normalized = toChildArray(children); + if (normalized.length !== 1) throw 'Children.only'; + return normalized[0]; + }, + toArray: toChildArray +}; diff --git a/compat/src/PureComponent.js b/compat/src/PureComponent.js new file mode 100644 index 0000000000..380aeb037f --- /dev/null +++ b/compat/src/PureComponent.js @@ -0,0 +1,16 @@ +import { Component } from 'preact'; +import { shallowDiffers } from './util'; + +/** + * Component class with a predefined `shouldComponentUpdate` implementation + */ +export function PureComponent(p, c) { + this.props = p; + this.context = c; +} +PureComponent.prototype = new Component(); +// Some third-party libraries check if this property is present +PureComponent.prototype.isPureReactComponent = true; +PureComponent.prototype.shouldComponentUpdate = function (props, state) { + return shallowDiffers(this.props, props) || shallowDiffers(this.state, state); +}; diff --git a/compat/src/forwardRef.js b/compat/src/forwardRef.js new file mode 100644 index 0000000000..35105eadc5 --- /dev/null +++ b/compat/src/forwardRef.js @@ -0,0 +1,47 @@ +import { options } from 'preact'; + +let oldDiffHook = options._diff; +options._diff = vnode => { + if (vnode.type && vnode.type._forwarded && vnode.ref) { + vnode.props.ref = vnode.ref; + vnode.ref = null; + } + if (oldDiffHook) oldDiffHook(vnode); +}; + +export const REACT_FORWARD_SYMBOL = + (typeof Symbol != 'undefined' && + Symbol.for && + Symbol.for('react.forward_ref')) || + 0xf47; + +/** + * Pass ref down to a child. This is mainly used in libraries with HOCs that + * wrap components. Using `forwardRef` there is an easy way to get a reference + * of the wrapped component instead of one of the wrapper itself. + * @param {import('./index').ForwardFn} fn + * @returns {import('./internal').FunctionComponent} + */ +export function forwardRef(fn) { + function Forwarded(props) { + if (!('ref' in props)) return fn(props, null); + + let ref = props.ref; + delete props.ref; + const result = fn(props, ref); + props.ref = ref; + return result; + } + + // mobx-react checks for this being present + Forwarded.$$typeof = REACT_FORWARD_SYMBOL; + // mobx-react heavily relies on implementation details. + // It expects an object here with a `render` property, + // and prototype.render will fail. Without this + // mobx-react throws. + Forwarded.render = Forwarded; + + Forwarded.prototype.isReactComponent = Forwarded._forwarded = true; + Forwarded.displayName = 'ForwardRef(' + (fn.displayName || fn.name) + ')'; + return Forwarded; +} diff --git a/compat/src/hooks.js b/compat/src/hooks.js new file mode 100644 index 0000000000..fbc2be592b --- /dev/null +++ b/compat/src/hooks.js @@ -0,0 +1,70 @@ +import { useState, useLayoutEffect, useEffect } from 'preact/hooks'; +import { is } from './util'; + +/** + * This is taken from https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js#L84 + * on a high level this cuts out the warnings, ... and attempts a smaller implementation + * @typedef {{ _value: any; _getSnapshot: () => any }} Store + */ +export function useSyncExternalStore(subscribe, getSnapshot) { + const value = getSnapshot(); + + /** + * @typedef {{ _instance: Store }} StoreRef + * @type {[StoreRef, (store: StoreRef) => void]} + */ + const [{ _instance }, forceUpdate] = useState({ + _instance: { _value: value, _getSnapshot: getSnapshot } + }); + + useLayoutEffect(() => { + _instance._value = value; + _instance._getSnapshot = getSnapshot; + + if (didSnapshotChange(_instance)) { + forceUpdate({ _instance }); + } + }, [subscribe, value, getSnapshot]); + + useEffect(() => { + if (didSnapshotChange(_instance)) { + forceUpdate({ _instance }); + } + + return subscribe(() => { + if (didSnapshotChange(_instance)) { + forceUpdate({ _instance }); + } + }); + }, [subscribe]); + + return value; +} + +/** @type {(inst: Store) => boolean} */ +function didSnapshotChange(inst) { + const latestGetSnapshot = inst._getSnapshot; + const prevValue = inst._value; + try { + const nextValue = latestGetSnapshot(); + return !is(prevValue, nextValue); + } catch (error) { + return true; + } +} + +export function startTransition(cb) { + cb(); +} + +export function useDeferredValue(val) { + return val; +} + +export function useTransition() { + return [false, startTransition]; +} + +// TODO: in theory this should be done after a VNode is diffed as we want to insert +// styles/... before it attaches +export const useInsertionEffect = useLayoutEffect; diff --git a/compat/src/index.d.ts b/compat/src/index.d.ts new file mode 100644 index 0000000000..d6b7aa3156 --- /dev/null +++ b/compat/src/index.d.ts @@ -0,0 +1,332 @@ +import * as _hooks from '../../hooks'; +import * as preact from '../../src'; +import { JSXInternal } from '../../src/jsx'; +import * as _Suspense from './suspense'; +import * as _SuspenseList from './suspense-list'; + +interface SignalLike { + value: T; + peek(): T; + subscribe(fn: (value: T) => void): () => void; +} + +type Signalish = T | SignalLike; + +// export default React; +export = React; +export as namespace React; +declare namespace React { + // Export JSX + export import JSX = JSXInternal; + + // Hooks + export import CreateHandle = _hooks.CreateHandle; + export import EffectCallback = _hooks.EffectCallback; + export import Inputs = _hooks.Inputs; + export import PropRef = _hooks.PropRef; + export import Reducer = _hooks.Reducer; + export import Dispatch = _hooks.Dispatch; + export import SetStateAction = _hooks.StateUpdater; + export import useCallback = _hooks.useCallback; + export import useContext = _hooks.useContext; + export import useDebugValue = _hooks.useDebugValue; + export import useEffect = _hooks.useEffect; + export import useImperativeHandle = _hooks.useImperativeHandle; + export import useId = _hooks.useId; + export import useLayoutEffect = _hooks.useLayoutEffect; + export import useMemo = _hooks.useMemo; + export import useReducer = _hooks.useReducer; + export import useRef = _hooks.useRef; + export import useState = _hooks.useState; + // React 18 hooks + export import useInsertionEffect = _hooks.useLayoutEffect; + export function useTransition(): [false, typeof startTransition]; + export function useDeferredValue(val: T): T; + export function useSyncExternalStore( + subscribe: (flush: () => void) => () => void, + getSnapshot: () => T + ): T; + + // Preact Defaults + export import Context = preact.Context; + export import ContextType = preact.ContextType; + export import RefObject = preact.RefObject; + export import Component = preact.Component; + export import FunctionComponent = preact.FunctionComponent; + export import ComponentType = preact.ComponentType; + export import ComponentClass = preact.ComponentClass; + export import FC = preact.FunctionComponent; + export import createContext = preact.createContext; + export import Ref = preact.Ref; + export import createRef = preact.createRef; + export import Fragment = preact.Fragment; + export import createElement = preact.createElement; + export import cloneElement = preact.cloneElement; + export import ComponentProps = preact.ComponentProps; + export import ReactNode = preact.ComponentChild; + export import ReactElement = preact.VNode; + export import Consumer = preact.Consumer; + export import ErrorInfo = preact.ErrorInfo; + + // Suspense + export import Suspense = _Suspense.Suspense; + export import lazy = _Suspense.lazy; + export import SuspenseList = _SuspenseList.SuspenseList; + + // Compat + export import StrictMode = preact.Fragment; + export const version: string; + export function startTransition(cb: () => void): void; + + // HTML + export interface HTMLAttributes + extends JSXInternal.HTMLAttributes {} + export interface HTMLProps + extends JSXInternal.HTMLAttributes, + preact.ClassAttributes {} + export interface AllHTMLAttributes + extends JSXInternal.AllHTMLAttributes {} + export import DetailedHTMLProps = JSXInternal.DetailedHTMLProps; + export import CSSProperties = JSXInternal.CSSProperties; + + export interface SVGProps + extends JSXInternal.SVGAttributes, + preact.ClassAttributes {} + + interface SVGAttributes extends JSXInternal.SVGAttributes {} + + interface ReactSVG extends JSXInternal.IntrinsicSVGElements {} + + export import HTMLAttributeReferrerPolicy = JSXInternal.HTMLAttributeReferrerPolicy; + export import HTMLAttributeAnchorTarget = JSXInternal.HTMLAttributeAnchorTarget; + export import HTMLInputTypeAttribute = JSXInternal.HTMLInputTypeAttribute; + export import HTMLAttributeCrossOrigin = JSXInternal.HTMLAttributeCrossOrigin; + + export import AnchorHTMLAttributes = JSXInternal.AnchorHTMLAttributes; + export import AudioHTMLAttributes = JSXInternal.AudioHTMLAttributes; + export import AreaHTMLAttributes = JSXInternal.AreaHTMLAttributes; + export import BaseHTMLAttributes = JSXInternal.BaseHTMLAttributes; + export import BlockquoteHTMLAttributes = JSXInternal.BlockquoteHTMLAttributes; + export import ButtonHTMLAttributes = JSXInternal.ButtonHTMLAttributes; + export import CanvasHTMLAttributes = JSXInternal.CanvasHTMLAttributes; + export import ColHTMLAttributes = JSXInternal.ColHTMLAttributes; + export import ColgroupHTMLAttributes = JSXInternal.ColgroupHTMLAttributes; + export import DataHTMLAttributes = JSXInternal.DataHTMLAttributes; + export import DetailsHTMLAttributes = JSXInternal.DetailsHTMLAttributes; + export import DelHTMLAttributes = JSXInternal.DelHTMLAttributes; + export import DialogHTMLAttributes = JSXInternal.DialogHTMLAttributes; + export import EmbedHTMLAttributes = JSXInternal.EmbedHTMLAttributes; + export import FieldsetHTMLAttributes = JSXInternal.FieldsetHTMLAttributes; + export import FormHTMLAttributes = JSXInternal.FormHTMLAttributes; + export import IframeHTMLAttributes = JSXInternal.IframeHTMLAttributes; + export import ImgHTMLAttributes = JSXInternal.ImgHTMLAttributes; + export import InsHTMLAttributes = JSXInternal.InsHTMLAttributes; + export import InputHTMLAttributes = JSXInternal.InputHTMLAttributes; + export import KeygenHTMLAttributes = JSXInternal.KeygenHTMLAttributes; + export import LabelHTMLAttributes = JSXInternal.LabelHTMLAttributes; + export import LiHTMLAttributes = JSXInternal.LiHTMLAttributes; + export import LinkHTMLAttributes = JSXInternal.LinkHTMLAttributes; + export import MapHTMLAttributes = JSXInternal.MapHTMLAttributes; + export import MenuHTMLAttributes = JSXInternal.MenuHTMLAttributes; + export import MediaHTMLAttributes = JSXInternal.MediaHTMLAttributes; + export import MetaHTMLAttributes = JSXInternal.MetaHTMLAttributes; + export import MeterHTMLAttributes = JSXInternal.MeterHTMLAttributes; + export import QuoteHTMLAttributes = JSXInternal.QuoteHTMLAttributes; + export import ObjectHTMLAttributes = JSXInternal.ObjectHTMLAttributes; + export import OlHTMLAttributes = JSXInternal.OlHTMLAttributes; + export import OptgroupHTMLAttributes = JSXInternal.OptgroupHTMLAttributes; + export import OptionHTMLAttributes = JSXInternal.OptionHTMLAttributes; + export import OutputHTMLAttributes = JSXInternal.OutputHTMLAttributes; + export import ParamHTMLAttributes = JSXInternal.ParamHTMLAttributes; + export import ProgressHTMLAttributes = JSXInternal.ProgressHTMLAttributes; + export import SlotHTMLAttributes = JSXInternal.SlotHTMLAttributes; + export import ScriptHTMLAttributes = JSXInternal.ScriptHTMLAttributes; + export import SelectHTMLAttributes = JSXInternal.SelectHTMLAttributes; + export import SourceHTMLAttributes = JSXInternal.SourceHTMLAttributes; + export import StyleHTMLAttributes = JSXInternal.StyleHTMLAttributes; + export import TableHTMLAttributes = JSXInternal.TableHTMLAttributes; + export import TextareaHTMLAttributes = JSXInternal.TextareaHTMLAttributes; + export import TdHTMLAttributes = JSXInternal.TdHTMLAttributes; + export import ThHTMLAttributes = JSXInternal.ThHTMLAttributes; + export import TimeHTMLAttributes = JSXInternal.TimeHTMLAttributes; + export import TrackHTMLAttributes = JSXInternal.TrackHTMLAttributes; + export import VideoHTMLAttributes = JSXInternal.VideoHTMLAttributes; + + // Events + export import TargetedEvent = JSXInternal.TargetedEvent; + export import ChangeEvent = JSXInternal.TargetedEvent; + export import ClipboardEvent = JSXInternal.TargetedClipboardEvent; + export import CompositionEvent = JSXInternal.TargetedCompositionEvent; + export import DragEvent = JSXInternal.TargetedDragEvent; + export import PointerEvent = JSXInternal.TargetedPointerEvent; + export import FocusEvent = JSXInternal.TargetedFocusEvent; + export import FormEvent = JSXInternal.TargetedEvent; + export import InvalidEvent = JSXInternal.TargetedEvent; + export import KeyboardEvent = JSXInternal.TargetedKeyboardEvent; + export import MouseEvent = JSXInternal.TargetedMouseEvent; + export import TouchEvent = JSXInternal.TargetedTouchEvent; + export import UIEvent = JSXInternal.TargetedUIEvent; + export import AnimationEvent = JSXInternal.TargetedAnimationEvent; + export import TransitionEvent = JSXInternal.TargetedTransitionEvent; + + // Event Handler Types + export import ChangeEventHandler = JSXInternal.GenericEventHandler; + export import ClipboardEventHandler = JSXInternal.ClipboardEventHandler; + export import CompositionEventHandler = JSXInternal.CompositionEventHandler; + export import DragEventHandler = JSXInternal.DragEventHandler; + export import PointerEventHandler = JSXInternal.PointerEventHandler; + export import FocusEventHandler = JSXInternal.FocusEventHandler; + export import FormEventHandler = JSXInternal.GenericEventHandler; + export import InvalidEventHandler = JSXInternal.GenericEventHandler; + export import KeyboardEventHandler = JSXInternal.KeyboardEventHandler; + export import MouseEventHandler = JSXInternal.MouseEventHandler; + export import TouchEventHandler = JSXInternal.TouchEventHandler; + export import UIEventHandler = JSXInternal.UIEventHandler; + export import AnimationEventHandler = JSXInternal.AnimationEventHandler; + export import TransitionEventHandler = JSXInternal.TransitionEventHandler; + + export function createPortal( + vnode: preact.ComponentChildren, + container: preact.ContainerNode + ): preact.VNode; + + export function render( + vnode: preact.ComponentChild, + parent: preact.ContainerNode, + callback?: () => void + ): Component | null; + + export function hydrate( + vnode: preact.ComponentChild, + parent: preact.ContainerNode, + callback?: () => void + ): Component | null; + + export function unmountComponentAtNode( + container: preact.ContainerNode + ): boolean; + + export function createFactory( + type: preact.VNode['type'] + ): ( + props?: any, + ...children: preact.ComponentChildren[] + ) => preact.VNode; + export function isValidElement(element: any): boolean; + export function isFragment(element: any): boolean; + export function isMemo(element: any): boolean; + export function findDOMNode( + component: preact.Component | Element + ): Element | null; + + export abstract class PureComponent< + P = {}, + S = {}, + SS = any + > extends preact.Component { + isPureReactComponent: boolean; + } + + export type MemoExoticComponent> = + preact.FunctionComponent> & { + readonly type: C; + }; + + export function memo

( + component: preact.FunctionalComponent

, + comparer?: (prev: P, next: P) => boolean + ): preact.FunctionComponent

; + export function memo>( + component: C, + comparer?: ( + prev: preact.ComponentProps, + next: preact.ComponentProps + ) => boolean + ): C; + + export interface RefAttributes extends preact.Attributes { + ref?: preact.Ref | undefined; + } + + export interface ForwardFn

{ + (props: P, ref: ForwardedRef): preact.ComponentChild; + displayName?: string; + } + + export interface ForwardRefExoticComponent

+ extends preact.FunctionComponent

{ + defaultProps?: Partial

| undefined; + } + + export function forwardRef( + fn: ForwardFn + ): preact.FunctionalComponent & { ref?: preact.Ref }>; + + export type PropsWithoutRef

= Omit; + + interface MutableRefObject { + current: T; + } + + export type ForwardedRef = + | ((instance: T | null) => void) + | MutableRefObject + | null; + + export type ElementType< + P = any, + Tag extends keyof JSX.IntrinsicElements = keyof JSX.IntrinsicElements + > = + | { [K in Tag]: P extends JSX.IntrinsicElements[K] ? K : never }[Tag] + | ComponentType

; + + export type ComponentPropsWithoutRef = PropsWithoutRef< + ComponentProps + >; + + export type ComponentPropsWithRef< + C extends ComponentType | keyof JSXInternal.IntrinsicElements + > = C extends new ( + props: infer P + ) => Component + ? PropsWithoutRef

& RefAttributes> + : ComponentProps; + + export function flushSync(fn: () => R): R; + export function flushSync(fn: (a: A) => R, a: A): R; + + export function unstable_batchedUpdates( + callback: (arg?: any) => void, + arg?: any + ): void; + + export type PropsWithChildren

= P & { + children?: preact.ComponentChildren | undefined; + }; + + export const Children: { + map( + children: T | T[], + fn: (child: T, i: number) => R + ): R[]; + forEach( + children: T | T[], + fn: (child: T, i: number) => void + ): void; + count: (children: preact.ComponentChildren) => number; + only: (children: preact.ComponentChildren) => preact.ComponentChild; + toArray: (children: preact.ComponentChildren) => preact.VNode<{}>[]; + }; + + // scheduler + export const unstable_ImmediatePriority: number; + export const unstable_UserBlockingPriority: number; + export const unstable_NormalPriority: number; + export const unstable_LowPriority: number; + export const unstable_IdlePriority: number; + export function unstable_runWithPriority( + priority: number, + callback: () => void + ): void; + export const unstable_now: () => number; +} diff --git a/compat/src/index.js b/compat/src/index.js new file mode 100644 index 0000000000..61fd2f3625 --- /dev/null +++ b/compat/src/index.js @@ -0,0 +1,238 @@ +import { + createElement, + render as preactRender, + cloneElement as preactCloneElement, + createRef, + Component, + createContext, + Fragment +} from 'preact'; +import { + useState, + useId, + useReducer, + useEffect, + useLayoutEffect, + useRef, + useImperativeHandle, + useMemo, + useCallback, + useContext, + useDebugValue +} from 'preact/hooks'; +import { + useInsertionEffect, + startTransition, + useDeferredValue, + useSyncExternalStore, + useTransition +} from './hooks'; +import { PureComponent } from './PureComponent'; +import { memo } from './memo'; +import { forwardRef } from './forwardRef'; +import { Children } from './Children'; +import { Suspense, lazy } from './suspense'; +import { SuspenseList } from './suspense-list'; +import { createPortal } from './portals'; +import { + hydrate, + render, + REACT_ELEMENT_TYPE, + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED +} from './render'; + +const version = '18.3.1'; // trick libraries to think we are react + +/** + * Legacy version of createElement. + * @param {import('./internal').VNode["type"]} type The node name or Component constructor + */ +function createFactory(type) { + return createElement.bind(null, type); +} + +/** + * Check if the passed element is a valid (p)react node. + * @param {*} element The element to check + * @returns {boolean} + */ +function isValidElement(element) { + return !!element && element.$$typeof === REACT_ELEMENT_TYPE; +} + +/** + * Check if the passed element is a Fragment node. + * @param {*} element The element to check + * @returns {boolean} + */ +function isFragment(element) { + return isValidElement(element) && element.type === Fragment; +} + +/** + * Check if the passed element is a Memo node. + * @param {*} element The element to check + * @returns {boolean} + */ +function isMemo(element) { + return ( + !!element && + !!element.displayName && + (typeof element.displayName === 'string' || + element.displayName instanceof String) && + element.displayName.startsWith('Memo(') + ); +} + +/** + * Wrap `cloneElement` to abort if the passed element is not a valid element and apply + * all vnode normalizations. + * @param {import('./internal').VNode} element The vnode to clone + * @param {object} props Props to add when cloning + * @param {Array} rest Optional component children + */ +function cloneElement(element) { + if (!isValidElement(element)) return element; + return preactCloneElement.apply(null, arguments); +} + +/** + * Remove a component tree from the DOM, including state and event handlers. + * @param {import('./internal').PreactElement} container + * @returns {boolean} + */ +function unmountComponentAtNode(container) { + if (container._children) { + preactRender(null, container); + return true; + } + return false; +} + +/** + * Get the matching DOM node for a component + * @param {import('./internal').Component} component + * @returns {import('./internal').PreactElement | null} + */ +function findDOMNode(component) { + return ( + (component && + (component.base || (component.nodeType === 1 && component))) || + null + ); +} + +/** + * Deprecated way to control batched rendering inside the reconciler, but we + * already schedule in batches inside our rendering code + * @template Arg + * @param {(arg: Arg) => void} callback function that triggers the updated + * @param {Arg} [arg] Optional argument that can be passed to the callback + */ +// eslint-disable-next-line camelcase +const unstable_batchedUpdates = (callback, arg) => callback(arg); + +/** + * In React, `flushSync` flushes the entire tree and forces a rerender. It's + * implmented here as a no-op. + * @template Arg + * @template Result + * @param {(arg: Arg) => Result} callback function that runs before the flush + * @param {Arg} [arg] Optional argument that can be passed to the callback + * @returns + */ +const flushSync = (callback, arg) => callback(arg); + +/** + * Strict Mode is not implemented in Preact, so we provide a stand-in for it + * that just renders its children without imposing any restrictions. + */ +const StrictMode = Fragment; + +// compat to react-is +export const isElement = isValidElement; + +export * from 'preact/hooks'; +export { + version, + Children, + render, + hydrate, + unmountComponentAtNode, + createPortal, + createElement, + createContext, + createFactory, + cloneElement, + createRef, + Fragment, + isValidElement, + isFragment, + isMemo, + findDOMNode, + Component, + PureComponent, + memo, + forwardRef, + flushSync, + useInsertionEffect, + startTransition, + useDeferredValue, + useSyncExternalStore, + useTransition, + // eslint-disable-next-line camelcase + unstable_batchedUpdates, + StrictMode, + Suspense, + SuspenseList, + lazy, + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED +}; + +// React copies the named exports to the default one. +export default { + useState, + useId, + useReducer, + useEffect, + useLayoutEffect, + useInsertionEffect, + useTransition, + useDeferredValue, + useSyncExternalStore, + startTransition, + useRef, + useImperativeHandle, + useMemo, + useCallback, + useContext, + useDebugValue, + version, + Children, + render, + hydrate, + unmountComponentAtNode, + createPortal, + createElement, + createContext, + createFactory, + cloneElement, + createRef, + Fragment, + isValidElement, + isElement, + isFragment, + isMemo, + findDOMNode, + Component, + PureComponent, + memo, + forwardRef, + flushSync, + unstable_batchedUpdates, + StrictMode, + Suspense, + SuspenseList, + lazy, + __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED +}; diff --git a/compat/src/internal.d.ts b/compat/src/internal.d.ts new file mode 100644 index 0000000000..efc5287ca3 --- /dev/null +++ b/compat/src/internal.d.ts @@ -0,0 +1,48 @@ +import { + Component as PreactComponent, + VNode as PreactVNode, + FunctionComponent as PreactFunctionComponent, + PreactElement +} from '../../src/internal'; +import { SuspenseProps } from './suspense'; + +export { ComponentChildren } from '../..'; + +export { PreactElement }; + +export interface Component

extends PreactComponent { + isReactComponent?: object; + isPureReactComponent?: true; + _patchedLifecycles?: true; + + // Suspense internal properties + _childDidSuspend?(error: Promise, suspendingVNode: VNode): void; + _suspended: (vnode: VNode) => (unsuspend: () => void) => void; + _onResolve?(): void; + + // Portal internal properties + _temp: any; + _container: PreactElement; +} + +export interface FunctionComponent

extends PreactFunctionComponent

{ + shouldComponentUpdate?(nextProps: Readonly

): boolean; + _forwarded?: boolean; + _patchedLifecycles?: true; +} + +export interface VNode extends PreactVNode { + $$typeof?: symbol | string; + preactCompatNormalized?: boolean; +} + +export interface SuspenseState { + _suspended?: null | VNode; +} + +export interface SuspenseComponent + extends PreactComponent { + _pendingSuspensionCount: number; + _suspenders: Component[]; + _detachOnNextRender: null | VNode; +} diff --git a/compat/src/memo.js b/compat/src/memo.js new file mode 100644 index 0000000000..e743199055 --- /dev/null +++ b/compat/src/memo.js @@ -0,0 +1,34 @@ +import { createElement } from 'preact'; +import { shallowDiffers } from './util'; + +/** + * Memoize a component, so that it only updates when the props actually have + * changed. This was previously known as `React.pure`. + * @param {import('./internal').FunctionComponent} c functional component + * @param {(prev: object, next: object) => boolean} [comparer] Custom equality function + * @returns {import('./internal').FunctionComponent} + */ +export function memo(c, comparer) { + function shouldUpdate(nextProps) { + let ref = this.props.ref; + let updateRef = ref == nextProps.ref; + if (!updateRef && ref) { + ref.call ? ref(null) : (ref.current = null); + } + + if (!comparer) { + return shallowDiffers(this.props, nextProps); + } + + return !comparer(this.props, nextProps) || !updateRef; + } + + function Memoed(props) { + this.shouldComponentUpdate = shouldUpdate; + return createElement(c, props); + } + Memoed.displayName = 'Memo(' + (c.displayName || c.name) + ')'; + Memoed.prototype.isReactComponent = true; + Memoed._forwarded = true; + return Memoed; +} diff --git a/compat/src/portals.js b/compat/src/portals.js new file mode 100644 index 0000000000..ddbcdbd0dd --- /dev/null +++ b/compat/src/portals.js @@ -0,0 +1,74 @@ +import { createElement, render } from 'preact'; + +/** + * @param {import('../../src/index').RenderableProps<{ context: any }>} props + */ +function ContextProvider(props) { + this.getChildContext = () => props.context; + return props.children; +} + +/** + * Portal component + * @this {import('./internal').Component} + * @param {object | null | undefined} props + * + * TODO: use createRoot() instead of fake root + */ +function Portal(props) { + const _this = this; + let container = props._container; + + _this.componentWillUnmount = function () { + render(null, _this._temp); + _this._temp = null; + _this._container = null; + }; + + // When we change container we should clear our old container and + // indicate a new mount. + if (_this._container && _this._container !== container) { + _this.componentWillUnmount(); + } + + if (!_this._temp) { + _this._container = container; + + // Create a fake DOM parent node that manages a subset of `container`'s children: + _this._temp = { + nodeType: 1, + parentNode: container, + childNodes: [], + contains: () => true, + appendChild(child) { + this.childNodes.push(child); + _this._container.appendChild(child); + }, + insertBefore(child, before) { + this.childNodes.push(child); + _this._container.appendChild(child); + }, + removeChild(child) { + this.childNodes.splice(this.childNodes.indexOf(child) >>> 1, 1); + _this._container.removeChild(child); + } + }; + } + + // Render our wrapping element into temp. + render( + createElement(ContextProvider, { context: _this.context }, props._vnode), + _this._temp + ); +} + +/** + * Create a `Portal` to continue rendering the vnode tree at a different DOM node + * @param {import('./internal').VNode} vnode The vnode to render + * @param {import('./internal').PreactElement} container The DOM node to continue rendering in to. + */ +export function createPortal(vnode, container) { + const el = createElement(Portal, { _vnode: vnode, _container: container }); + el.containerInfo = container; + return el; +} diff --git a/compat/src/render.js b/compat/src/render.js new file mode 100644 index 0000000000..f18cbd896b --- /dev/null +++ b/compat/src/render.js @@ -0,0 +1,313 @@ +import { + render as preactRender, + hydrate as preactHydrate, + options, + toChildArray, + Component +} from 'preact'; +import { + useCallback, + useContext, + useDebugValue, + useEffect, + useId, + useImperativeHandle, + useLayoutEffect, + useMemo, + useReducer, + useRef, + useState +} from 'preact/hooks'; +import { + useDeferredValue, + useInsertionEffect, + useSyncExternalStore, + useTransition +} from './index'; + +export const REACT_ELEMENT_TYPE = + (typeof Symbol != 'undefined' && Symbol.for && Symbol.for('react.element')) || + 0xeac7; + +const CAMEL_PROPS = + /^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/; +const ON_ANI = /^on(Ani|Tra|Tou|BeforeInp|Compo)/; +const CAMEL_REPLACE = /[A-Z0-9]/g; +const IS_DOM = typeof document !== 'undefined'; + +// Input types for which onchange should not be converted to oninput. +// type="file|checkbox|radio", plus "range" in IE11. +// (IE11 doesn't support Symbol, which we use here to turn `rad` into `ra` which matches "range") +const onChangeInputType = type => + (typeof Symbol != 'undefined' && typeof Symbol() == 'symbol' + ? /fil|che|rad/ + : /fil|che|ra/ + ).test(type); + +// Some libraries like `react-virtualized` explicitly check for this. +Component.prototype.isReactComponent = {}; + +// `UNSAFE_*` lifecycle hooks +// Preact only ever invokes the unprefixed methods. +// Here we provide a base "fallback" implementation that calls any defined UNSAFE_ prefixed method. +// - If a component defines its own `componentDidMount()` (including via defineProperty), use that. +// - If a component defines `UNSAFE_componentDidMount()`, `componentDidMount` is the alias getter/setter. +// - If anything assigns to an `UNSAFE_*` property, the assignment is forwarded to the unprefixed property. +// See https://github.com/preactjs/preact/issues/1941 +[ + 'componentWillMount', + 'componentWillReceiveProps', + 'componentWillUpdate' +].forEach(key => { + Object.defineProperty(Component.prototype, key, { + configurable: true, + get() { + return this['UNSAFE_' + key]; + }, + set(v) { + Object.defineProperty(this, key, { + configurable: true, + writable: true, + value: v + }); + } + }); +}); + +/** + * Proxy render() since React returns a Component reference. + * @param {import('./internal').VNode} vnode VNode tree to render + * @param {import('./internal').PreactElement} parent DOM node to render vnode tree into + * @param {() => void} [callback] Optional callback that will be called after rendering + * @returns {import('./internal').Component | null} The root component reference or null + */ +export function render(vnode, parent, callback) { + // React destroys any existing DOM nodes, see #1727 + // ...but only on the first render, see #1828 + if (parent._children == null) { + parent.textContent = ''; + } + + preactRender(vnode, parent); + if (typeof callback == 'function') callback(); + + return vnode ? vnode._component : null; +} + +export function hydrate(vnode, parent, callback) { + preactHydrate(vnode, parent); + if (typeof callback == 'function') callback(); + + return vnode ? vnode._component : null; +} + +let oldEventHook = options.event; +options.event = e => { + if (oldEventHook) e = oldEventHook(e); + + e.persist = empty; + e.isPropagationStopped = isPropagationStopped; + e.isDefaultPrevented = isDefaultPrevented; + return (e.nativeEvent = e); +}; + +function empty() {} + +function isPropagationStopped() { + return this.cancelBubble; +} + +function isDefaultPrevented() { + return this.defaultPrevented; +} + +const classNameDescriptorNonEnumberable = { + enumerable: false, + configurable: true, + get() { + return this.class; + } +}; + +function handleDomVNode(vnode) { + let props = vnode.props, + type = vnode.type, + normalizedProps = {}; + + let isNonDashedType = type.indexOf('-') === -1; + for (let i in props) { + let value = props[i]; + + if ( + (i === 'value' && 'defaultValue' in props && value == null) || + // Emulate React's behavior of not rendering the contents of noscript tags on the client. + (IS_DOM && i === 'children' && type === 'noscript') || + i === 'class' || + i === 'className' + ) { + // Skip applying value if it is null/undefined and we already set + // a default value + continue; + } + + let lowerCased = i.toLowerCase(); + if (i === 'defaultValue' && 'value' in props && props.value == null) { + // `defaultValue` is treated as a fallback `value` when a value prop is present but null/undefined. + // `defaultValue` for Elements with no value prop is the same as the DOM defaultValue property. + i = 'value'; + } else if (i === 'download' && value === true) { + // Calling `setAttribute` with a truthy value will lead to it being + // passed as a stringified value, e.g. `download="true"`. React + // converts it to an empty string instead, otherwise the attribute + // value will be used as the file name and the file will be called + // "true" upon downloading it. + value = ''; + } else if (lowerCased === 'translate' && value === 'no') { + value = false; + } else if (lowerCased[0] === 'o' && lowerCased[1] === 'n') { + if (lowerCased === 'ondoubleclick') { + i = 'ondblclick'; + } else if ( + lowerCased === 'onchange' && + (type === 'input' || type === 'textarea') && + !onChangeInputType(props.type) + ) { + lowerCased = i = 'oninput'; + } else if (lowerCased === 'onfocus') { + i = 'onfocusin'; + } else if (lowerCased === 'onblur') { + i = 'onfocusout'; + } else if (ON_ANI.test(i)) { + i = lowerCased; + } + } else if (isNonDashedType && CAMEL_PROPS.test(i)) { + i = i.replace(CAMEL_REPLACE, '-$&').toLowerCase(); + } else if (value === null) { + value = undefined; + } + + // Add support for onInput and onChange, see #3561 + // if we have an oninput prop already change it to oninputCapture + if (lowerCased === 'oninput') { + i = lowerCased; + if (normalizedProps[i]) { + i = 'oninputCapture'; + } + } + + normalizedProps[i] = value; + } + + // Add support for array select values: '); + + hydrate(, scratch); + expect(scratch.firstElementChild.value).to.equal('foo'); + + // IE11 always displays the value as node.innerHTML + if (!/Trident/.test(window.navigator.userAgent)) { + expect(scratch.innerHTML).to.be.equal(''); + } + }); + + it('should alias defaultValue to children', () => { + // TODO: IE11 doesn't update `node.value` when + // `node.defaultValue` is set. + if (/Trident/.test(navigator.userAgent)) return; + + render('); + + act(() => { + set('hello'); + }); + // Note: This looks counterintuitive, but it's working correctly - the value + // missing from HTML because innerHTML doesn't serialize form field values. + // See demo: https://jsfiddle.net/4had2Lu8 + // Related renderToString PR: preactjs/preact-render-to-string#161 + // + // This is not true for IE11. It displays the value in + // node.innerHTML regardless. + if (!/Trident/.test(window.navigator.userAgent)) { + expect(scratch.innerHTML).to.equal(''); + } + expect(scratch.firstElementChild.value).to.equal('hello'); + + act(() => { + set(''); + }); + // Same as earlier: IE11 always displays the value as node.innerHTML + if (!/Trident/.test(window.navigator.userAgent)) { + expect(scratch.innerHTML).to.equal(''); + } + expect(scratch.firstElementChild.value).to.equal(''); + }); +}); diff --git a/compat/test/browser/unmountComponentAtNode.test.js b/compat/test/browser/unmountComponentAtNode.test.js new file mode 100644 index 0000000000..bcf87df0ef --- /dev/null +++ b/compat/test/browser/unmountComponentAtNode.test.js @@ -0,0 +1,28 @@ +import React, { createElement, unmountComponentAtNode } from 'preact/compat'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; + +describe('unmountComponentAtNode', () => { + /** @type {HTMLDivElement} */ + let scratch; + + beforeEach(() => { + scratch = setupScratch(); + }); + + afterEach(() => { + teardown(scratch); + }); + + it('should unmount a root node', () => { + const App = () =>

foo
; + React.render(, scratch); + + expect(unmountComponentAtNode(scratch)).to.equal(true); + expect(scratch.innerHTML).to.equal(''); + }); + + it('should do nothing if root is not mounted', () => { + expect(unmountComponentAtNode(scratch)).to.equal(false); + expect(scratch.innerHTML).to.equal(''); + }); +}); diff --git a/compat/test/browser/unstable_batchedUpdates.test.js b/compat/test/browser/unstable_batchedUpdates.test.js new file mode 100644 index 0000000000..bef738484b --- /dev/null +++ b/compat/test/browser/unstable_batchedUpdates.test.js @@ -0,0 +1,33 @@ +import { unstable_batchedUpdates, flushSync } from 'preact/compat'; + +describe('unstable_batchedUpdates', () => { + it('should call the callback', () => { + const spy = sinon.spy(); + unstable_batchedUpdates(spy); + expect(spy).to.be.calledOnce; + }); + + it('should call callback with only one arg', () => { + const spy = sinon.spy(); + unstable_batchedUpdates(spy, 'foo', 'bar'); + expect(spy).to.be.calledWithExactly('foo'); + }); +}); + +describe('flushSync', () => { + it('should invoke the given callback', () => { + const returnValue = {}; + const spy = sinon.spy(() => returnValue); + const result = flushSync(spy); + expect(spy).to.have.been.calledOnce; + expect(result).to.equal(returnValue); + }); + + it('should invoke the given callback with the given argument', () => { + const returnValue = {}; + const spy = sinon.spy(() => returnValue); + const result = flushSync(spy, 'foo'); + expect(spy).to.be.calledWithExactly('foo'); + expect(result).to.equal(returnValue); + }); +}); diff --git a/compat/test/browser/useSyncExternalStore.test.js b/compat/test/browser/useSyncExternalStore.test.js new file mode 100644 index 0000000000..2f5ab16a03 --- /dev/null +++ b/compat/test/browser/useSyncExternalStore.test.js @@ -0,0 +1,810 @@ +import React, { + createElement, + Fragment, + useSyncExternalStore, + render, + useState, + useCallback, + useEffect, + useLayoutEffect +} from 'preact/compat'; +import { setupRerender, act } from 'preact/test-utils'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; + +const ReactDOM = React; + +describe('useSyncExternalStore', () => { + /** @type {HTMLDivElement} */ + let scratch; + + /** @type {() => void} */ + let rerender; + + /** @type {{ logs: string[], log(arg: string): void; }} */ + const Scheduler = { + logs: [], + log(arg) { + this.logs.push(arg); + } + }; + + beforeEach(() => { + scratch = setupScratch(); + rerender = setupRerender(); + }); + + afterEach(() => { + teardown(scratch); + Scheduler.logs = []; + }); + + function defer(cb) { + return Promise.resolve().then(cb); + } + + function assertLog(expected) { + expect(Scheduler.logs).to.deep.equal(expected); + Scheduler.logs = []; + } + + function Text({ text }) { + Scheduler.log(text); + return text; + } + + /** @type {(container: Element) => { render(children: React.JSX.Element): void}} */ + function createRoot(container) { + return { + render(children) { + render(children, container); + } + }; + } + + function createExternalStore(initialState) { + const listeners = new Set(); + let currentState = initialState; + return { + listeners, + set(text) { + currentState = text; + listeners.forEach(listener => listener()); + }, + subscribe(listener) { + listeners.add(listener); + return () => listeners.delete(listener); + }, + getState() { + return currentState; + }, + getSubscriberCount() { + return listeners.size; + } + }; + } + + it('subscribes and follows effects', () => { + const subscribe = sinon.spy(() => () => {}); + const getSnapshot = sinon.spy(() => 'hello world'); + + const App = () => { + const value = useSyncExternalStore(subscribe, getSnapshot); + return

{value}

; + }; + + act(() => { + render(, scratch); + }); + expect(scratch.innerHTML).to.equal('

hello world

'); + expect(subscribe).to.be.calledOnce; + expect(getSnapshot).to.be.calledThrice; + }); + + it('subscribes and rerenders when called', () => { + /** @type {() => void} */ + let flush; + const subscribe = sinon.spy(cb => { + flush = cb; + return () => {}; + }); + let called = false; + const getSnapshot = sinon.spy(() => { + if (called) { + return 'hello new world'; + } + + return 'hello world'; + }); + + const App = () => { + const value = useSyncExternalStore(subscribe, getSnapshot); + return

{value}

; + }; + + act(() => { + render(, scratch); + }); + expect(scratch.innerHTML).to.equal('

hello world

'); + expect(subscribe).to.be.calledOnce; + expect(getSnapshot).to.be.calledThrice; + + called = true; + flush(); + rerender(); + + expect(scratch.innerHTML).to.equal('

hello new world

'); + }); + + it('getSnapshot can return NaN without causing infinite loop', () => { + /** @type {() => void} */ + let flush; + const subscribe = sinon.spy(cb => { + flush = cb; + return () => {}; + }); + let called = false; + const getSnapshot = sinon.spy(() => { + if (called) { + return NaN; + } + + return 1; + }); + + const App = () => { + const value = useSyncExternalStore(subscribe, getSnapshot); + return

{value}

; + }; + + act(() => { + render(, scratch); + }); + expect(scratch.innerHTML).to.equal('

1

'); + expect(subscribe).to.be.calledOnce; + expect(getSnapshot).to.be.calledThrice; + + called = true; + flush(); + rerender(); + + expect(scratch.innerHTML).to.equal('

NaN

'); + }); + + it('should not call function values on subscription', () => { + /** @type {() => void} */ + let flush; + const subscribe = sinon.spy(cb => { + flush = cb; + return () => {}; + }); + + const func = () => 'value: ' + i++; + + let i = 0; + const getSnapshot = sinon.spy(() => { + return func; + }); + + const App = () => { + const value = useSyncExternalStore(subscribe, getSnapshot); + return

{value()}

; + }; + + act(() => { + render(, scratch); + }); + expect(scratch.innerHTML).to.equal('

value: 0

'); + expect(subscribe).to.be.calledOnce; + expect(getSnapshot).to.be.calledThrice; + + flush(); + rerender(); + + expect(scratch.innerHTML).to.equal('

value: 0

'); + }); + + it('should work with changing getSnapshot', () => { + /** @type {() => void} */ + let flush; + const subscribe = sinon.spy(cb => { + flush = cb; + return () => {}; + }); + + let i = 0; + const App = () => { + const value = useSyncExternalStore(subscribe, () => { + return i; + }); + return

value: {value}

; + }; + + act(() => { + render(, scratch); + }); + expect(scratch.innerHTML).to.equal('

value: 0

'); + expect(subscribe).to.be.calledOnce; + + i++; + flush(); + rerender(); + + expect(scratch.innerHTML).to.equal('

value: 1

'); + }); + + it('works with useCallback', () => { + /** @type {() => void} */ + let toggle; + const App = () => { + const [state, setState] = useState(true); + toggle = setState.bind(this, () => false); + + const value = useSyncExternalStore( + useCallback(() => { + return () => {}; + }, [state]), + () => (state ? 'yep' : 'nope') + ); + + return

{value}

; + }; + + act(() => { + render(, scratch); + }); + expect(scratch.innerHTML).to.equal('

yep

'); + + toggle(); + rerender(); + + expect(scratch.innerHTML).to.equal('

nope

'); + }); + + it('handles store updates before subscribing', async () => { + // This test is testing scheduling mechanics, so teardown the manual + // rerender test setup to rely on Preact's built-in scheduling and verify + // this behavior works. We still need a DOM container to render into so set + // that back up. + teardown(scratch); + scratch = setupScratch(); + + const store = createExternalStore(0); + + function App() { + const value = useSyncExternalStore(store.subscribe, store.getState); + useEffect(() => { + Scheduler.log('Passive effect: ' + value); + }, [value]); + return ; + } + + const container = document.createElement('div'); + const root = createRoot(container); + + // Schedule a mutation in the next microtask after the initial render but + // before subscribing to the store + const mutation = defer(() => { + // Assert we are running this mutation before subscribing to the store + expect(store.listeners.size).to.equal(0); + store.set(1); + }); + + root.render(); + expect(container.textContent).to.equal('0'); + assertLog([0]); + + // Wait for the mutation to occur. Then wait for the passive effects that + // subscribe to the store and log the new value. + await mutation; + await new Promise(r => setTimeout(r, 32)); + + expect(container.textContent).to.equal('1'); + expect(store.listeners.size).to.equal(1); + assertLog(['Passive effect: 0', 1, 'Passive effect: 1']); + }); + + // The following tests are taken from the React test suite: + // https://github.com/facebook/react/blob/3e09c27b880e1fecdb1eca5db510ecce37ea6be2/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js + describe('React useSyncExternalStore test suite', () => { + it('basic usage', async () => { + const store = createExternalStore('Initial'); + + function App() { + const text = useSyncExternalStore(store.subscribe, store.getState); + return ; + } + + const container = document.createElement('div'); + const root = createRoot(container); + await act(() => root.render()); + + assertLog(['Initial']); + expect(container.textContent).to.equal('Initial'); + + await act(() => { + store.set('Updated'); + }); + assertLog(['Updated']); + expect(container.textContent).to.equal('Updated'); + }); + + it('skips re-rendering if nothing changes', async () => { + const store = createExternalStore('Initial'); + + function App() { + const text = useSyncExternalStore(store.subscribe, store.getState); + return ; + } + + const container = document.createElement('div'); + const root = createRoot(container); + await act(() => root.render()); + + assertLog(['Initial']); + expect(container.textContent).to.equal('Initial'); + + // Update to the same value + await act(() => { + store.set('Initial'); + }); + // Should not re-render + assertLog([]); + expect(container.textContent).to.equal('Initial'); + }); + + it('switch to a different store', async () => { + const storeA = createExternalStore(0); + const storeB = createExternalStore(0); + + let setStore; + function App() { + const [store, _setStore] = useState(storeA); + setStore = _setStore; + const value = useSyncExternalStore(store.subscribe, store.getState); + return ; + } + + const container = document.createElement('div'); + const root = createRoot(container); + await act(() => root.render()); + + assertLog([0]); + expect(container.textContent).to.equal('0'); + + await act(() => { + storeA.set(1); + }); + assertLog([1]); + expect(container.textContent).to.equal('1'); + + // Switch stores and update in the same batch + await act(() => { + ReactDOM.flushSync(() => { + // This update will be disregarded + storeA.set(2); + setStore(storeB); + }); + }); + // Now reading from B instead of A + assertLog([0]); + expect(container.textContent).to.equal('0'); + + // Update A + await act(() => { + storeA.set(3); + }); + // Nothing happened, because we're no longer subscribed to A + assertLog([]); + expect(container.textContent).to.equal('0'); + + // Update B + await act(() => { + storeB.set(1); + }); + assertLog([1]); + expect(container.textContent).to.equal('1'); + }); + + it('selecting a specific value inside getSnapshot', async () => { + const store = createExternalStore({ a: 0, b: 0 }); + + function A() { + const a = useSyncExternalStore( + store.subscribe, + () => store.getState().a + ); + return ; + } + function B() { + const b = useSyncExternalStore( + store.subscribe, + () => store.getState().b + ); + return ; + } + + function App() { + return ( + + + + + ); + } + + const container = document.createElement('div'); + const root = createRoot(container); + await act(() => root.render()); + + assertLog(['A0', 'B0']); + expect(container.textContent).to.equal('A0B0'); + + // Update b but not a + await act(() => { + store.set({ a: 0, b: 1 }); + }); + // Only b re-renders + assertLog(['B1']); + expect(container.textContent).to.equal('A0B1'); + + // Update a but not b + await act(() => { + store.set({ a: 1, b: 1 }); + }); + // Only a re-renders + assertLog(['A1']); + expect(container.textContent).to.equal('A1B1'); + }); + + // In React 18, you can't observe in between a sync render and its + // passive effects, so this is only relevant to legacy roots + // @gate enableUseSyncExternalStoreShim + it("compares to current state before bailing out, even when there's a mutation in between the sync and passive effects", async () => { + const store = createExternalStore(0); + + function App() { + const value = useSyncExternalStore(store.subscribe, store.getState); + useEffect(() => { + Scheduler.log('Passive effect: ' + value); + }, [value]); + return ; + } + + const container = document.createElement('div'); + const root = createRoot(container); + await act(() => root.render()); + assertLog([0, 'Passive effect: 0']); + + // Schedule an update. We'll intentionally not use `act` so that we can + // insert a mutation before React subscribes to the store in a + // passive effect. + store.set(1); + rerender(); + assertLog([ + 1 + // Passive effect hasn't fired yet + ]); + expect(container.textContent).to.equal('1'); + + // Flip the store state back to the previous value. + store.set(0); + rerender(); + assertLog([ + 'Passive effect: 1', + // Re-render. If the current state were tracked by updating a ref in a + // passive effect, then this would break because the previous render's + // passive effect hasn't fired yet, so we'd incorrectly think that + // the state hasn't changed. + 0 + ]); + // Should flip back to 0 + expect(container.textContent).to.equal('0'); + + // Preact: Wait for 'Passive effect: 0' to flush from the rAF so it doesn't impact other tests + await new Promise(r => setTimeout(r, 32)); + }); + + it('mutating the store in between render and commit when getSnapshot has changed', async () => { + const store = createExternalStore({ a: 1, b: 1 }); + + const getSnapshotA = () => store.getState().a; + const getSnapshotB = () => store.getState().b; + + function Child1({ step }) { + const value = useSyncExternalStore(store.subscribe, store.getState); + useLayoutEffect(() => { + if (step === 1) { + // Update B in a layout effect. This happens in the same commit + // that changed the getSnapshot in Child2. Child2's effects haven't + // fired yet, so it doesn't have access to the latest getSnapshot. So + // it can't use the getSnapshot to bail out. + Scheduler.log('Update B in commit phase'); + store.set({ a: value.a, b: 2 }); + } + }, [step]); + return null; + } + + function Child2({ step }) { + const label = step === 0 ? 'A' : 'B'; + const getSnapshot = step === 0 ? getSnapshotA : getSnapshotB; + const value = useSyncExternalStore(store.subscribe, getSnapshot); + return ; + } + + let setStep; + function App() { + const [step, _setStep] = useState(0); + setStep = _setStep; + return ( + <> + + + + ); + } + + const container = document.createElement('div'); + const root = createRoot(container); + await act(() => root.render()); + assertLog(['A1']); + expect(container.textContent).to.equal('A1'); + + await act(() => { + // Change getSnapshot and update the store in the same batch + setStep(1); + }); + assertLog([ + 'B1', + 'Update B in commit phase', + // If Child2 had used the old getSnapshot to bail out, then it would have + // incorrectly bailed out here instead of re-rendering. + 'B2' + ]); + expect(container.textContent).to.equal('B2'); + }); + + it('mutating the store in between render and commit when getSnapshot has _not_ changed', async () => { + // Same as previous test, but `getSnapshot` does not change + const store = createExternalStore({ a: 1, b: 1 }); + + const getSnapshotA = () => store.getState().a; + + function Child1({ step }) { + const value = useSyncExternalStore(store.subscribe, store.getState); + useLayoutEffect(() => { + if (step === 1) { + // Update B in a layout effect. This happens in the same commit + // that changed the getSnapshot in Child2. Child2's effects haven't + // fired yet, so it doesn't have access to the latest getSnapshot. So + // it can't use the getSnapshot to bail out. + Scheduler.log('Update B in commit phase'); + store.set({ a: value.a, b: 2 }); + } + }, [step]); + return null; + } + + function Child2({ step }) { + const value = useSyncExternalStore(store.subscribe, getSnapshotA); + return ; + } + + let setStep; + function App() { + const [step, _setStep] = useState(0); + setStep = _setStep; + return ( + <> + + + + ); + } + + const container = document.createElement('div'); + const root = createRoot(container); + await act(() => root.render()); + assertLog(['A1']); + expect(container.textContent).to.equal('A1'); + + // This will cause a layout effect, and in the layout effect we'll update + // the store + await act(() => { + setStep(1); + }); + assertLog([ + 'A1', + // This updates B, but since Child2 doesn't subscribe to B, it doesn't + // need to re-render. + 'Update B in commit phase' + // No re-render + ]); + expect(container.textContent).to.equal('A1'); + }); + + it("does not bail out if the previous update hasn't finished yet", async () => { + const store = createExternalStore(0); + + function Child1() { + const value = useSyncExternalStore(store.subscribe, store.getState); + useLayoutEffect(() => { + if (value === 1) { + Scheduler.log('Reset back to 0'); + store.set(0); + } + }, [value]); + return ; + } + + function Child2() { + const value = useSyncExternalStore(store.subscribe, store.getState); + return ; + } + + const container = document.createElement('div'); + const root = createRoot(container); + await act(() => + root.render( + <> + + + + ) + ); + assertLog([0, 0]); + expect(container.textContent).to.equal('00'); + + await act(() => { + store.set(1); + }); + // Preact logs differ from React here cuz of how we do rerendering. We + // rerender subtrees and then commit effects so Child2 never sees the + // update to 1 cuz Child1 rerenders and runs its layout effects first. + assertLog([1, /*1,*/ 'Reset back to 0', 0, 0]); + expect(container.textContent).to.equal('00'); + }); + + it('uses the latest getSnapshot, even if it changed in the same batch as a store update', async () => { + const store = createExternalStore({ a: 0, b: 0 }); + + const getSnapshotA = () => store.getState().a; + const getSnapshotB = () => store.getState().b; + + let setGetSnapshot; + function App() { + const [getSnapshot, _setGetSnapshot] = useState(() => getSnapshotA); + setGetSnapshot = _setGetSnapshot; + const text = useSyncExternalStore(store.subscribe, getSnapshot); + return ; + } + + const container = document.createElement('div'); + const root = createRoot(container); + await act(() => root.render()); + assertLog([0]); + + // Update the store and getSnapshot at the same time + await act(() => { + ReactDOM.flushSync(() => { + setGetSnapshot(() => getSnapshotB); + store.set({ a: 1, b: 2 }); + }); + }); + // It should read from B instead of A + assertLog([2]); + expect(container.textContent).to.equal('2'); + }); + + it('handles errors thrown by getSnapshot', async () => { + class ErrorBoundary extends React.Component { + state = { error: null }; + static getDerivedStateFromError(error) { + return { error }; + } + render() { + if (this.state.error) { + return ; + } + return this.props.children; + } + } + + const store = createExternalStore({ + value: 0, + throwInGetSnapshot: false, + throwInIsEqual: false + }); + + function App() { + const { value } = useSyncExternalStore(store.subscribe, () => { + const state = store.getState(); + if (state.throwInGetSnapshot) { + throw new Error('Error in getSnapshot'); + } + return state; + }); + return ; + } + + const errorBoundary = React.createRef(); + const container = document.createElement('div'); + const root = createRoot(container); + await act(() => + root.render( + + + + ) + ); + assertLog([0]); + expect(container.textContent).to.equal('0'); + + // Update that throws in a getSnapshot. We can catch it with an error boundary. + await act(() => { + store.set({ + value: 1, + throwInGetSnapshot: true, + throwInIsEqual: false + }); + }); + + assertLog(['Error in getSnapshot']); + expect(container.textContent).to.equal('Error in getSnapshot'); + }); + + it('getSnapshot can return NaN without infinite loop warning', async () => { + const store = createExternalStore('not a number'); + + function App() { + const value = useSyncExternalStore(store.subscribe, () => + parseInt(store.getState(), 10) + ); + return ; + } + + const container = document.createElement('div'); + const root = createRoot(container); + + // Initial render that reads a snapshot of NaN. This is OK because we use + // Object.is algorithm to compare values. + await act(() => root.render()); + expect(container.textContent).to.equal('NaN'); + + // Update to real number + await act(() => store.set(123)); + expect(container.textContent).to.equal('123'); + + // Update back to NaN + await act(() => store.set('not a number')); + expect(container.textContent).to.equal('NaN'); + }); + + it('regression test for facebook/react#23150', async () => { + const store = createExternalStore('Initial'); + + function App() { + const text = useSyncExternalStore(store.subscribe, store.getState); + const [derivedText, setDerivedText] = useState(text); + useEffect(() => {}, []); + if (derivedText !== text.toUpperCase()) { + setDerivedText(text.toUpperCase()); + } + return ; + } + + const container = document.createElement('div'); + const root = createRoot(container); + await act(() => root.render()); + + assertLog(['INITIAL']); + expect(container.textContent).to.equal('INITIAL'); + + await act(() => { + store.set('Updated'); + }); + assertLog(['UPDATED']); + expect(container.textContent).to.equal('UPDATED'); + }); + }); +}); diff --git a/compat/test/ts/forward-ref.tsx b/compat/test/ts/forward-ref.tsx new file mode 100644 index 0000000000..25d0694079 --- /dev/null +++ b/compat/test/ts/forward-ref.tsx @@ -0,0 +1,30 @@ +import React from '../../src'; + +const MyInput: React.ForwardFn<{ id: string }, { focus(): void }> = ( + props, + ref +) => { + const inputRef = React.useRef(null); + + React.useImperativeHandle(ref, () => ({ + focus: () => { + if (inputRef.current) { + inputRef.current.focus(); + } + } + })); + + return ; +}; + +export const foo = React.forwardRef(MyInput); + +export const Bar = React.forwardRef( + (props, ref) => { + return
{props.children}
; + } +); + +export const baz = ( + ref: React.ForwardedRef +): React.Ref => ref; diff --git a/compat/test/ts/index.tsx b/compat/test/ts/index.tsx new file mode 100644 index 0000000000..f636ce6d5f --- /dev/null +++ b/compat/test/ts/index.tsx @@ -0,0 +1,17 @@ +import React from '../../src'; + +React.render(
, document.createElement('div')); +React.render(
, document.createDocumentFragment()); +React.render(
, document.body.shadowRoot!); + +React.hydrate(
, document.createElement('div')); +React.hydrate(
, document.createDocumentFragment()); +React.hydrate(
, document.body.shadowRoot!); + +React.unmountComponentAtNode(document.createElement('div')); +React.unmountComponentAtNode(document.createDocumentFragment()); +React.unmountComponentAtNode(document.body.shadowRoot!); + +React.createPortal(
, document.createElement('div')); +React.createPortal(
, document.createDocumentFragment()); +React.createPortal(
, document.body.shadowRoot!); diff --git a/compat/test/ts/lazy.tsx b/compat/test/ts/lazy.tsx new file mode 100644 index 0000000000..b3797c19a2 --- /dev/null +++ b/compat/test/ts/lazy.tsx @@ -0,0 +1,17 @@ +import * as React from '../../src'; + +export interface LazyProps { + isProp: boolean; +} + +interface LazyState { + forState: string; +} +export default class IsLazyComponent extends React.Component< + LazyProps, + LazyState +> { + render({ isProp }: LazyProps) { + return
{isProp ? 'Super Lazy TRUE' : 'Super Lazy FALSE'}
; + } +} diff --git a/compat/test/ts/memo.tsx b/compat/test/ts/memo.tsx new file mode 100644 index 0000000000..4e89e2687f --- /dev/null +++ b/compat/test/ts/memo.tsx @@ -0,0 +1,56 @@ +import * as React from '../../src'; +import { expectType } from './utils'; + +interface MemoProps { + required: string; + optional?: string; + defaulted: string; +} + +interface MemoPropsExceptDefaults { + required: string; + optional?: string; +} + +const ComponentExceptDefaults = () =>
; + +const ReadonlyBaseComponent = (props: Readonly) => ( +
{props.required + props.optional + props.defaulted}
+); +ReadonlyBaseComponent.defaultProps = { defaulted: '' }; + +const BaseComponent = (props: MemoProps) => ( +
{props.required + props.optional + props.defaulted}
+); +BaseComponent.defaultProps = { defaulted: '' }; + +// memo for readonly component with default comparison +const MemoedReadonlyComponent = React.memo(ReadonlyBaseComponent); +expectType>(MemoedReadonlyComponent); +export const memoedReadonlyComponent = ( + +); + +// memo for non-readonly component with default comparison +const MemoedComponent = React.memo(BaseComponent); +expectType>(MemoedComponent); +export const memoedComponent = ; + +// memo with custom comparison +const CustomMemoedComponent = React.memo(BaseComponent, (a, b) => { + expectType(a); + expectType(b); + return a.required === b.required; +}); +expectType>(CustomMemoedComponent); +export const customMemoedComponent = ; + +const MemoedComponentExceptDefaults = React.memo( + ComponentExceptDefaults +); +expectType>( + MemoedComponentExceptDefaults +); +export const memoedComponentExceptDefaults = ( + +); diff --git a/compat/test/ts/react-default.tsx b/compat/test/ts/react-default.tsx new file mode 100644 index 0000000000..f46c0b4e0c --- /dev/null +++ b/compat/test/ts/react-default.tsx @@ -0,0 +1,6 @@ +import React from '../../src'; +class ReactIsh extends React.Component { + render() { + return
Text
; + } +} diff --git a/compat/test/ts/react-star.tsx b/compat/test/ts/react-star.tsx new file mode 100644 index 0000000000..da826906c2 --- /dev/null +++ b/compat/test/ts/react-star.tsx @@ -0,0 +1,7 @@ +// import React from '../../src'; +import * as React from '../../src'; +class ReactIsh extends React.Component { + render() { + return
Text
; + } +} diff --git a/compat/test/ts/scheduler.ts b/compat/test/ts/scheduler.ts new file mode 100644 index 0000000000..999e652963 --- /dev/null +++ b/compat/test/ts/scheduler.ts @@ -0,0 +1,19 @@ +import { + unstable_runWithPriority, + unstable_NormalPriority, + unstable_LowPriority, + unstable_IdlePriority, + unstable_UserBlockingPriority, + unstable_ImmediatePriority, + unstable_now +} from '../../src'; + +const noop = () => null; +unstable_runWithPriority(unstable_IdlePriority, noop); +unstable_runWithPriority(unstable_LowPriority, noop); +unstable_runWithPriority(unstable_NormalPriority, noop); +unstable_runWithPriority(unstable_UserBlockingPriority, noop); +unstable_runWithPriority(unstable_ImmediatePriority, noop); + +if (typeof unstable_now() === 'number') { +} diff --git a/compat/test/ts/suspense.tsx b/compat/test/ts/suspense.tsx new file mode 100644 index 0000000000..c082f54663 --- /dev/null +++ b/compat/test/ts/suspense.tsx @@ -0,0 +1,67 @@ +import * as React from '../../src'; + +interface LazyProps { + isProp: boolean; +} + +const IsLazyFunctional = (props: LazyProps) => ( +
{props.isProp ? 'Super Lazy TRUE' : 'Super Lazy FALSE'}
+); + +const FallBack = () =>
Still working...
; +/** + * Have to mock dynamic import as import() throws a syntax error in the test runner + */ +const componentPromise = new Promise<{ default: typeof IsLazyFunctional }>( + resolve => { + setTimeout(() => { + resolve({ default: IsLazyFunctional }); + }, 800); + } +); + +/** + * For usage with import: + * const IsLazyComp = lazy(() => import('./lazy')); + */ +const IsLazyFunc = React.lazy(() => componentPromise); + +// Suspense using lazy component +class ReactSuspensefulFunc extends React.Component { + render() { + return ( + }> + + + ); + } +} + +//SuspenseList using lazy components +function ReactSuspenseListTester(_props: any) { + return ( + + }> + + + }> + + + + ); +} + +const Comp = () =>

Hello world

; + +const importComponent = async () => { + return { MyComponent: Comp }; +}; + +const Lazy = React.lazy(() => + importComponent().then(mod => ({ default: mod.MyComponent })) +); + +// eslint-disable-next-line +function App() { + return ; +} diff --git a/compat/test/ts/tsconfig.json b/compat/test/ts/tsconfig.json new file mode 100644 index 0000000000..75fc121a5d --- /dev/null +++ b/compat/test/ts/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "lib": ["es6", "dom"], + "strict": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react", + "noEmit": true, + "allowSyntheticDefaultImports": true + }, + "include": ["./**/*.ts", "./**/*.tsx"] +} diff --git a/compat/test/ts/utils.ts b/compat/test/ts/utils.ts new file mode 100644 index 0000000000..16ec22d34b --- /dev/null +++ b/compat/test/ts/utils.ts @@ -0,0 +1,4 @@ +/** + * Assert the parameter is of a specific type. + */ +export const expectType = (_: T): void => undefined; diff --git a/config/codemod-const.js b/config/codemod-const.js index 5d27f93eab..37eee5b769 100644 --- a/config/codemod-const.js +++ b/config/codemod-const.js @@ -9,14 +9,15 @@ export default (file, api) => { constants = {}, found = 0; - code.find(j.VariableDeclaration) - .filter( decl => { - for (let i=decl.value.declarations.length; i--; ) { + code + .find(j.VariableDeclaration) + .filter(decl => { + for (let i = decl.value.declarations.length; i--; ) { let node = decl.value.declarations[i], name = node.id && node.id.name, init = node.init; if (name && init && name.match(/^[A-Z0-9_$]+$/g) && !init.regex) { - if (init.type==='Literal') { + if (init.type === 'Literal') { // console.log(`Inlining constant: ${name}=${init.raw}`); found++; constants[name] = init; @@ -31,9 +32,12 @@ export default (file, api) => { }) .remove(); - code.find(j.Identifier) - .filter( path => path.value.name && constants.hasOwnProperty(path.value.name) ) - .replaceWith( path => (found++, constants[path.value.name]) ); + code + .find(j.Identifier) + .filter( + path => path.value.name && constants.hasOwnProperty(path.value.name) + ) + .replaceWith(path => (found++, constants[path.value.name])); return found ? code.toSource({ quote: 'single' }) : null; }; diff --git a/config/codemod-let-name.js b/config/codemod-let-name.js index 782cc99d76..5f69411ed4 100644 --- a/config/codemod-let-name.js +++ b/config/codemod-let-name.js @@ -6,7 +6,10 @@ export default (file, api) => { let code = j(file.source); // @TODO unsafe, but without it we gain 20b gzipped: https://www.diffchecker.com/bVrOJWTO - code.findVariableDeclarators().filter(d => /^_i/.test(d.value.id.name)).renameTo('i'); + code + .findVariableDeclarators() + .filter(d => /^_i/.test(d.value.id.name)) + .renameTo('i'); code.findVariableDeclarators('_key').renameTo('key'); return code.toSource({ quote: 'single' }); diff --git a/config/codemod-strip-tdz.js b/config/codemod-strip-tdz.js index a1f07fafe2..469c9c32a5 100644 --- a/config/codemod-strip-tdz.js +++ b/config/codemod-strip-tdz.js @@ -1,6 +1,5 @@ /* eslint no-console:0 */ - // parent node types that we don't want to remove pointless initializations from (because it breaks hoisting) const BLOCKED = ['ForStatement', 'WhileStatement']; // 'IfStatement', 'SwitchStatement' @@ -24,11 +23,12 @@ export default (file, api) => { } } - decl.value.declarations.filter(isPointless).forEach( node => { - if (remove===false) { - console.log(`> Skipping removal of undefined init for "${node.id.name}": within ${p.value.type}`); - } - else { + decl.value.declarations.filter(isPointless).forEach(node => { + if (remove === false) { + console.log( + `> Skipping removal of undefined init for "${node.id.name}": within ${p.value.type}` + ); + } else { removeNodeInitialization(node); } }); @@ -42,10 +42,14 @@ export default (file, api) => { function isPointless(node) { let { init } = node; if (init) { - if (init.type==='UnaryExpression' && init.operator==='void' && init.argument.value==0) { + if ( + init.type === 'UnaryExpression' && + init.operator === 'void' && + init.argument.value == 0 + ) { return true; } - if (init.type==='Identifier' && init.name==='undefined') { + if (init.type === 'Identifier' && init.name === 'undefined') { return true; } } diff --git a/config/compat-entries.js b/config/compat-entries.js new file mode 100644 index 0000000000..6fd771b146 --- /dev/null +++ b/config/compat-entries.js @@ -0,0 +1,25 @@ +const path = require('path'); +const fs = require('fs'); +const kl = require('kolorist'); + +const pkgFiles = new Set(require('../package.json').files); +const compatDir = path.join(__dirname, '..', 'compat'); +const files = fs.readdirSync(compatDir); + +let missing = 0; +for (const file of files) { + const expected = 'compat/' + file; + if (/\.(js|mjs)$/.test(file) && !pkgFiles.has(expected)) { + missing++; + + const filePath = kl.cyan('compat/' + file); + const label = kl.inverse(kl.red(' ERROR ')); + console.error( + `${label} File ${filePath} is missing in "files" entry in package.json` + ); + } +} + +if (missing > 0) { + process.exit(1); +} diff --git a/config/eslint-config.js b/config/eslint-config.js deleted file mode 100644 index 95ac48965c..0000000000 --- a/config/eslint-config.js +++ /dev/null @@ -1,66 +0,0 @@ -module.exports = { - parser: 'babel-eslint', - extends: 'eslint:recommended', - plugins: [ - 'react' - ], - env: { - browser: true, - mocha: true, - node: true, - es6: true - }, - parserOptions: { - ecmaFeatures: { - modules: true, - jsx: true - } - }, - globals: { - sinon: true, - expect: true - }, - rules: { - 'react/jsx-uses-react': 2, - 'react/jsx-uses-vars': 2, - 'no-unused-vars': [1, { varsIgnorePattern: '^h$' }], - 'no-cond-assign': 1, - 'no-empty': 0, - 'no-console': 1, - semi: 2, - camelcase: 0, - 'comma-style': 2, - 'comma-dangle': [2, 'never'], - indent: [2, 'tab', {SwitchCase: 1}], - 'no-mixed-spaces-and-tabs': [2, 'smart-tabs'], - 'no-trailing-spaces': [2, { skipBlankLines: true }], - 'max-nested-callbacks': [2, 3], - 'no-eval': 2, - 'no-implied-eval': 2, - 'no-new-func': 2, - 'guard-for-in': 0, - eqeqeq: 0, - 'no-else-return': 2, - 'no-redeclare': 2, - 'no-dupe-keys': 2, - radix: 2, - strict: [2, 'never'], - 'no-shadow': 0, - 'callback-return': [1, ['callback', 'cb', 'next', 'done']], - 'no-delete-var': 2, - 'no-undef-init': 2, - 'no-shadow-restricted-names': 2, - 'handle-callback-err': 0, - 'no-lonely-if': 2, - 'keyword-spacing': 2, - 'constructor-super': 2, - 'no-this-before-super': 2, - 'no-dupe-class-members': 2, - 'no-const-assign': 2, - 'prefer-spread': 2, - 'no-useless-concat': 2, - 'no-var': 2, - 'object-shorthand': 2, - 'prefer-arrow-callback': 2 - } -}; diff --git a/config/node-13-exports.js b/config/node-13-exports.js new file mode 100644 index 0000000000..9528d2aefa --- /dev/null +++ b/config/node-13-exports.js @@ -0,0 +1,32 @@ +const fs = require('fs'); + +const subRepositories = [ + 'compat', + 'debug', + 'devtools', + 'hooks', + 'jsx-runtime', + 'test-utils' +]; +const snakeCaseToCamelCase = str => + str.replace(/([-_][a-z])/g, group => group.toUpperCase().replace('-', '')); + +const copyPreact = () => { + // Copy .module.js --> .mjs for Node 13 compat. + fs.writeFileSync( + `${process.cwd()}/dist/preact.mjs`, + fs.readFileSync(`${process.cwd()}/dist/preact.module.js`) + ); +}; + +const copy = name => { + // Copy .module.js --> .mjs for Node 13 compat. + const filename = name.includes('-') ? snakeCaseToCamelCase(name) : name; + fs.writeFileSync( + `${process.cwd()}/${name}/dist/${filename}.mjs`, + fs.readFileSync(`${process.cwd()}/${name}/dist/${filename}.module.js`) + ); +}; + +copyPreact(); +subRepositories.forEach(copy); diff --git a/config/properties.json b/config/properties.json deleted file mode 100644 index ebde50a3d6..0000000000 --- a/config/properties.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "props": { - "cname": 16, - "props": { - "$_dirty": "__d", - "$_disable": "__x", - "$_listeners": "__l", - "$_renderCallbacks": "__h", - "$__key": "__k", - "$__ref": "__r", - "$normalizedNodeName": "__n", - "$nextBase": "__b", - "$prevContext": "__c", - "$prevProps": "__p", - "$prevState": "__s", - "$_parentComponent": "__u", - "$_componentConstructor": "_componentConstructor", - "$__html": "__html", - "$_component": "_component", - "$__preactattr_": "__preactattr_" - } - }, - "vars": { - "cname": -1, - "props": {} - } -} \ No newline at end of file diff --git a/config/rollup.config.devtools.js b/config/rollup.config.devtools.js deleted file mode 100644 index 4f6d97c14c..0000000000 --- a/config/rollup.config.devtools.js +++ /dev/null @@ -1,32 +0,0 @@ -import babel from 'rollup-plugin-babel'; - -export default { - input: 'devtools/index.js', - output: { - format: 'umd', - file: 'devtools.js', - name: 'preactDevTools', - sourcemap: true, - globals: { - preact: 'preact' - } - }, - external: ['preact'], - plugins: [ - babel({ - sourceMap: true, - exclude: 'node_modules/**', - babelrc: false, - presets: [ - ['env', { - modules: false, - loose: true, - exclude: ['transform-es2015-typeof-symbol'], - targets: { - browsers: ['last 2 versions', 'IE >= 9'] - } - }] - ] - }) - ] -}; diff --git a/config/rollup.config.js b/config/rollup.config.js deleted file mode 100644 index a30eb882be..0000000000 --- a/config/rollup.config.js +++ /dev/null @@ -1,43 +0,0 @@ -import nodeResolve from 'rollup-plugin-node-resolve'; -import babel from 'rollup-plugin-babel'; -import memory from 'rollup-plugin-memory'; - -export default { - input: 'src/preact.js', - output: { - format: 'iife', - file: 'dist/preact.dev.js', - name: 'preact', - sourcemap: true, - strict: true - }, - plugins: [ - memory({ - path: 'src/preact.js', - contents: ` - import preact from './preact'; - if (typeof module!='undefined') module.exports = preact; - else self.preact = preact; - ` - }), - nodeResolve({ - main: true - }), - babel({ - sourceMap: true, - exclude: 'node_modules/**', - babelrc: false, - comments: false, - presets: [ - ['env', { - modules: false, - loose: true, - exclude: ['transform-es2015-typeof-symbol'], - targets: { - browsers: ['last 2 versions', 'IE >= 9'] - } - }] - ] - }) - ] -}; diff --git a/config/rollup.config.module.js b/config/rollup.config.module.js deleted file mode 100644 index ff215324ca..0000000000 --- a/config/rollup.config.module.js +++ /dev/null @@ -1,10 +0,0 @@ -import config from './rollup.config'; - -// ES output -config.output.format = 'es'; -config.output.file = 'dist/preact.mjs'; - -// remove memory() plugin -config.plugins.splice(0, 1); - -export default config; diff --git a/config/rollup.config.umd.js b/config/rollup.config.umd.js deleted file mode 100644 index 038067db1b..0000000000 --- a/config/rollup.config.umd.js +++ /dev/null @@ -1,10 +0,0 @@ -import config from './rollup.config'; - -// UMD output -config.output.format = 'umd'; -config.output.file = 'dist/preact.umd.js'; - -// remove memory() plugin -config.plugins.splice(0, 1); - -export default config; diff --git a/debug/LICENSE b/debug/LICENSE new file mode 100644 index 0000000000..da5389a93c --- /dev/null +++ b/debug/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-present Jason Miller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/debug/index.js b/debug/index.js deleted file mode 100644 index 876c2f6553..0000000000 --- a/debug/index.js +++ /dev/null @@ -1,122 +0,0 @@ -/* eslint-disable no-console */ - -if (process.env.NODE_ENV === 'development') { - const preact = require('preact'); - const options = preact.options; - const oldVnodeOption = options.vnode; - - options.vnode = function(vnode) { - const { nodeName, attributes, children } = vnode; - - if (nodeName === void 0) { - console.error('Undefined component passed to preact.h()\n'+serializeVNode(vnode)); - } - - if ( - attributes && attributes.ref !== void 0 && - typeof attributes.ref !== 'function' && - typeof attributes.ref !== 'object' && - !('$$typeof' in vnode) // allow string refs when preact-compat is installed - ) { - throw new Error( - `Component's "ref" property should be a function or createRef() object,` + - ` but [${typeof attributes.ref}] passed\n` + serializeVNode(vnode) - ); - } - - { - const keys = {}; - - inspectChildren(children, (deepChild) => { - if (!deepChild || deepChild.key==null) return; - - // In Preact, all keys are stored as object values, i.e. being strings - const key = deepChild.key + ''; - - if (keys.hasOwnProperty(key)) { - console.error( - 'Following component has two or more children with the ' + - 'same "key" attribute. This may cause glitches and misbehavior ' + - 'in rendering process. Component: \n\n' + - serializeVNode(vnode) - ); - - // Return early to not spam the console - return true; - } - - keys[key] = true; - }); - } - - if (oldVnodeOption) oldVnodeOption.call(this, vnode); - }; - - try { - const oldRender = preact.render; - preact.render = function (vnode, parent, merge) { - if (parent == null && merge == null) { - // render(vnode, parent, merge) can't have both parent and merge be undefined - console.error('The "containerNode" or "replaceNode" is not defined in the render method. ' + - 'Component: \n\n' + serializeVNode(vnode)); - } - else if (parent == merge) { - // if parent == merge, it doesn't reason well and would cause trouble when preact - // tries to update or replace that 'replaceNode' element - console.error( - 'The "containerNode" and "replaceNode" are the same in render method, ' + - 'when the "replaceNode" DOM node is expected to be a child of "containerNode". ' + - 'docs-ref: https://preactjs.com/guide/api-reference#-preact-render-. Component: \n\n' + - serializeVNode(vnode) - ); - } - return oldRender(vnode, parent, merge); - }; - } - catch (e) {} - - const inspectChildren = (children, inspect) => { - if (!Array.isArray(children)) { - children = [children]; - } - return children.some((child, i) => { - if (Array.isArray(child)) { - return inspectChildren(child, inspect); - } - - return inspect(child, i); - }); - }; - - const serializeVNode = ({ nodeName, attributes, children }) => { - if (typeof nodeName==='function') { - nodeName = nodeName.name || nodeName.displayName; - } - - let props = ''; - if (attributes) { - for (let attr in attributes) { - if (attributes.hasOwnProperty(attr) && attr!=='children') { - let value = attributes[attr]; - - // If it is an object but doesn't have toString(), use Object.toString - if (typeof value==='function') { - value = `function ${value.displayName || value.name}() {}`; - } - if (Object(value) === value && !value.toString) { - value = Object.prototype.toString.call(value); - } - else { - value = value + ''; - } - - props += ` ${attr}=${JSON.stringify(value)}`; - } - } - } - - return `<${nodeName}${props}${children && children.length ? ('>..') : ' />'}`; - }; - - require('preact/devtools'); -} diff --git a/debug/mangle.json b/debug/mangle.json new file mode 100644 index 0000000000..8352f38e63 --- /dev/null +++ b/debug/mangle.json @@ -0,0 +1,21 @@ +{ + "help": { + "what is this file?": "It controls protected/private property mangling so that minified builds have consistent property names.", + "why are there duplicate minified properties?": "Most properties are only used on one type of objects, so they can have the same name since they will never collide. Doing this reduces size." + }, + "minify": { + "mangle": { + "properties": { + "regex": "^_[^_]", + "reserved": [ + "__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED", + "__REACT_DEVTOOLS_GLOBAL_HOOK__", + "__PREACT_DEVTOOLS__", + "_renderers", + "__source", + "__self" + ] + } + } + } +} diff --git a/debug/package.json b/debug/package.json new file mode 100644 index 0000000000..836b4b49f7 --- /dev/null +++ b/debug/package.json @@ -0,0 +1,27 @@ +{ + "name": "preact-debug", + "amdName": "preactDebug", + "version": "1.0.0", + "private": true, + "description": "Preact extensions for development", + "main": "dist/debug.js", + "module": "dist/debug.module.js", + "umd:main": "dist/debug.umd.js", + "source": "src/index.js", + "license": "MIT", + "mangle": { + "regex": "^(?!_renderer)^_" + }, + "peerDependencies": { + "preact": "^10.0.0" + }, + "exports": { + ".": { + "types": "./src/index.d.ts", + "browser": "./dist/debug.module.js", + "umd": "./dist/debug.umd.js", + "import": "./dist/debug.mjs", + "require": "./dist/debug.js" + } + } +} diff --git a/debug/src/check-props.js b/debug/src/check-props.js new file mode 100644 index 0000000000..41ff747370 --- /dev/null +++ b/debug/src/check-props.js @@ -0,0 +1,54 @@ +const ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED'; + +let loggedTypeFailures = {}; + +/** + * Reset the history of which prop type warnings have been logged. + */ +export function resetPropWarnings() { + loggedTypeFailures = {}; +} + +/** + * Assert that the values match with the type specs. + * Error messages are memorized and will only be shown once. + * + * Adapted from https://github.com/facebook/prop-types/blob/master/checkPropTypes.js + * + * @param {object} typeSpecs Map of name to a ReactPropType + * @param {object} values Runtime values that need to be type-checked + * @param {string} location e.g. "prop", "context", "child context" + * @param {string} componentName Name of the component for error messages. + * @param {?Function} getStack Returns the component stack. + */ +export function checkPropTypes( + typeSpecs, + values, + location, + componentName, + getStack +) { + Object.keys(typeSpecs).forEach(typeSpecName => { + let error; + try { + error = typeSpecs[typeSpecName]( + values, + typeSpecName, + componentName, + location, + null, + ReactPropTypesSecret + ); + } catch (e) { + error = e; + } + if (error && !(error.message in loggedTypeFailures)) { + loggedTypeFailures[error.message] = true; + console.error( + `Failed ${location} type: ${error.message}${ + (getStack && `\n${getStack()}`) || '' + }` + ); + } + }); +} diff --git a/debug/src/component-stack.js b/debug/src/component-stack.js new file mode 100644 index 0000000000..52a1801273 --- /dev/null +++ b/debug/src/component-stack.js @@ -0,0 +1,146 @@ +import { options, Fragment } from 'preact'; + +/** + * Get human readable name of the component/dom node + * @param {import('./internal').VNode} vnode + * @param {import('./internal').VNode} vnode + * @returns {string} + */ +export function getDisplayName(vnode) { + if (vnode.type === Fragment) { + return 'Fragment'; + } else if (typeof vnode.type == 'function') { + return vnode.type.displayName || vnode.type.name; + } else if (typeof vnode.type == 'string') { + return vnode.type; + } + + return '#text'; +} + +/** + * Used to keep track of the currently rendered `vnode` and print it + * in debug messages. + */ +let renderStack = []; + +/** + * Keep track of the current owners. An owner describes a component + * which was responsible to render a specific `vnode`. This exclude + * children that are passed via `props.children`, because they belong + * to the parent owner. + * + * ```jsx + * const Foo = props =>
{props.children}
// div's owner is Foo + * const Bar = props => { + * return ( + * // Foo's owner is Bar, span's owner is Bar + * ) + * } + * ``` + * + * Note: A `vnode` may be hoisted to the root scope due to compiler + * optimiztions. In these cases the `_owner` will be different. + */ +let ownerStack = []; + +/** + * Get the currently rendered `vnode` + * @returns {import('./internal').VNode | null} + */ +export function getCurrentVNode() { + return renderStack.length > 0 ? renderStack[renderStack.length - 1] : null; +} + +/** + * If the user doesn't have `@babel/plugin-transform-react-jsx-source` + * somewhere in his tool chain we can't print the filename and source + * location of a component. In that case we just omit that, but we'll + * print a helpful message to the console, notifying the user of it. + */ +let showJsxSourcePluginWarning = true; + +/** + * Check if a `vnode` is a possible owner. + * @param {import('./internal').VNode} vnode + */ +function isPossibleOwner(vnode) { + return typeof vnode.type == 'function' && vnode.type != Fragment; +} + +/** + * Return the component stack that was captured up to this point. + * @param {import('./internal').VNode} vnode + * @returns {string} + */ +export function getOwnerStack(vnode) { + const stack = [vnode]; + let next = vnode; + while (next._owner != null) { + stack.push(next._owner); + next = next._owner; + } + + return stack.reduce((acc, owner) => { + acc += ` in ${getDisplayName(owner)}`; + + const source = owner.__source; + if (source) { + acc += ` (at ${source.fileName}:${source.lineNumber})`; + } else if (showJsxSourcePluginWarning) { + console.warn( + 'Add @babel/plugin-transform-react-jsx-source to get a more detailed component stack. Note that you should not add it to production builds of your App for bundle size reasons.' + ); + } + showJsxSourcePluginWarning = false; + + return (acc += '\n'); + }, ''); +} + +/** + * Setup code to capture the component trace while rendering. Note that + * we cannot simply traverse `vnode._parent` upwards, because we have some + * debug messages for `this.setState` where the `vnode` is `undefined`. + */ +export function setupComponentStack() { + let oldDiff = options._diff; + let oldDiffed = options.diffed; + let oldRoot = options._root; + let oldVNode = options.vnode; + let oldRender = options._render; + + options.diffed = vnode => { + if (isPossibleOwner(vnode)) { + ownerStack.pop(); + } + renderStack.pop(); + if (oldDiffed) oldDiffed(vnode); + }; + + options._diff = vnode => { + if (isPossibleOwner(vnode)) { + renderStack.push(vnode); + } + if (oldDiff) oldDiff(vnode); + }; + + options._root = (vnode, parent) => { + ownerStack = []; + if (oldRoot) oldRoot(vnode, parent); + }; + + options.vnode = vnode => { + vnode._owner = + ownerStack.length > 0 ? ownerStack[ownerStack.length - 1] : null; + if (oldVNode) oldVNode(vnode); + }; + + options._render = vnode => { + if (isPossibleOwner(vnode)) { + ownerStack.push(vnode); + } + + if (oldRender) oldRender(vnode); + }; +} diff --git a/debug/src/constants.js b/debug/src/constants.js new file mode 100644 index 0000000000..e15b547a19 --- /dev/null +++ b/debug/src/constants.js @@ -0,0 +1,3 @@ +export const ELEMENT_NODE = 1; +export const DOCUMENT_NODE = 9; +export const DOCUMENT_FRAGMENT_NODE = 11; diff --git a/debug/src/debug.js b/debug/src/debug.js new file mode 100644 index 0000000000..c8c4085abf --- /dev/null +++ b/debug/src/debug.js @@ -0,0 +1,594 @@ +import { checkPropTypes } from './check-props'; +import { options, Component } from 'preact'; +import { + ELEMENT_NODE, + DOCUMENT_NODE, + DOCUMENT_FRAGMENT_NODE +} from './constants'; +import { + getOwnerStack, + setupComponentStack, + getCurrentVNode, + getDisplayName +} from './component-stack'; +import { assign, isNaN } from './util'; + +const isWeakMapSupported = typeof WeakMap == 'function'; + +/** + * @param {import('./internal').VNode} vnode + * @returns {Array} + */ +function getDomChildren(vnode) { + let domChildren = []; + + if (!vnode._children) return domChildren; + + vnode._children.forEach(child => { + if (child && typeof child.type === 'function') { + domChildren.push.apply(domChildren, getDomChildren(child)); + } else if (child && typeof child.type === 'string') { + domChildren.push(child.type); + } + }); + + return domChildren; +} + +/** + * @param {import('./internal').VNode} parent + * @returns {string} + */ +function getClosestDomNodeParentName(parent) { + if (!parent) return ''; + if (typeof parent.type == 'function') { + if (parent._parent == null) { + if (parent._dom != null && parent._dom.parentNode != null) { + return parent._dom.parentNode.localName; + } + return ''; + } + return getClosestDomNodeParentName(parent._parent); + } + return /** @type {string} */ (parent.type); +} + +export function initDebug() { + setupComponentStack(); + + let hooksAllowed = false; + + /* eslint-disable no-console */ + let oldBeforeDiff = options._diff; + let oldDiffed = options.diffed; + let oldVnode = options.vnode; + let oldRender = options._render; + let oldCatchError = options._catchError; + let oldRoot = options._root; + let oldHook = options._hook; + const warnedComponents = !isWeakMapSupported + ? null + : { + useEffect: new WeakMap(), + useLayoutEffect: new WeakMap(), + lazyPropTypes: new WeakMap() + }; + const deprecations = []; + + options._catchError = (error, vnode, oldVNode, errorInfo) => { + let component = vnode && vnode._component; + if (component && typeof error.then == 'function') { + const promise = error; + error = new Error( + `Missing Suspense. The throwing component was: ${getDisplayName(vnode)}` + ); + + let parent = vnode; + for (; parent; parent = parent._parent) { + if (parent._component && parent._component._childDidSuspend) { + error = promise; + break; + } + } + + // We haven't recovered and we know at this point that there is no + // Suspense component higher up in the tree + if (error instanceof Error) { + throw error; + } + } + + try { + errorInfo = errorInfo || {}; + errorInfo.componentStack = getOwnerStack(vnode); + oldCatchError(error, vnode, oldVNode, errorInfo); + + // when an error was handled by an ErrorBoundary we will nonetheless emit an error + // event on the window object. This is to make up for react compatibility in dev mode + // and thus make the Next.js dev overlay work. + if (typeof error.then != 'function') { + setTimeout(() => { + throw error; + }); + } + } catch (e) { + throw e; + } + }; + + options._root = (vnode, parentNode) => { + if (!parentNode) { + throw new Error( + 'Undefined parent passed to render(), this is the second argument.\n' + + 'Check if the element is available in the DOM/has the correct id.' + ); + } + + let isValid; + switch (parentNode.nodeType) { + case ELEMENT_NODE: + case DOCUMENT_FRAGMENT_NODE: + case DOCUMENT_NODE: + isValid = true; + break; + default: + isValid = false; + } + + if (!isValid) { + let componentName = getDisplayName(vnode); + throw new Error( + `Expected a valid HTML node as a second argument to render. Received ${parentNode} instead: render(<${componentName} />, ${parentNode});` + ); + } + + if (oldRoot) oldRoot(vnode, parentNode); + }; + + options._diff = vnode => { + let { type } = vnode; + + hooksAllowed = true; + + if (type === undefined) { + throw new Error( + 'Undefined component passed to createElement()\n\n' + + 'You likely forgot to export your component or might have mixed up default and named imports' + + serializeVNode(vnode) + + `\n\n${getOwnerStack(vnode)}` + ); + } else if (type != null && typeof type == 'object') { + if (type._children !== undefined && type._dom !== undefined) { + throw new Error( + `Invalid type passed to createElement(): ${type}\n\n` + + 'Did you accidentally pass a JSX literal as JSX twice?\n\n' + + ` let My${getDisplayName(vnode)} = ${serializeVNode(type)};\n` + + ` let vnode = ;\n\n` + + 'This usually happens when you export a JSX literal and not the component.' + + `\n\n${getOwnerStack(vnode)}` + ); + } + + throw new Error( + 'Invalid type passed to createElement(): ' + + (Array.isArray(type) ? 'array' : type) + ); + } + + if ( + vnode.ref !== undefined && + typeof vnode.ref != 'function' && + typeof vnode.ref != 'object' && + !('$$typeof' in vnode) // allow string refs when preact-compat is installed + ) { + throw new Error( + `Component's "ref" property should be a function, or an object created ` + + `by createRef(), but got [${typeof vnode.ref}] instead\n` + + serializeVNode(vnode) + + `\n\n${getOwnerStack(vnode)}` + ); + } + + if (typeof vnode.type == 'string') { + for (const key in vnode.props) { + if ( + key[0] === 'o' && + key[1] === 'n' && + typeof vnode.props[key] != 'function' && + vnode.props[key] != null + ) { + throw new Error( + `Component's "${key}" property should be a function, ` + + `but got [${typeof vnode.props[key]}] instead\n` + + serializeVNode(vnode) + + `\n\n${getOwnerStack(vnode)}` + ); + } + } + } + + // Check prop-types if available + if (typeof vnode.type == 'function' && vnode.type.propTypes) { + if ( + vnode.type.displayName === 'Lazy' && + warnedComponents && + !warnedComponents.lazyPropTypes.has(vnode.type) + ) { + const m = + 'PropTypes are not supported on lazy(). Use propTypes on the wrapped component itself. '; + try { + const lazyVNode = vnode.type(); + warnedComponents.lazyPropTypes.set(vnode.type, true); + console.warn( + m + `Component wrapped in lazy() is ${getDisplayName(lazyVNode)}` + ); + } catch (promise) { + console.warn( + m + "We will log the wrapped component's name once it is loaded." + ); + } + } + + let values = vnode.props; + if (vnode.type._forwarded) { + values = assign({}, values); + delete values.ref; + } + + checkPropTypes( + vnode.type.propTypes, + values, + 'prop', + getDisplayName(vnode), + () => getOwnerStack(vnode) + ); + } + + if (oldBeforeDiff) oldBeforeDiff(vnode); + }; + + let renderCount = 0; + let currentComponent; + options._render = vnode => { + if (oldRender) { + oldRender(vnode); + } + hooksAllowed = true; + + const nextComponent = vnode._component; + if (nextComponent === currentComponent) { + renderCount++; + } else { + renderCount = 1; + } + + if (renderCount >= 25) { + throw new Error( + `Too many re-renders. This is limited to prevent an infinite loop ` + + `which may lock up your browser. The component causing this is: ${getDisplayName( + vnode + )}` + ); + } + + currentComponent = nextComponent; + }; + + options._hook = (comp, index, type) => { + if (!comp || !hooksAllowed) { + throw new Error('Hook can only be invoked from render methods.'); + } + + if (oldHook) oldHook(comp, index, type); + }; + + // Ideally we'd want to print a warning once per component, but we + // don't have access to the vnode that triggered it here. As a + // compromise and to avoid flooding the console with warnings we + // print each deprecation warning only once. + const warn = (property, message) => ({ + get() { + const key = 'get' + property + message; + if (deprecations && deprecations.indexOf(key) < 0) { + deprecations.push(key); + console.warn(`getting vnode.${property} is deprecated, ${message}`); + } + }, + set() { + const key = 'set' + property + message; + if (deprecations && deprecations.indexOf(key) < 0) { + deprecations.push(key); + console.warn(`setting vnode.${property} is not allowed, ${message}`); + } + } + }); + + const deprecatedAttributes = { + nodeName: warn('nodeName', 'use vnode.type'), + attributes: warn('attributes', 'use vnode.props'), + children: warn('children', 'use vnode.props.children') + }; + + const deprecatedProto = Object.create({}, deprecatedAttributes); + + options.vnode = vnode => { + const props = vnode.props; + if ( + vnode.type !== null && + props != null && + ('__source' in props || '__self' in props) + ) { + const newProps = (vnode.props = {}); + for (let i in props) { + const v = props[i]; + if (i === '__source') vnode.__source = v; + else if (i === '__self') vnode.__self = v; + else newProps[i] = v; + } + } + + // eslint-disable-next-line + vnode.__proto__ = deprecatedProto; + if (oldVnode) oldVnode(vnode); + }; + + options.diffed = vnode => { + const { type, _parent: parent } = vnode; + // Check if the user passed plain objects as children. Note that we cannot + // move this check into `options.vnode` because components can receive + // children in any shape they want (e.g. + // `{{ foo: 123, bar: "abc" }}`). + // Putting this check in `options.diffed` ensures that + // `vnode._children` is set and that we only validate the children + // that were actually rendered. + if (vnode._children) { + vnode._children.forEach(child => { + if (typeof child === 'object' && child && child.type === undefined) { + const keys = Object.keys(child).join(','); + throw new Error( + `Objects are not valid as a child. Encountered an object with the keys {${keys}}.` + + `\n\n${getOwnerStack(vnode)}` + ); + } + }); + } + + if (vnode._component === currentComponent) { + renderCount = 0; + } + + if ( + typeof type === 'string' && + (isTableElement(type) || + type === 'p' || + type === 'a' || + type === 'button') + ) { + // Avoid false positives when Preact only partially rendered the + // HTML tree. Whilst we attempt to include the outer DOM in our + // validation, this wouldn't work on the server for + // `preact-render-to-string`. There we'd otherwise flood the terminal + // with false positives, which we'd like to avoid. + let domParentName = getClosestDomNodeParentName(parent); + if (domParentName !== '' && isTableElement(type)) { + if ( + type === 'table' && + // Tables can be nested inside each other if it's inside a cell. + // See https://developer.mozilla.org/en-US/docs/Learn/HTML/Tables/Advanced#nesting_tables + domParentName !== 'td' && + isTableElement(domParentName) + ) { + console.log(domParentName, parent._dom); + console.error( + 'Improper nesting of table. Your should not have a table-node parent.' + + serializeVNode(vnode) + + `\n\n${getOwnerStack(vnode)}` + ); + } else if ( + (type === 'thead' || type === 'tfoot' || type === 'tbody') && + domParentName !== 'table' + ) { + console.error( + 'Improper nesting of table. Your should have a
parent.' + + serializeVNode(vnode) + + `\n\n${getOwnerStack(vnode)}` + ); + } else if ( + type === 'tr' && + domParentName !== 'thead' && + domParentName !== 'tfoot' && + domParentName !== 'tbody' + ) { + console.error( + 'Improper nesting of table. Your should have a parent.' + + serializeVNode(vnode) + + `\n\n${getOwnerStack(vnode)}` + ); + } else if (type === 'td' && domParentName !== 'tr') { + console.error( + 'Improper nesting of table. Your parent.' + + serializeVNode(vnode) + + `\n\n${getOwnerStack(vnode)}` + ); + } else if (type === 'th' && domParentName !== 'tr') { + console.error( + 'Improper nesting of table. Your .' + + serializeVNode(vnode) + + `\n\n${getOwnerStack(vnode)}` + ); + } + } else if (type === 'p') { + let illegalDomChildrenTypes = getDomChildren(vnode).filter(childType => + ILLEGAL_PARAGRAPH_CHILD_ELEMENTS.test(childType) + ); + if (illegalDomChildrenTypes.length) { + console.error( + 'Improper nesting of paragraph. Your

should not have ' + + illegalDomChildrenTypes.join(', ') + + ' as child-elements.' + + serializeVNode(vnode) + + `\n\n${getOwnerStack(vnode)}` + ); + } + } else if (type === 'a' || type === 'button') { + if (getDomChildren(vnode).indexOf(type) !== -1) { + console.error( + `Improper nesting of interactive content. Your <${type}>` + + ` should not have other ${type === 'a' ? 'anchor' : 'button'}` + + ' tags as child-elements.' + + serializeVNode(vnode) + + `\n\n${getOwnerStack(vnode)}` + ); + } + } + } + + hooksAllowed = false; + + if (oldDiffed) oldDiffed(vnode); + + if (vnode._children != null) { + const keys = []; + for (let i = 0; i < vnode._children.length; i++) { + const child = vnode._children[i]; + if (!child || child.key == null) continue; + + const key = child.key; + if (keys.indexOf(key) !== -1) { + console.error( + 'Following component has two or more children with the ' + + `same key attribute: "${key}". This may cause glitches and misbehavior ` + + 'in rendering process. Component: \n\n' + + serializeVNode(vnode) + + `\n\n${getOwnerStack(vnode)}` + ); + + // Break early to not spam the console + break; + } + + keys.push(key); + } + } + + if (vnode._component != null && vnode._component.__hooks != null) { + // Validate that none of the hooks in this component contain arguments that are NaN. + // This is a common mistake that can be hard to debug, so we want to catch it early. + const hooks = vnode._component.__hooks._list; + if (hooks) { + for (let i = 0; i < hooks.length; i += 1) { + const hook = hooks[i]; + if (hook._args) { + for (let j = 0; j < hook._args.length; j++) { + const arg = hook._args[j]; + if (isNaN(arg)) { + const componentName = getDisplayName(vnode); + console.warn( + `Invalid argument passed to hook. Hooks should not be called with NaN in the dependency array. Hook index ${i} in component ${componentName} was called with NaN.` + ); + } + } + } + } + } + } + }; +} + +const setState = Component.prototype.setState; +Component.prototype.setState = function (update, callback) { + if (this._vnode == null) { + // `this._vnode` will be `null` during componentWillMount. But it + // is perfectly valid to call `setState` during cWM. So we + // need an additional check to verify that we are dealing with a + // call inside constructor. + if (this.state == null) { + console.warn( + `Calling "this.setState" inside the constructor of a component is a ` + + `no-op and might be a bug in your application. Instead, set ` + + `"this.state = {}" directly.\n\n${getOwnerStack(getCurrentVNode())}` + ); + } + } + + return setState.call(this, update, callback); +}; + +function isTableElement(type) { + return ( + type === 'table' || + type === 'tfoot' || + type === 'tbody' || + type === 'thead' || + type === 'td' || + type === 'tr' || + type === 'th' + ); +} + +const ILLEGAL_PARAGRAPH_CHILD_ELEMENTS = + /^(address|article|aside|blockquote|details|div|dl|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|main|menu|nav|ol|p|pre|search|section|table|ul)$/; + +const forceUpdate = Component.prototype.forceUpdate; +Component.prototype.forceUpdate = function (callback) { + if (this._vnode == null) { + console.warn( + `Calling "this.forceUpdate" inside the constructor of a component is a ` + + `no-op and might be a bug in your application.\n\n${getOwnerStack( + getCurrentVNode() + )}` + ); + } else if (this._parentDom == null) { + console.warn( + `Can't call "this.forceUpdate" on an unmounted component. This is a no-op, ` + + `but it indicates a memory leak in your application. To fix, cancel all ` + + `subscriptions and asynchronous tasks in the componentWillUnmount method.` + + `\n\n${getOwnerStack(this._vnode)}` + ); + } + return forceUpdate.call(this, callback); +}; + +/** + * Serialize a vnode tree to a string + * @param {import('./internal').VNode} vnode + * @returns {string} + */ +export function serializeVNode(vnode) { + let { props } = vnode; + let name = getDisplayName(vnode); + + let attrs = ''; + for (let prop in props) { + if (props.hasOwnProperty(prop) && prop !== 'children') { + let value = props[prop]; + + // If it is an object but doesn't have toString(), use Object.toString + if (typeof value == 'function') { + value = `function ${value.displayName || value.name}() {}`; + } + + value = + Object(value) === value && !value.toString + ? Object.prototype.toString.call(value) + : value + ''; + + attrs += ` ${prop}=${JSON.stringify(value)}`; + } + } + + let children = props.children; + return `<${name}${attrs}${ + children && children.length ? '>..' : ' />' + }`; +} + +options._hydrationMismatch = (newVNode, excessDomChildren) => { + const { type } = newVNode; + const availableTypes = excessDomChildren + .map(child => child && child.localName) + .filter(Boolean); + console.error( + `Expected a DOM node of type ${type} but found ${availableTypes.join(', ')}as available DOM-node(s), this is caused by the SSR'd HTML containing different DOM-nodes compared to the hydrated one.\n\n${getOwnerStack(newVNode)}` + ); +}; diff --git a/debug/src/index.d.ts b/debug/src/index.d.ts new file mode 100644 index 0000000000..3f6ab627cd --- /dev/null +++ b/debug/src/index.d.ts @@ -0,0 +1,4 @@ +/** + * Reset the history of which prop type warnings have been logged. + */ +export function resetPropWarnings(): void; diff --git a/debug/src/index.js b/debug/src/index.js new file mode 100644 index 0000000000..37eee3bba0 --- /dev/null +++ b/debug/src/index.js @@ -0,0 +1,6 @@ +import { initDebug } from './debug'; +import 'preact/devtools'; + +initDebug(); + +export { resetPropWarnings } from './check-props'; diff --git a/debug/src/internal.d.ts b/debug/src/internal.d.ts new file mode 100644 index 0000000000..866943c948 --- /dev/null +++ b/debug/src/internal.d.ts @@ -0,0 +1,82 @@ +import { Component, PreactElement, VNode, Options } from '../../src/internal'; + +export { Component, PreactElement, VNode, Options }; + +export interface DevtoolsInjectOptions { + /** 1 = DEV, 0 = production */ + bundleType: 1 | 0; + /** The devtools enable different features for different versions of react */ + version: string; + /** Informative string, currently unused in the devtools */ + rendererPackageName: string; + /** Find the root dom node of a vnode */ + findHostInstanceByFiber(vnode: VNode): HTMLElement | null; + /** Find the closest vnode given a dom node */ + findFiberByHostInstance(instance: HTMLElement): VNode | null; +} + +export interface DevtoolsUpdater { + setState(objOrFn: any): void; + forceUpdate(): void; + setInState(path: Array, value: any): void; + setInProps(path: Array, value: any): void; + setInContext(): void; +} + +export type NodeType = 'Composite' | 'Native' | 'Wrapper' | 'Text'; + +export interface DevtoolData { + nodeType: NodeType; + // Component type + type: any; + name: string; + ref: any; + key: string | number; + updater: DevtoolsUpdater | null; + text: string | number | null; + state: any; + props: any; + children: VNode[] | string | number | null; + publicInstance: PreactElement | Text | Component; + memoizedInteractions: any[]; + + actualDuration: number; + actualStartTime: number; + treeBaseDuration: number; +} + +export type EventType = + | 'unmount' + | 'rootCommitted' + | 'root' + | 'mount' + | 'update' + | 'updateProfileTimes'; + +export interface DevtoolsEvent { + data?: DevtoolData; + internalInstance: VNode; + renderer: string; + type: EventType; +} + +export interface DevtoolsHook { + _renderers: Record; + _roots: Set; + on(ev: string, listener: () => void): void; + emit(ev: string, data?: object): void; + helpers: Record; + getFiberRoots(rendererId: string): Set; + inject(config: DevtoolsInjectOptions): string; + onCommitFiberRoot(rendererId: string, root: VNode): void; + onCommitFiberUnmount(rendererId: string, vnode: VNode): void; +} + +export interface DevtoolsWindow extends Window { + /** + * If the devtools extension is installed it will inject this object into + * the dom. This hook handles all communications between preact and the + * devtools panel. + */ + __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevtoolsHook; +} diff --git a/debug/src/util.js b/debug/src/util.js new file mode 100644 index 0000000000..be4228b9b6 --- /dev/null +++ b/debug/src/util.js @@ -0,0 +1,15 @@ +/** + * Assign properties from `props` to `obj` + * @template O, P The obj and props types + * @param {O} obj The object to copy properties to + * @param {P} props The object to copy properties from + * @returns {O & P} + */ +export function assign(obj, props) { + for (let i in props) obj[i] = props[i]; + return /** @type {O & P} */ (obj); +} + +export function isNaN(value) { + return value !== value; +} diff --git a/debug/test/browser/component-stack-2.test.js b/debug/test/browser/component-stack-2.test.js new file mode 100644 index 0000000000..a9a75af13f --- /dev/null +++ b/debug/test/browser/component-stack-2.test.js @@ -0,0 +1,54 @@ +import { createElement, render, Component } from 'preact'; +import 'preact/debug'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; + +/** @jsx createElement */ + +// This test is not part of component-stack.test.js to avoid it being +// transpiled with '@babel/plugin-transform-react-jsx-source' enabled. + +describe('component stack', () => { + /** @type {HTMLDivElement} */ + let scratch; + + let errors = []; + let warnings = []; + + beforeEach(() => { + scratch = setupScratch(); + + errors = []; + warnings = []; + sinon.stub(console, 'error').callsFake(e => errors.push(e)); + sinon.stub(console, 'warn').callsFake(w => warnings.push(w)); + }); + + afterEach(() => { + console.error.restore(); + console.warn.restore(); + teardown(scratch); + }); + + it('should print a warning when "@babel/plugin-transform-react-jsx-source" is not installed', () => { + function Foo() { + return ; + } + + class Thrower extends Component { + constructor(props) { + super(props); + this.setState({ foo: 1 }); + } + + render() { + return

foo
; + } + } + + render(, scratch); + + expect( + warnings[0].indexOf('@babel/plugin-transform-react-jsx-source') > -1 + ).to.equal(true); + }); +}); diff --git a/debug/test/browser/component-stack.test.js b/debug/test/browser/component-stack.test.js new file mode 100644 index 0000000000..f64bdde3b3 --- /dev/null +++ b/debug/test/browser/component-stack.test.js @@ -0,0 +1,100 @@ +import { createElement, render, Component } from 'preact'; +import 'preact/debug'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; + +/** @jsx createElement */ + +describe('component stack', () => { + /** @type {HTMLDivElement} */ + let scratch; + + let errors = []; + let warnings = []; + + const getStack = arr => arr[0].split('\n\n')[1]; + + beforeEach(() => { + scratch = setupScratch(); + + errors = []; + warnings = []; + sinon.stub(console, 'error').callsFake(e => errors.push(e)); + sinon.stub(console, 'warn').callsFake(w => warnings.push(w)); + }); + + afterEach(() => { + console.error.restore(); + console.warn.restore(); + teardown(scratch); + }); + + it('should print component stack', () => { + function Foo() { + return ; + } + + class Thrower extends Component { + constructor(props) { + super(props); + this.setState({ foo: 1 }); + } + + render() { + return
foo
; + } + } + + render(, scratch); + + let lines = getStack(warnings).split('\n'); + expect(lines[0].indexOf('Thrower') > -1).to.equal(true); + expect(lines[1].indexOf('Foo') > -1).to.equal(true); + }); + + it('should only print owners', () => { + function Foo(props) { + return
{props.children}
; + } + + function Bar() { + return ( + + + + ); + } + + class Thrower extends Component { + render() { + return ( +
should have a
should have a
+ foo + +
+
+ ); + } + } + + render(, scratch); + + let lines = getStack(errors).split('\n'); + expect(lines[0].indexOf('tr') > -1).to.equal(true); + expect(lines[1].indexOf('Thrower') > -1).to.equal(true); + expect(lines[2].indexOf('Bar') > -1).to.equal(true); + }); + + it('should not print a warning when "@babel/plugin-transform-react-jsx-source" is installed', () => { + function Thrower() { + throw new Error('foo'); + } + + try { + render(, scratch); + } catch {} + + expect(warnings.join(' ')).to.not.include( + '@babel/plugin-transform-react-jsx-source' + ); + }); +}); diff --git a/debug/test/browser/debug-compat.test.js b/debug/test/browser/debug-compat.test.js new file mode 100644 index 0000000000..d177406d99 --- /dev/null +++ b/debug/test/browser/debug-compat.test.js @@ -0,0 +1,81 @@ +import { createElement, render, createRef } from 'preact'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; +import './fakeDevTools'; +import 'preact/debug'; +import * as PropTypes from 'prop-types'; + +// eslint-disable-next-line no-duplicate-imports +import { resetPropWarnings } from 'preact/debug'; +import { forwardRef, createPortal } from 'preact/compat'; + +const h = createElement; +/** @jsx createElement */ + +describe('debug compat', () => { + let scratch; + let root; + let errors = []; + let warnings = []; + + beforeEach(() => { + errors = []; + warnings = []; + scratch = setupScratch(); + sinon.stub(console, 'error').callsFake(e => errors.push(e)); + sinon.stub(console, 'warn').callsFake(w => warnings.push(w)); + + root = document.createElement('div'); + document.body.appendChild(root); + }); + + afterEach(() => { + /** @type {*} */ + console.error.restore(); + console.warn.restore(); + teardown(scratch); + + document.body.removeChild(root); + }); + + describe('portals', () => { + it('should not throw an invalid render argument for a portal.', () => { + function Foo(props) { + return
{createPortal(props.children, root)}
; + } + expect(() => render(foobar, scratch)).not.to.throw(); + }); + }); + + describe('PropTypes', () => { + beforeEach(() => { + resetPropWarnings(); + }); + + it('should not fail if ref is passed to comp wrapped in forwardRef', () => { + // This test ensures compat with airbnb/prop-types-exact, mui exact prop types util, etc. + + const Foo = forwardRef(function Foo(props, ref) { + return

{props.text}

; + }); + + Foo.propTypes = { + text: PropTypes.string.isRequired, + ref(props) { + if ('ref' in props) { + throw new Error( + 'ref should not be passed to prop-types valiation!' + ); + } + } + }; + + const ref = createRef(); + + render(, scratch); + + expect(console.error).not.been.called; + + expect(ref.current).to.not.be.undefined; + }); + }); +}); diff --git a/debug/test/browser/debug-hooks.test.js b/debug/test/browser/debug-hooks.test.js new file mode 100644 index 0000000000..84bc0281ea --- /dev/null +++ b/debug/test/browser/debug-hooks.test.js @@ -0,0 +1,111 @@ +import { createElement, render, Component } from 'preact'; +import { useState, useEffect } from 'preact/hooks'; +import 'preact/debug'; +import { act } from 'preact/test-utils'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; + +/** @jsx createElement */ + +describe('debug with hooks', () => { + let scratch; + let errors = []; + let warnings = []; + + beforeEach(() => { + errors = []; + warnings = []; + scratch = setupScratch(); + sinon.stub(console, 'error').callsFake(e => errors.push(e)); + sinon.stub(console, 'warn').callsFake(w => warnings.push(w)); + }); + + afterEach(() => { + console.error.restore(); + console.warn.restore(); + teardown(scratch); + }); + + it('should throw an error when using a hook outside a render', () => { + class Foo extends Component { + componentWillMount() { + useState(); + } + + render() { + return this.props.children; + } + } + + class App extends Component { + render() { + return

test

; + } + } + const fn = () => + act(() => + render( + + + , + scratch + ) + ); + expect(fn).to.throw(/Hook can only be invoked from render/); + }); + + it('should throw an error when invoked outside of a component', () => { + function foo() { + useEffect(() => {}); // Pretend to use a hook + return

test

; + } + + const fn = () => + act(() => { + render(foo(), scratch); + }); + expect(fn).to.throw(/Hook can only be invoked from render/); + }); + + it('should throw an error when invoked outside of a component before render', () => { + function Foo(props) { + useEffect(() => {}); // Pretend to use a hook + return props.children; + } + + const fn = () => + act(() => { + useState(); + render(Hello!, scratch); + }); + expect(fn).to.throw(/Hook can only be invoked from render/); + }); + + it('should throw an error when invoked outside of a component after render', () => { + function Foo(props) { + useEffect(() => {}); // Pretend to use a hook + return props.children; + } + + const fn = () => + act(() => { + render(Hello!, scratch); + useState(); + }); + expect(fn).to.throw(/Hook can only be invoked from render/); + }); + + it('should throw an error when invoked inside an effect callback', () => { + function Foo(props) { + useEffect(() => { + useState(); + }); + return props.children; + } + + const fn = () => + act(() => { + render(Hello!, scratch); + }); + expect(fn).to.throw(/Hook can only be invoked from render/); + }); +}); diff --git a/debug/test/browser/debug-suspense.test.js b/debug/test/browser/debug-suspense.test.js new file mode 100644 index 0000000000..a436154fdf --- /dev/null +++ b/debug/test/browser/debug-suspense.test.js @@ -0,0 +1,190 @@ +import { createElement, render, lazy, Suspense } from 'preact/compat'; +import 'preact/debug'; +import { setupRerender } from 'preact/test-utils'; +import { + setupScratch, + teardown, + serializeHtml +} from '../../../test/_util/helpers'; + +/** @jsx createElement */ + +describe('debug with suspense', () => { + /** @type {HTMLDivElement} */ + let scratch; + let rerender; + let errors = []; + let warnings = []; + + beforeEach(() => { + errors = []; + warnings = []; + scratch = setupScratch(); + rerender = setupRerender(); + sinon.stub(console, 'error').callsFake(e => errors.push(e)); + sinon.stub(console, 'warn').callsFake(w => warnings.push(w)); + }); + + afterEach(() => { + console.error.restore(); + console.warn.restore(); + teardown(scratch); + }); + + it('should throw on missing ', () => { + function Foo() { + throw Promise.resolve(); + } + + expect(() => render(, scratch)).to.throw; + }); + + it('should throw an error when using lazy and missing Suspense', () => { + const Foo = () =>
Foo
; + const LazyComp = lazy( + () => new Promise(resolve => resolve({ default: Foo })) + ); + const fn = () => { + render(, scratch); + }; + + expect(fn).to.throw(/Missing Suspense/gi); + }); + + describe('PropTypes', () => { + it('should validate propTypes inside lazy()', () => { + function Baz(props) { + return

{props.unhappy}

; + } + + Baz.propTypes = { + unhappy: function alwaysThrows(obj, key) { + if (obj[key] === 'signal') throw Error('got prop inside lazy()'); + } + }; + + const loader = Promise.resolve({ default: Baz }); + const LazyBaz = lazy(() => loader); + + const suspense = ( + fallback...
}> + + + ); + render(suspense, scratch); + rerender(); // render fallback + + expect(console.error).to.not.be.called; + expect(serializeHtml(scratch)).to.equal('
fallback...
'); + + return loader.then(() => { + rerender(); + expect(errors.length).to.equal(1); + expect(errors[0].includes('got prop')).to.equal(true); + expect(serializeHtml(scratch)).to.equal('

signal

'); + }); + }); + + describe('warn for PropTypes on lazy()', () => { + it('should log the function name', () => { + const loader = Promise.resolve({ + default: function MyLazyLoaded() { + return
Hi there
; + } + }); + const FakeLazy = lazy(() => loader); + FakeLazy.propTypes = {}; + const suspense = ( + fallback...
}> + + + ); + render(suspense, scratch); + rerender(); // Render fallback + + expect(serializeHtml(scratch)).to.equal('
fallback...
'); + + return loader.then(() => { + rerender(); + expect(console.warn).to.be.calledTwice; + expect(warnings[1].includes('MyLazyLoaded')).to.equal(true); + expect(serializeHtml(scratch)).to.equal('
Hi there
'); + }); + }); + + it('should log the displayName', () => { + function MyLazyLoadedComponent() { + return
Hi there
; + } + MyLazyLoadedComponent.displayName = 'HelloLazy'; + const loader = Promise.resolve({ default: MyLazyLoadedComponent }); + const FakeLazy = lazy(() => loader); + FakeLazy.propTypes = {}; + const suspense = ( + fallback...
}> + + + ); + render(suspense, scratch); + rerender(); // Render fallback + + expect(serializeHtml(scratch)).to.equal('
fallback...
'); + + return loader.then(() => { + rerender(); + expect(console.warn).to.be.calledTwice; + expect(warnings[1].includes('HelloLazy')).to.equal(true); + expect(serializeHtml(scratch)).to.equal('
Hi there
'); + }); + }); + + it("should not log a component if lazy loader's Promise rejects", () => { + const loader = Promise.reject(new Error('Hey there')); + const FakeLazy = lazy(() => loader); + FakeLazy.propTypes = {}; + render( + fallback...
}> + + , + scratch + ); + rerender(); // Render fallback + + expect(serializeHtml(scratch)).to.equal('
fallback...
'); + + return loader.catch(() => { + try { + rerender(); + } catch (e) { + // Ignore the loader's bubbling error + } + + // Called once on initial render, and again when promise rejects + expect(console.warn).to.be.calledTwice; + }); + }); + + it("should not log a component if lazy's loader throws", () => { + const FakeLazy = lazy(() => { + throw new Error('Hello'); + }); + FakeLazy.propTypes = {}; + let error; + try { + render( + fallback...
}> + + , + scratch + ); + } catch (e) { + error = e; + } + + expect(console.warn).to.be.calledOnce; + expect(error).not.to.be.undefined; + expect(error.message).to.eql('Hello'); + }); + }); + }); +}); diff --git a/debug/test/browser/debug.options.test.js b/debug/test/browser/debug.options.test.js new file mode 100644 index 0000000000..d791eb62a6 --- /dev/null +++ b/debug/test/browser/debug.options.test.js @@ -0,0 +1,137 @@ +import { + vnodeSpy, + rootSpy, + beforeDiffSpy, + hookSpy, + afterDiffSpy, + catchErrorSpy +} from '../../../test/_util/optionSpies'; + +import { createElement, render, Component } from 'preact'; +import { useState } from 'preact/hooks'; +import { setupRerender } from 'preact/test-utils'; +import 'preact/debug'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; + +/** @jsx createElement */ + +describe('debug options', () => { + /** @type {HTMLDivElement} */ + let scratch; + + /** @type {() => void} */ + let rerender; + + /** @type {(count: number) => void} */ + let setCount; + + /** @type {import('sinon').SinonFakeTimers | undefined} */ + let clock; + + beforeEach(() => { + scratch = setupScratch(); + rerender = setupRerender(); + + vnodeSpy.resetHistory(); + rootSpy.resetHistory(); + beforeDiffSpy.resetHistory(); + hookSpy.resetHistory(); + afterDiffSpy.resetHistory(); + catchErrorSpy.resetHistory(); + }); + + afterEach(() => { + teardown(scratch); + if (clock) clock.restore(); + }); + + class ClassApp extends Component { + constructor() { + super(); + this.state = { count: 0 }; + setCount = count => this.setState({ count }); + } + + render() { + return
{this.state.count}
; + } + } + + it('should call old options on mount', () => { + render(, scratch); + + expect(vnodeSpy).to.have.been.called; + expect(rootSpy).to.have.been.called; + expect(beforeDiffSpy).to.have.been.called; + expect(afterDiffSpy).to.have.been.called; + }); + + it('should call old options on update', () => { + render(, scratch); + + setCount(1); + rerender(); + + expect(vnodeSpy).to.have.been.called; + expect(rootSpy).to.have.been.called; + expect(beforeDiffSpy).to.have.been.called; + expect(afterDiffSpy).to.have.been.called; + }); + + it('should call old options on unmount', () => { + render(, scratch); + render(null, scratch); + + expect(vnodeSpy).to.have.been.called; + expect(rootSpy).to.have.been.called; + expect(beforeDiffSpy).to.have.been.called; + expect(afterDiffSpy).to.have.been.called; + }); + + it('should call old hook options for hook components', () => { + function HookApp() { + const [count, realSetCount] = useState(0); + setCount = realSetCount; + return
{count}
; + } + + render(, scratch); + + expect(hookSpy).to.have.been.called; + }); + + it('should call old options on error', () => { + const e = new Error('test'); + class ErrorApp extends Component { + constructor() { + super(); + this.state = { error: true }; + } + componentDidCatch() { + this.setState({ error: false }); + } + render() { + return ; + } + } + + function Throw({ error }) { + if (error) { + throw e; + } else { + return
no error
; + } + } + + clock = sinon.useFakeTimers(); + + render(, scratch); + rerender(); + + expect(catchErrorSpy).to.have.been.called; + + // we expect to throw after setTimeout to trigger a window.onerror + // this is to ensure react compat (i.e. with next.js' dev overlay) + expect(() => clock.tick(0)).to.throw(e); + }); +}); diff --git a/debug/test/browser/debug.test.js b/debug/test/browser/debug.test.js new file mode 100644 index 0000000000..eda8e77534 --- /dev/null +++ b/debug/test/browser/debug.test.js @@ -0,0 +1,919 @@ +import { + createElement, + render, + createRef, + Component, + Fragment, + hydrate +} from 'preact'; +import { useState } from 'preact/hooks'; +import { + setupScratch, + teardown, + serializeHtml +} from '../../../test/_util/helpers'; +import './fakeDevTools'; +import 'preact/debug'; +import { setupRerender } from 'preact/test-utils'; +import * as PropTypes from 'prop-types'; + +// eslint-disable-next-line no-duplicate-imports +import { resetPropWarnings } from 'preact/debug'; + +const h = createElement; +/** @jsx createElement */ + +describe('debug', () => { + /** @type {HTMLDivElement} */ + let scratch; + let errors = []; + let warnings = []; + let rerender; + + beforeEach(() => { + errors = []; + warnings = []; + scratch = setupScratch(); + rerender = setupRerender(); + sinon.stub(console, 'error').callsFake(e => errors.push(e)); + sinon.stub(console, 'warn').callsFake(w => warnings.push(w)); + }); + + afterEach(() => { + /** @type {*} */ + console.error.restore(); + console.warn.restore(); + teardown(scratch); + }); + + it('should initialize devtools', () => { + expect(window.__PREACT_DEVTOOLS__.attachPreact).to.have.been.called; + }); + + it('should print an error on rendering on undefined parent', () => { + let fn = () => render(
, undefined); + expect(fn).to.throw(/render/); + }); + + it('should print an error on rendering on invalid parent', () => { + let fn = () => render(
, 6); + expect(fn).to.throw(/valid HTML node/); + expect(fn).to.throw(/
{ + const App = () =>
; + let fn = () => render(, 6); + expect(fn).to.throw(/ render(, {}); + expect(fn).to.throw(/ render(, 'badroot'); + expect(fn).to.throw(/ { + class App extends Component { + render() { + return
; + } + } + let fn = () => render(, 6); + expect(fn).to.throw(/ { + let fn = () => render(h(undefined), scratch); + expect(fn).to.throw(/createElement/); + }); + + it('should print an error on invalid object component', () => { + let fn = () => render(h({}), scratch); + expect(fn).to.throw(/createElement/); + }); + + it('should print an error when component is an array', () => { + let fn = () => render(h([
]), scratch); + expect(fn).to.throw(/createElement/); + }); + + it('should print an error on double jsx conversion', () => { + let Foo =
; + let fn = () => render(h(), scratch); + expect(fn).to.throw(/JSX twice/); + }); + + it('should add __source to the vnode in debug mode.', () => { + const vnode = h('div', { + __source: { + fileName: 'div.jsx', + lineNumber: 3 + } + }); + expect(vnode.__source).to.deep.equal({ + fileName: 'div.jsx', + lineNumber: 3 + }); + expect(vnode.props.__source).to.be.undefined; + }); + + it('should add __self to the vnode in debug mode.', () => { + const vnode = h('div', { + __self: {} + }); + expect(vnode.__self).to.deep.equal({}); + expect(vnode.props.__self).to.be.undefined; + }); + + it('should warn when accessing certain attributes', () => { + const vnode = h('div', null); + + // Push into an array to avoid empty statements being dead code eliminated + const res = []; + res.push(vnode); + res.push(vnode.attributes); + expect(console.warn).to.be.calledOnce; + expect(console.warn.args[0]).to.match(/use vnode.props/); + res.push(vnode.nodeName); + expect(console.warn).to.be.calledTwice; + expect(console.warn.args[1]).to.match(/use vnode.type/); + res.push(vnode.children); + expect(console.warn).to.be.calledThrice; + expect(console.warn.args[2]).to.match(/use vnode.props.children/); + + // Should only warn once + res.push(vnode.attributes); + expect(console.warn).to.be.calledThrice; + res.push(vnode.nodeName); + expect(console.warn).to.be.calledThrice; + res.push(vnode.children); + expect(console.warn).to.be.calledThrice; + + vnode.attributes = {}; + expect(console.warn.args[3]).to.match(/use vnode.props/); + vnode.nodeName = ''; + expect(console.warn.args[4]).to.match(/use vnode.type/); + vnode.children = []; + expect(console.warn.args[5]).to.match(/use vnode.props.children/); + + // Should only warn once + vnode.attributes = {}; + expect(console.warn.args.length).to.equal(6); + vnode.nodeName = ''; + expect(console.warn.args.length).to.equal(6); + vnode.children = []; + expect(console.warn.args.length).to.equal(6); + + // Mark res as used, otherwise it will be dead code eliminated + expect(res.length).to.equal(7); + }); + + it('should warn when calling setState inside the constructor', () => { + class Foo extends Component { + constructor(props) { + super(props); + this.setState({ foo: true }); + } + render() { + return
foo
; + } + } + + render(, scratch); + expect(console.warn).to.be.calledOnce; + expect(console.warn.args[0]).to.match(/no-op/); + }); + + it('should NOT warn when calling setState inside the cWM', () => { + class Foo extends Component { + componentWillMount() { + this.setState({ foo: true }); + } + render() { + return
foo
; + } + } + + render(, scratch); + expect(console.warn).to.not.be.called; + }); + + it('should warn when calling forceUpdate inside the constructor', () => { + class Foo extends Component { + constructor(props) { + super(props); + this.forceUpdate(); + } + render() { + return
foo
; + } + } + + render(, scratch); + expect(console.warn).to.be.calledOnce; + expect(console.warn.args[0]).to.match(/no-op/); + }); + + it('should warn when calling forceUpdate on an unmounted Component', () => { + let forceUpdate; + + class Foo extends Component { + constructor(props) { + super(props); + forceUpdate = () => this.forceUpdate(); + } + render() { + return
foo
; + } + } + + render(, scratch); + forceUpdate(); + expect(console.warn).to.not.be.called; + + render(null, scratch); + + forceUpdate(); + expect(console.warn).to.be.calledOnce; + expect(console.warn.args[0]).to.match(/no-op/); + }); + + it('should print an error when child is a plain object', () => { + let fn = () => render(
{{}}
, scratch); + expect(fn).to.throw(/not valid/); + }); + + it('should print an error on invalid refs', () => { + let fn = () => render(
, scratch); + expect(fn).to.throw(/createRef/); + }); + + it('should not print for null as a handler', () => { + let fn = () => render(
, scratch); + expect(fn).not.to.throw(); + }); + + it('should not print for undefined as a handler', () => { + let fn = () => render(
, scratch); + expect(fn).not.to.throw(); + }); + + it('should not print for attributes starting with on for Components', () => { + const Comp = () =>

online

; + let fn = () => render(, scratch); + expect(fn).not.to.throw(); + }); + + it('should print an error on invalid handler', () => { + let fn = () => render(
, scratch); + expect(fn).to.throw(/"onclick" property should be a function/); + }); + + it('should NOT print an error on valid refs', () => { + let noop = () => {}; + render(
, scratch); + + let ref = createRef(); + render(
, scratch); + expect(console.error).to.not.be.called; + }); + + it('throws an error if a component rerenders too many times', () => { + let rerenderCount = 0; + function TestComponent({ loop = false }) { + const [count, setCount] = useState(0); + if (loop) { + setCount(count + 1); + } + + if (count > 30) { + expect.fail( + 'Repeated rerenders did not cause the expected error. This test is failing.' + ); + } + + rerenderCount += 1; + return
; + } + + expect(() => { + render( + + + + , + scratch + ); + }).to.throw(/Too many re-renders/); + // 1 for first TestComponent + 24 for second TestComponent + expect(rerenderCount).to.equal(25); + }); + + it('does not throw an error if a component renders many times in different cycles', () => { + let set; + function TestComponent() { + const [count, setCount] = useState(0); + set = () => setCount(count + 1); + return
{count}
; + } + + render(, scratch); + for (let i = 0; i < 30; i++) { + set(); + rerender(); + } + expect(scratch.innerHTML).to.equal('
30
'); + }); + + describe('duplicate keys', () => { + const List = props =>
    {props.children}
; + const ListItem = props =>
  • {props.children}
  • ; + + it('should print an error on duplicate keys with DOM nodes', () => { + render( +
    + + +
    , + scratch + ); + expect(console.error).to.be.calledOnce; + }); + + it('should allow distinct object keys', () => { + const A = { is: 'A' }; + const B = { is: 'B' }; + render( +
    + + +
    , + scratch + ); + expect(console.error).not.to.be.called; + }); + + it('should print an error for duplicate object keys', () => { + const A = { is: 'A' }; + render( +
    + + +
    , + scratch + ); + expect(console.error).to.be.calledOnce; + }); + + it('should print an error on duplicate keys with Components', () => { + function App() { + return ( + + a + b + d + d + + ); + } + + render(, scratch); + expect(console.error).to.be.calledOnce; + }); + + it('should print an error on duplicate keys with Fragments', () => { + function App() { + return ( + + + a + b + + {/* Should be okay to duplicate keys since these are inside a Fragment */} + c + d + e + + f + +
    sibling
    +
    + ); + } + + render(, scratch); + expect(console.error).to.be.calledTwice; + }); + }); + + describe('table markup', () => { + it('missing ///', () => { + const Table = () => ( +
    +
    + + + + ); + render(
    hi
    , scratch); + expect(console.error).to.be.calledOnce; + }); + + it('missing
    with ', () => { + const Table = () => ( +
    +
    + + + + + + ); + render(
    hi
    , scratch); + expect(console.error).to.be.calledOnce; + }); + + it('missing
    with ', () => { + const Table = () => ( +
    +
    + + + + + + ); + render(
    hi
    , scratch); + expect(console.error).to.be.calledOnce; + }); + + it('missing
    with ', () => { + const Table = () => ( +
    +
    + + + + + + ); + render(
    hi
    , scratch); + expect(console.error).to.be.calledOnce; + }); + + it('missing ', () => { + const Table = () => ( +
    + + + +
    Hi
    + ); + render(, scratch); + expect(console.error).to.be.calledOnce; + }); + + it('missing with td component', () => { + const Cell = ({ children }) => ; + const Table = () => ( +
    {children}
    + + Hi + +
    + ); + render(, scratch); + expect(console.error).to.be.calledOnce; + }); + + it('missing with th component', () => { + const Cell = ({ children }) => ; + const Table = () => ( +
    {children}
    + + Hi + +
    + ); + render(, scratch); + expect(console.error).to.be.calledOnce; + }); + + it('Should accept ', () => { + const Table = () => ( +
    instead of in
    + + + + + +
    Hi
    + ); + render(, scratch); + expect(console.error).to.not.be.called; + }); + + it('Accepts well formed table with TD components', () => { + const Cell = ({ children }) => ; + const Table = () => ( +
    {children}
    + + + + + + + + + + + + + Body + + +
    Head
    Body
    + ); + render(, scratch); + expect(console.error).to.not.be.called; + }); + + it('Accepts well formed table', () => { + const Table = () => ( +
    + + + + + + + + + + + + + + + +
    Head
    Body
    Body
    + ); + render(, scratch); + expect(console.error).to.not.be.called; + }); + + it('Accepts minimal well formed table', () => { + const Table = () => ( +
    + + + + + + + + +
    Head
    Body
    + ); + render(, scratch); + expect(console.error).to.not.be.called; + }); + + it('should include DOM parents outside of root node', () => { + const Table = () => ( + + + + ); + + const table = document.createElement('table'); + scratch.appendChild(table); + render(
    Head
    , table); + expect(console.error).to.not.be.called; + }); + + it('should warn for improper nested table', () => { + const Table = () => ( +
    + + +
    + + +
    + ); + + render(, scratch); + expect(console.error).to.be.calledOnce; + }); + + it('accepts valid nested tables', () => { + const Table = () => ( +
    + + + + + + + + + + + +
    foo
    + + + + + + + + +
    cell1cell2cell3
    +
    bar
    + ); + + render(, scratch); + expect(console.error).to.not.be.called; + }); + }); + + describe('paragraph nesting', () => { + it('should not warn a regular text paragraph', () => { + const Paragraph = () =>

    Hello world

    ; + + render(, scratch); + expect(console.error).to.not.be.called; + }); + + it('should not crash for an empty pragraph', () => { + const Paragraph = () =>

    ; + + render(, scratch); + expect(console.error).to.not.be.called; + }); + + it('should warn for nesting illegal dom-nodes under a paragraph', () => { + const Paragraph = () => ( +

    +

    Hello world

    +

    + ); + + render(, scratch); + expect(console.error).to.be.calledOnce; + }); + + it('should warn for nesting illegal dom-nodes under a paragraph with a parent', () => { + const Paragraph = () => ( +
    +

    +

    Hello world
    +

    +
    + ); + + render(, scratch); + expect(console.error).to.be.calledOnce; + }); + + it('should warn for nesting illegal dom-nodes under a paragraph as func', () => { + const Title = ({ children }) =>

    {children}

    ; + const Paragraph = () => ( +

    + Hello world +

    + ); + + render(, scratch); + expect(console.error).to.be.calledOnce; + }); + + it('should not warn for nesting span under a paragraph', () => { + const Paragraph = () => ( +

    + Hello world +

    + ); + + render(, scratch); + expect(console.error).to.not.be.called; + }); + }); + + describe('button nesting', () => { + it('should not warn on a regular button', () => { + const Button = () => ; + + render( + + ); + + render(; + const Button = () => ( + + ); + + render( + ); + + render( + + ); + + render(, scratch); + expect(console.error).to.not.be.called; + }); + }); + + describe('PropTypes', () => { + beforeEach(() => { + resetPropWarnings(); + }); + + it("should fail if props don't match prop-types", () => { + function Foo(props) { + return

    {props.text}

    ; + } + + Foo.propTypes = { + text: PropTypes.string.isRequired + }; + + render(, scratch); + + expect(console.error).to.be.calledOnce; + + // The message here may change when the "prop-types" library is updated, + // but we check it exactly to make sure all parameters were supplied + // correctly. + expect(console.error).to.have.been.calledOnceWith( + sinon.match( + /^Failed prop type: Invalid prop `text` of type `number` supplied to `Foo`, expected `string`\.\n {2}in Foo \(at (.*)[/\\]debug[/\\]test[/\\]browser[/\\]debug\.test\.js:[0-9]+\)$/m + ) + ); + }); + + it('should only log a given prop type error once', () => { + function Foo(props) { + return

    {props.text}

    ; + } + + Foo.propTypes = { + text: PropTypes.string.isRequired, + count: PropTypes.number + }; + + // Trigger the same error twice. The error should only be logged + // once. + render(, scratch); + render(, scratch); + + expect(console.error).to.be.calledOnce; + + // Trigger a different error. This should result in a new log + // message. + console.error.resetHistory(); + render(, scratch); + expect(console.error).to.be.calledOnce; + }); + + it('should render with error logged when validator gets signal and throws exception', () => { + function Baz(props) { + return

    {props.unhappy}

    ; + } + + Baz.propTypes = { + unhappy: function alwaysThrows(obj, key) { + if (obj[key] === 'signal') throw Error('got prop'); + } + }; + + render(, scratch); + + expect(console.error).to.be.calledOnce; + expect(errors[0].includes('got prop')).to.equal(true); + expect(serializeHtml(scratch)).to.equal('

    signal

    '); + }); + + it('should not print to console when types are correct', () => { + function Bar(props) { + return

    {props.text}

    ; + } + + Bar.propTypes = { + text: PropTypes.string.isRequired + }; + + render(, scratch); + expect(console.error).to.not.be.called; + }); + }); + + describe('Hydration mismatches', () => { + it('Should warn us for a node mismatch', () => { + scratch.innerHTML = '
    foo/div>'; + const App = () => ( +
    +

    foo

    +
    + ); + hydrate(, scratch); + expect(console.error).to.be.calledOnce; + expect(console.error).to.be.calledOnceWith( + sinon.match(/Expected a DOM node of type p but found span/) + ); + }); + + it('Should not warn for a text-node mismatch', () => { + scratch.innerHTML = '
    foo bar baz/div>'; + const App = () => ( +
    + foo {'bar'} {'baz'} +
    + ); + hydrate(, scratch); + expect(console.error).to.not.be.called; + }); + + it('Should not warn for a well-formed tree', () => { + scratch.innerHTML = '
    foobar
    '; + const App = () => ( +
    + foo + bar +
    + ); + hydrate(, scratch); + expect(console.error).to.not.be.called; + }); + }); +}); diff --git a/debug/test/browser/fakeDevTools.js b/debug/test/browser/fakeDevTools.js new file mode 100644 index 0000000000..e611df3387 --- /dev/null +++ b/debug/test/browser/fakeDevTools.js @@ -0,0 +1 @@ +window.__PREACT_DEVTOOLS__ = { attachPreact: sinon.spy() }; diff --git a/debug/test/browser/serializeVNode.test.js b/debug/test/browser/serializeVNode.test.js new file mode 100644 index 0000000000..f8c851527f --- /dev/null +++ b/debug/test/browser/serializeVNode.test.js @@ -0,0 +1,66 @@ +import { createElement, Component } from 'preact'; +import { serializeVNode } from '../../src/debug'; + +/** @jsx createElement */ + +describe('serializeVNode', () => { + it("should prefer a function component's displayName", () => { + function Foo() { + return
    ; + } + Foo.displayName = 'Bar'; + + expect(serializeVNode()).to.equal(''); + }); + + it("should prefer a class component's displayName", () => { + class Bar extends Component { + render() { + return
    ; + } + } + Bar.displayName = 'Foo'; + + expect(serializeVNode()).to.equal(''); + }); + + it('should serialize vnodes without children', () => { + expect(serializeVNode(
    )).to.equal('
    '); + }); + + it('should serialize vnodes with children', () => { + expect(serializeVNode(
    Hello World
    )).to.equal('
    ..
    '); + }); + + it('should serialize components', () => { + function Foo() { + return
    ; + } + expect(serializeVNode()).to.equal(''); + }); + + it('should serialize props', () => { + expect(serializeVNode(
    )).to.equal('
    '); + + // Ensure that we have a predictable function name. Our test runner + // creates an all inclusive bundle per file and the identifier + // "noop" may have already been used. + // eslint-disable-next-line func-style + let noop = function noopFn() {}; + expect(serializeVNode(
    )).to.equal( + '
    ' + ); + + function Foo(props) { + return props.foo; + } + + expect(serializeVNode()).to.equal( + '' + ); + + expect(serializeVNode(
    )).to.equal( + '
    ' + ); + }); +}); diff --git a/debug/test/browser/validateHookArgs.test.js b/debug/test/browser/validateHookArgs.test.js new file mode 100644 index 0000000000..5e2b6b6d3b --- /dev/null +++ b/debug/test/browser/validateHookArgs.test.js @@ -0,0 +1,81 @@ +import { createElement, render, createRef } from 'preact'; +import { + useState, + useEffect, + useLayoutEffect, + useCallback, + useMemo, + useImperativeHandle +} from 'preact/hooks'; +import { setupRerender } from 'preact/test-utils'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; +import 'preact/debug'; + +/** @jsx createElement */ + +describe('Hook argument validation', () => { + /** + * @param {string} name + * @param {(arg: number) => void} hook + */ + function validateHook(name, hook) { + const TestComponent = ({ initialValue }) => { + const [value, setValue] = useState(initialValue); + hook(value); + + return ( + + ); + }; + + it(`should error if ${name} is mounted with NaN as an argument`, async () => { + render(, scratch); + expect(console.warn).to.be.calledOnce; + expect(console.warn.args[0]).to.match( + /Hooks should not be called with NaN in the dependency array/ + ); + }); + + it(`should error if ${name} is updated with NaN as an argument`, async () => { + render(, scratch); + + scratch.querySelector('button').click(); + rerender(); + + expect(console.warn).to.be.calledOnce; + expect(console.warn.args[0]).to.match( + /Hooks should not be called with NaN in the dependency array/ + ); + }); + } + + /** @type {HTMLElement} */ + let scratch; + /** @type {() => void} */ + let rerender; + let warnings = []; + + beforeEach(() => { + scratch = setupScratch(); + rerender = setupRerender(); + warnings = []; + sinon.stub(console, 'warn').callsFake(w => warnings.push(w)); + }); + + afterEach(() => { + teardown(scratch); + console.warn.restore(); + }); + + validateHook('useEffect', arg => useEffect(() => {}, [arg])); + validateHook('useLayoutEffect', arg => useLayoutEffect(() => {}, [arg])); + validateHook('useCallback', arg => useCallback(() => {}, [arg])); + validateHook('useMemo', arg => useMemo(() => {}, [arg])); + + const ref = createRef(); + validateHook('useImperativeHandle', arg => { + useImperativeHandle(ref, () => undefined, [arg]); + }); +}); diff --git a/demo/contenteditable.jsx b/demo/contenteditable.jsx new file mode 100644 index 0000000000..27862461fc --- /dev/null +++ b/demo/contenteditable.jsx @@ -0,0 +1,24 @@ +import { useState } from 'preact/hooks'; + +export default function Contenteditable() { + const [value, setValue] = useState("Hey there
    I'm editable!"); + + return ( +
    +
    + +
    +
    setValue(e.currentTarget.innerHTML)} + dangerouslySetInnerHTML={{ __html: value }} + /> +
    + ); +} diff --git a/demo/context.jsx b/demo/context.jsx new file mode 100644 index 0000000000..477a24e1a0 --- /dev/null +++ b/demo/context.jsx @@ -0,0 +1,69 @@ +// eslint-disable-next-line no-unused-vars +import { Component, createContext } from 'preact'; +const { Provider, Consumer } = createContext(); + +class ThemeProvider extends Component { + state = { + value: this.props.value + }; + + onClick = () => { + this.setState(prev => ({ + value: + prev.value === this.props.value ? this.props.next : this.props.value + })); + }; + + render() { + return ( +
    + + {this.props.children} +
    + ); + } +} + +class Child extends Component { + shouldComponentUpdate() { + return false; + } + + render() { + return ( + <> +

    (blocked update)

    + {this.props.children} + + ); + } +} + +export default class ContextDemo extends Component { + render() { + return ( + + + + {data => ( +
    +

    + current theme: {data} +

    + + + {data => ( +

    + current sub theme: {data} +

    + )} +
    +
    +
    + )} +
    +
    +
    + ); + } +} diff --git a/demo/devtools.jsx b/demo/devtools.jsx new file mode 100644 index 0000000000..275b4e36c5 --- /dev/null +++ b/demo/devtools.jsx @@ -0,0 +1,34 @@ +import { Component, memo, Suspense, lazy } from 'react'; + +function Foo() { + return
    I'm memoed
    ; +} + +function LazyComp() { + return
    I'm (fake) lazy loaded
    ; +} + +const Lazy = lazy(() => Promise.resolve({ default: LazyComp })); + +const Memoed = memo(Foo); + +export default class DevtoolsDemo extends Component { + render() { + return ( +
    +

    memo()

    +

    + functional component: +

    + +

    lazy()

    +

    + functional component: +

    + Loading (fake) lazy loaded component...
    }> + + +
    + ); + } +} diff --git a/demo/fragments.jsx b/demo/fragments.jsx new file mode 100644 index 0000000000..27a652e8a3 --- /dev/null +++ b/demo/fragments.jsx @@ -0,0 +1,26 @@ +import { Component } from 'preact'; + +export default class FragmentComp extends Component { + state = { number: 0 }; + + componentDidMount() { + setInterval(_ => this.updateChildren(), 1000); + } + + updateChildren() { + this.setState(state => ({ number: state.number + 1 })); + } + + render(props, state) { + return ( +
    +
    {state.number}
    + <> +
    one
    +
    {state.number}
    +
    three
    + +
    + ); + } +} diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000000..c4d4fe0d68 --- /dev/null +++ b/demo/index.html @@ -0,0 +1,13 @@ + + + + + + + Preact Demo + + +
    + + + diff --git a/demo/index.jsx b/demo/index.jsx new file mode 100644 index 0000000000..dda0aa63c4 --- /dev/null +++ b/demo/index.jsx @@ -0,0 +1,197 @@ +import { render, Component, Fragment } from 'preact'; +// import renderToString from 'preact-render-to-string'; +import './style.scss'; +import { Router, Link } from 'preact-router'; +import Pythagoras from './pythagoras'; +import Spiral from './spiral'; +import Reorder from './reorder'; +import Todo from './todo'; +import Fragments from './fragments'; +import Context from './context'; +import installLogger from './logger'; +import ProfilerDemo from './profiler'; +import KeyBug from './key_bug'; +import StateOrderBug from './stateOrderBug'; +import PeopleBrowser from './people'; +import StyledComp from './styled-components'; +import { initDevTools } from 'preact/devtools/src/devtools'; +import { initDebug } from 'preact/debug/src/debug'; +import DevtoolsDemo from './devtools'; +import SuspenseDemo from './suspense'; +import Redux from './redux'; +import TextFields from './textFields'; +import ReduxBug from './reduxUpdate'; +import SuspenseRouterBug from './suspense-router'; +import NestedSuspenseBug from './nested-suspense'; +import Contenteditable from './contenteditable'; +import { MobXDemo } from './mobx'; +import Zustand from './zustand'; +import ReduxToolkit from './redux-toolkit'; + +let isBenchmark = /(\/spiral|\/pythagoras|[#&]bench)/g.test( + window.location.href +); +if (!isBenchmark) { + // eslint-disable-next-line no-console + console.log('Enabling devtools and debug'); + initDevTools(); + initDebug(); +} + +// mobx-state-tree fix +window.setImmediate = setTimeout; + +class Home extends Component { + render() { + return ( +
    +

    Hello

    +
    + ); + } +} + +class DevtoolsWarning extends Component { + onClick = () => { + window.location.reload(); + }; + + render() { + return ( + + ); + } +} + +class App extends Component { + render({ url }) { + return ( +
    +
    + +
    +
    + + + + +
    + {!isBenchmark ? : } +
    +
    + {!isBenchmark ? : } +
    + + + + + + + + + + + + + + + + + + + +
    +
    +
    + ); + } +} + +function EmptyFragment() { + return ; +} + +// document.body.innerHTML = renderToString(); +// document.body.firstChild.setAttribute('is-ssr', 'true'); + +installLogger( + String(localStorage.LOG) === 'true' || location.href.match(/logger/), + String(localStorage.CONSOLE) === 'true' || location.href.match(/console/) +); + +render(, document.body); diff --git a/demo/key_bug.jsx b/demo/key_bug.jsx new file mode 100644 index 0000000000..c51aea23d0 --- /dev/null +++ b/demo/key_bug.jsx @@ -0,0 +1,32 @@ +import { Component } from 'preact'; + +function Foo(props) { + return
    This is: {props.children}
    ; +} + +export default class KeyBug extends Component { + constructor() { + super(); + this.onClick = this.onClick.bind(this); + this.state = { active: false }; + } + + onClick() { + this.setState(prev => ({ active: !prev.active })); + } + + render() { + return ( +
    + {this.state.active && foo} +

    Hello World

    +
    + + bar bar + +
    + +
    + ); + } +} diff --git a/demo/list.jsx b/demo/list.jsx new file mode 100644 index 0000000000..2ef968c2b6 --- /dev/null +++ b/demo/list.jsx @@ -0,0 +1,63 @@ +import { h, render } from 'preact'; +import htm from 'htm'; +import './style.css'; + +const html = htm.bind(h); +const createRoot = parent => ({ + render: v => render(v, parent) +}); + +function List({ items, renders, useKeys, useCounts, update }) { + const toggleKeys = () => update({ useKeys: !useKeys }); + const toggleCounts = () => update({ useCounts: !useCounts }); + const swap = () => { + const u = { items: items.slice() }; + u.items[1] = items[8]; + u.items[8] = items[1]; + update(u); + }; + return html` +
    + + + + +
      + ${items.map( + (item, i) => html` +
    • + ${item.name} ${useCounts ? ` (${renders} renders)` : ''} +
    • + ` + )} +
    +
    + `; +} + +const root = createRoot(document.body); + +let data = { + items: new Array(1000).fill(null).map((x, i) => ({ name: `Item ${i + 1}` })), + renders: 0, + useKeys: false, + useCounts: false +}; + +function update(partial) { + if (partial) Object.assign(data, partial); + data.renders++; + data.update = update; + root.render(List(data)); +} + +update(); diff --git a/demo/logger.jsx b/demo/logger.jsx new file mode 100644 index 0000000000..4a3a088440 --- /dev/null +++ b/demo/logger.jsx @@ -0,0 +1,170 @@ +export default function logger(logStats, logConsole) { + if (!logStats && !logConsole) { + return; + } + + const consoleBuffer = new ConsoleBuffer(); + + let calls = {}; + let lock = true; + + function serialize(obj) { + if (obj instanceof Text) return '#text'; + if (obj instanceof Element) return `<${obj.localName}>`; + if (obj === document) return 'document'; + return Object.prototype.toString.call(obj).replace(/(^\[object |\]$)/g, ''); + } + + function count(key) { + if (lock === true) return; + calls[key] = (calls[key] || 0) + 1; + + if (logConsole) { + consoleBuffer.log(key); + } + } + + function logCall(obj, method, name) { + let old = obj[method]; + obj[method] = function () { + let c = ''; + for (let i = 0; i < arguments.length; i++) { + if (c) c += ', '; + c += serialize(arguments[i]); + } + count(`${serialize(this)}.${method}(${c})`); + return old.apply(this, arguments); + }; + } + + logCall(document, 'createElement'); + logCall(document, 'createElementNS'); + logCall(Element.prototype, 'remove'); + logCall(Element.prototype, 'appendChild'); + logCall(Element.prototype, 'removeChild'); + logCall(Element.prototype, 'insertBefore'); + logCall(Element.prototype, 'replaceChild'); + logCall(Element.prototype, 'setAttribute'); + logCall(Element.prototype, 'setAttributeNS'); + logCall(Element.prototype, 'removeAttribute'); + logCall(Element.prototype, 'removeAttributeNS'); + let d = + Object.getOwnPropertyDescriptor(CharacterData.prototype, 'data') || + Object.getOwnPropertyDescriptor(Node.prototype, 'data'); + Object.defineProperty(Text.prototype, 'data', { + get() { + let value = d.get.call(this); + count(`get #text.data`); + return value; + }, + set(v) { + count(`set #text.data`); + return d.set.call(this, v); + } + }); + + let root; + function setup() { + if (!logStats) return; + + lock = true; + root = document.createElement('table'); + root.style.cssText = + 'position: fixed; right: 0; top: 0; z-index:999; background: #000; font-size: 12px; color: #FFF; opacity: 0.9; white-space: nowrap;'; + let header = document.createElement('thead'); + header.innerHTML = + '
    '; + root.tableBody = document.createElement('tbody'); + root.appendChild(root.tableBody); + root.appendChild(header); + document.documentElement.appendChild(root); + let btn = document.getElementById('clear-logs'); + btn.addEventListener('click', () => { + for (let key in calls) { + calls[key] = 0; + } + }); + lock = false; + } + + let rows = {}; + function createRow(id) { + let row = document.createElement('tr'); + row.key = document.createElement('td'); + row.key.textContent = id; + row.appendChild(row.key); + row.value = document.createElement('td'); + row.value.textContent = ' '; + row.appendChild(row.value); + root.tableBody.appendChild(row); + return (rows[id] = row); + } + + function insertInto(parent) { + parent.appendChild(root); + } + + function remove() { + clearInterval(updateTimer); + } + + function update() { + if (!logStats) return; + + lock = true; + for (let i in calls) { + if (calls.hasOwnProperty(i)) { + let row = rows[i] || createRow(i); + row.value.firstChild.nodeValue = calls[i]; + } + } + lock = false; + } + + let updateTimer = setInterval(update, 50); + + setup(); + lock = false; + return { insertInto, update, remove }; +} + +/** + * Logging to the console significantly affects performance. + * Buffer calls to console and replay them at the end of the + * current stack + * @extends {Console} + */ +class ConsoleBuffer { + constructor() { + /** @type {Array<[string, any[]]>} */ + this.buffer = []; + this.deferred = null; + + for (let methodName of Object.keys(console)) { + this[methodName] = this.proxy(methodName); + } + } + + proxy(methodName) { + return (...args) => { + this.buffer.push([methodName, args]); + this.deferFlush(); + }; + } + + deferFlush() { + if (this.deferred == null) { + this.deferred = Promise.resolve() + .then(() => this.flush()) + .then(() => (this.deferred = null)); + } + } + + flush() { + let method; + while ((method = this.buffer.shift())) { + let [name, args] = method; + console[name](...args); + } + } +} diff --git a/demo/mobx.jsx b/demo/mobx.jsx new file mode 100644 index 0000000000..9e95bd20e3 --- /dev/null +++ b/demo/mobx.jsx @@ -0,0 +1,75 @@ +import React, { forwardRef, useRef, useState } from 'react'; +import { decorate, observable } from 'mobx'; +import { observer, useObserver } from 'mobx-react'; +import 'mobx-react-lite/batchingForReactDom'; + +class Todo { + constructor() { + this.id = Math.random(); + this.title = 'initial'; + this.finished = false; + } +} +decorate(Todo, { + title: observable, + finished: observable +}); + +const Forward = observer( + // eslint-disable-next-line react/display-name + forwardRef(({ todo }, ref) => { + return ( +

    + Forward: "{todo.title}" {'' + todo.finished} +

    + ); + }) +); + +const todo = new Todo(); + +const TodoView = observer(({ todo }) => { + return ( +

    + Todo View: "{todo.title}" {'' + todo.finished} +

    + ); +}); + +const HookView = ({ todo }) => { + return useObserver(() => { + return ( +

    + Todo View: "{todo.title}" {'' + todo.finished} +

    + ); + }); +}; + +export function MobXDemo() { + const ref = useRef(null); + let [v, set] = useState(0); + + const success = ref.current && ref.current.nodeName === 'P'; + + return ( +
    + { + todo.title = e.target.value; + set(v + 1); + }} + /> +

    + + {success ? 'SUCCESS' : 'FAIL'} + +

    + + + +
    + ); +} diff --git a/demo/nested-suspense/addnewcomponent.jsx b/demo/nested-suspense/addnewcomponent.jsx new file mode 100644 index 0000000000..e3e469591b --- /dev/null +++ b/demo/nested-suspense/addnewcomponent.jsx @@ -0,0 +1,5 @@ +import { createElement } from 'react'; + +export default function AddNewComponent({ appearance }) { + return
    AddNewComponent (component #{appearance})
    ; +} diff --git a/demo/nested-suspense/component-container.jsx b/demo/nested-suspense/component-container.jsx new file mode 100644 index 0000000000..d81f4e8eaf --- /dev/null +++ b/demo/nested-suspense/component-container.jsx @@ -0,0 +1,17 @@ +import { lazy } from 'react'; + +const pause = timeout => + new Promise(d => setTimeout(d, timeout), console.log(timeout)); + +const SubComponent = lazy(() => + pause(Math.random() * 1000).then(() => import('./subcomponent.jsx')) +); + +export default function ComponentContainer({ appearance }) { + return ( +
    + GenerateComponents (component #{appearance}) + +
    + ); +} diff --git a/demo/nested-suspense/dropzone.jsx b/demo/nested-suspense/dropzone.jsx new file mode 100644 index 0000000000..c4a28b4a55 --- /dev/null +++ b/demo/nested-suspense/dropzone.jsx @@ -0,0 +1,5 @@ +import { createElement } from 'react'; + +export default function DropZone({ appearance }) { + return
    DropZone (component #{appearance})
    ; +} diff --git a/demo/nested-suspense/editor.jsx b/demo/nested-suspense/editor.jsx new file mode 100644 index 0000000000..253d130451 --- /dev/null +++ b/demo/nested-suspense/editor.jsx @@ -0,0 +1,5 @@ +import { createElement } from 'react'; + +export default function Editor({ children }) { + return
    {children}
    ; +} diff --git a/demo/nested-suspense/index.jsx b/demo/nested-suspense/index.jsx new file mode 100644 index 0000000000..5e41673f96 --- /dev/null +++ b/demo/nested-suspense/index.jsx @@ -0,0 +1,69 @@ +import { createElement, Suspense, lazy, Component } from 'react'; + +const Loading = function () { + return
    Loading...
    ; +}; +const Error = function ({ resetState }) { + return ( +
    + Error!  + + Reset app + +
    + ); +}; + +const pause = timeout => + new Promise(d => setTimeout(d, timeout), console.log(timeout)); + +const DropZone = lazy(() => + pause(Math.random() * 1000).then(() => import('./dropzone.jsx')) +); +const Editor = lazy(() => + pause(Math.random() * 1000).then(() => import('./editor.jsx')) +); +const AddNewComponent = lazy(() => + pause(Math.random() * 1000).then(() => import('./addnewcomponent.jsx')) +); +const GenerateComponents = lazy(() => + pause(Math.random() * 1000).then(() => import('./component-container.jsx')) +); + +export default class App extends Component { + state = { hasError: false }; + + static getDerivedStateFromError(error) { + // Update state so the next render will show the fallback UI. + console.warn(error); + return { hasError: true }; + } + + render() { + return this.state.hasError ? ( + this.setState({ hasError: false })} /> + ) : ( + }> + + +
    + }> + + + +
    + +
    + +
    Footer here
    +
    + ); + } +} diff --git a/demo/nested-suspense/subcomponent.jsx b/demo/nested-suspense/subcomponent.jsx new file mode 100644 index 0000000000..74d6d1ddca --- /dev/null +++ b/demo/nested-suspense/subcomponent.jsx @@ -0,0 +1,5 @@ +import { createElement } from 'react'; + +export default function SubComponent({ onClick }) { + return
    Lazy loaded sub component
    ; +} diff --git a/demo/old.js.bak b/demo/old.js.bak new file mode 100644 index 0000000000..7f2b7c0eb5 --- /dev/null +++ b/demo/old.js.bak @@ -0,0 +1,103 @@ + +// function createRoot(title) { +// let div = document.createElement('div'); +// let h2 = document.createElement('h2'); +// h2.textContent = title; +// div.appendChild(h2); +// document.body.appendChild(div); +// return div; +// } + + +/* +function logCall(obj, method, name) { + let old = obj[method]; + obj[method] = function(...args) { + console.log(`<${this.localName}>.`+(name || `${method}(${args})`)); + return old.apply(this, args); + }; +} + +logCall(HTMLElement.prototype, 'appendChild'); +logCall(HTMLElement.prototype, 'removeChild'); +logCall(HTMLElement.prototype, 'insertBefore'); +logCall(HTMLElement.prototype, 'replaceChild'); +logCall(HTMLElement.prototype, 'setAttribute'); +logCall(HTMLElement.prototype, 'removeAttribute'); +let d = Object.getOwnPropertyDescriptor(Node.prototype, 'nodeValue'); +Object.defineProperty(Text.prototype, 'nodeValue', { + get() { + let value = d.get.call(this); + console.log('get Text#nodeValue: ', value); + return value; + }, + set(v) { + console.log('set Text#nodeValue', v); + return d.set.call(this, v); + } +}); + + +render(( +
    +

    This is a test.

    + + +
    +), createRoot('Stateful component update demo:')); + + +class Foo extends Component { + componentDidMount() { + console.log('mounted'); + this.timer = setInterval( () => { + this.setState({ time: Date.now() }); + }, 5000); + } + componentWillUnmount() { + clearInterval(this.timer); + } + render(props, state, context) { + // console.log('rendering', props, state, context); + return + } +} + + +render(( +
    +

    This is a test.

    + + +
    +), createRoot('Stateful component update demo:')); + + +let items = []; +let count = 0; +let three = createRoot('Top-level render demo:'); + +setInterval( () => { + if (count++ %20 < 10 ) { + items.push(
  • item #{items.length}
  • ); + } + else { + items.shift(); + } + + render(( +
    +

    This is a test.

    + +
      {items}
    +
    + ), three); +}, 5000); + +// Mount the top-level component to the DOM: +render(
    , document.body); +*/ diff --git a/demo/package-lock.json b/demo/package-lock.json new file mode 100644 index 0000000000..780a471ee0 --- /dev/null +++ b/demo/package-lock.json @@ -0,0 +1,2866 @@ +{ + "name": "demo", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "demo", + "dependencies": { + "@material-ui/core": "4.9.5", + "@reduxjs/toolkit": "^2.2.3", + "d3-scale": "^1.0.7", + "d3-selection": "^1.2.0", + "htm": "2.1.1", + "mobx": "^5.15.4", + "mobx-react": "^6.2.2", + "mobx-state-tree": "^3.16.0", + "preact-render-to-string": "^5.0.2", + "preact-router": "^3.0.0", + "react-redux": "^7.1.0", + "react-router": "^5.0.1", + "react-router-dom": "^5.0.1", + "redux": "^4.0.1", + "styled-components": "^4.2.0", + "zustand": "^4.5.2" + }, + "devDependencies": { + "@babel/plugin-proposal-class-properties": "^7.0.0-beta.55", + "@babel/plugin-proposal-decorators": "^7.4.0", + "vite": "^5.4.10" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz", + "integrity": "sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.6.tgz", + "integrity": "sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==", + "dev": true, + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.6", + "@babel/helper-compilation-targets": "^7.18.6", + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helpers": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.6", + "@babel/types": "^7.18.6", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.18.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.7.tgz", + "integrity": "sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==", + "dependencies": { + "@babel/types": "^7.18.7", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz", + "integrity": "sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.6.tgz", + "integrity": "sha512-YfDzdnoxHGV8CzqHGyCbFvXg5QESPFkXlHtvdCkesLjjVMT2Adxe4FGUR5ChIb3DxSaXO12iIOCWoXdsUVwnqw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.6", + "@babel/helper-function-name": "^7.18.6", + "@babel/helper-member-expression-to-functions": "^7.18.6", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz", + "integrity": "sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz", + "integrity": "sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw==", + "dependencies": { + "@babel/template": "^7.18.6", + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.6.tgz", + "integrity": "sha512-CeHxqwwipekotzPDUuJOfIMtcIHBuc7WAzLmTYWctVigqS5RktNMQ5bEwQSuGewzYnCtTWa3BARXeiLxDTv+Ng==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.8.tgz", + "integrity": "sha512-che3jvZwIcZxrwh63VfnFTUzcAM9v/lznYkkRxIBGMPt1SudOKHAEec0SIRCfiuIzTcF7VGj/CaTT6gY4eWxvA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.6", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.8", + "@babel/types": "^7.18.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz", + "integrity": "sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.6.tgz", + "integrity": "sha512-fTf7zoXnUGl9gF25fXCWE26t7Tvtyn6H4hkLSYhATwJvw2uYxd3aoXplMSe0g9XbwK7bmxNes7+FGO0rB/xC0g==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.6", + "@babel/helper-member-expression-to-functions": "^7.18.6", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.18.6", + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.6.tgz", + "integrity": "sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.6", + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.8.tgz", + "integrity": "sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.18.6.tgz", + "integrity": "sha512-gAdhsjaYmiZVxx5vTMiRfj31nB7LhwBJFMSLzeDxc7X4tKLixup0+k9ughn0RcpBrv9E3PBaXJW7jF5TCihAOg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/plugin-syntax-decorators": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.18.6.tgz", + "integrity": "sha512-fqyLgjcxf/1yhyZ6A+yo1u9gJ7eleFQod2lkaUsF9DQ7sbbY3Ligym3L0+I2c0WmqNKDpoD9UTb1AKP3qRMOAQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.6.tgz", + "integrity": "sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.6.tgz", + "integrity": "sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.8.tgz", + "integrity": "sha512-UNg/AcSySJYR/+mIcJQDCv00T+AqRO7j/ZEJLzpaYtgM48rMg5MnkJgyNqkzo88+p4tfRvZJCEiwwfG6h4jkRg==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.7", + "@babel/helper-environment-visitor": "^7.18.6", + "@babel/helper-function-name": "^7.18.6", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.18.8", + "@babel/types": "^7.18.8", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.8.tgz", + "integrity": "sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "dependencies": { + "@emotion/memoize": "0.7.4" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", + "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@material-ui/core": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.5.tgz", + "integrity": "sha512-hVuUqw6847jcgRsUqzCiYCXcIJYhPUfM3gS9sNehTsbI0SF3tufLNO2B2Cgkuns8uOGy0nicD4p3L7JqhnEElg==", + "deprecated": "You can now upgrade to @mui/material. See the guide: https://mui.com/guides/migration-v4/", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@material-ui/styles": "^4.9.0", + "@material-ui/system": "^4.9.3", + "@material-ui/types": "^5.0.0", + "@material-ui/utils": "^4.7.1", + "@types/react-transition-group": "^4.2.0", + "clsx": "^1.0.2", + "hoist-non-react-statics": "^3.3.2", + "popper.js": "^1.14.1", + "prop-types": "^15.7.2", + "react-is": "^16.8.0", + "react-transition-group": "^4.3.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/material-ui" + }, + "peerDependencies": { + "@types/react": "^16.8.6", + "react": "^16.8.0", + "react-dom": "^16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/styles": { + "version": "4.11.5", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.5.tgz", + "integrity": "sha512-o/41ot5JJiUsIETME9wVLAJrmIWL3j0R0Bj2kCOLbSfqEkKf0fmaPt+5vtblUh5eXr2S+J/8J3DaCb10+CzPGA==", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@emotion/hash": "^0.8.0", + "@material-ui/types": "5.1.0", + "@material-ui/utils": "^4.11.3", + "clsx": "^1.0.4", + "csstype": "^2.5.2", + "hoist-non-react-statics": "^3.3.2", + "jss": "^10.5.1", + "jss-plugin-camel-case": "^10.5.1", + "jss-plugin-default-unit": "^10.5.1", + "jss-plugin-global": "^10.5.1", + "jss-plugin-nested": "^10.5.1", + "jss-plugin-props-sort": "^10.5.1", + "jss-plugin-rule-value-function": "^10.5.1", + "jss-plugin-vendor-prefixer": "^10.5.1", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/material-ui" + }, + "peerDependencies": { + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/system": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.2.tgz", + "integrity": "sha512-6CSKu2MtmiJgcCGf6nBQpM8fLkuB9F55EKfbdTC80NND5wpTmKzwdhLYLH3zL4cLlK0gVaaltW7/wMuyTnN0Lw==", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@material-ui/utils": "^4.11.3", + "csstype": "^2.5.2", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/material-ui" + }, + "peerDependencies": { + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/types": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", + "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==", + "peerDependencies": { + "@types/react": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/utils": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.3.tgz", + "integrity": "sha512-ZuQPV4rBK/V1j2dIkSSEcH5uT6AaHuKWFfotADHsC0wVL1NLd2WkFCm4ZZbX33iO4ydl6V0GPngKm8HZQ2oujg==", + "dependencies": { + "@babel/runtime": "^7.4.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.0 || ^17.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.3.tgz", + "integrity": "sha512-76dll9EnJXg4EVcI5YNxZA/9hSAmZsFqzMmNRHvIlzw2WS/twfcVX3ysYrWGJMClwEmChQFC4yRq74tn6fdzRA==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.1" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/@reduxjs/toolkit/node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.25.0.tgz", + "integrity": "sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.25.0.tgz", + "integrity": "sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.25.0.tgz", + "integrity": "sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.25.0.tgz", + "integrity": "sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.25.0.tgz", + "integrity": "sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.25.0.tgz", + "integrity": "sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.25.0.tgz", + "integrity": "sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.25.0.tgz", + "integrity": "sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.25.0.tgz", + "integrity": "sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.25.0.tgz", + "integrity": "sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.25.0.tgz", + "integrity": "sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.25.0.tgz", + "integrity": "sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.25.0.tgz", + "integrity": "sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.25.0.tgz", + "integrity": "sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.25.0.tgz", + "integrity": "sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.25.0.tgz", + "integrity": "sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.25.0.tgz", + "integrity": "sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.25.0.tgz", + "integrity": "sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/node": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.4.tgz", + "integrity": "sha512-M0+G6V0Y4YV8cqzHssZpaNCqvYwlCiulmm0PwpNLF55r/+cT8Ol42CHRU1SEaYFH2rTwiiE1aYg/2g2rrtGdPA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "16.14.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.28.tgz", + "integrity": "sha512-83zBE6+XUVXsdL3iFzOyUewdauaU+KviKCHEGOgSW52coAuqW7tEKQM0E9+ZC0Zk6TELQ2/JgogPvp7FavzFwg==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-redux": { + "version": "7.1.24", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz", + "integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react/node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "node_modules/acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.2.tgz", + "integrity": "sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001366", + "electron-to-chromium": "^1.4.188", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg==" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001366", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001366.tgz", + "integrity": "sha512-yy7XLWCubDobokgzudpkKux8e0UOOnLHE6mlNJBzT3lZJz6s5atSEzjoL+fsCPkI0G8MP5uVdDx1ur/fXEWkZA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ], + "peer": true + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "optional": true, + "peer": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-2.3.2.tgz", + "integrity": "sha512-VOFaeZA053BqvvvqIA8c9n0+9vFppVBAHCp6JgFTtTMU3Mzi+XnelJ9XC9ul3BqFzZyQ5N+H0SnwsWT2Ebchxw==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^3.3.0" + } + }, + "node_modules/css-to-react-native/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + }, + "node_modules/css-vendor": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", + "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", + "dependencies": { + "@babel/runtime": "^7.8.3", + "is-in-browser": "^1.0.2" + } + }, + "node_modules/csstype": { + "version": "2.6.20", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz", + "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==" + }, + "node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "node_modules/d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, + "node_modules/d3-color": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" + }, + "node_modules/d3-format": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" + }, + "node_modules/d3-interpolate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", + "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", + "dependencies": { + "d3-color": "1" + } + }, + "node_modules/d3-scale": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.7.tgz", + "integrity": "sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw==", + "dependencies": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-color": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "node_modules/d3-selection": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + }, + "node_modules/d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + }, + "node_modules/d3-time-format": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", + "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", + "dependencies": { + "d3-time": "1" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dom-helpers/node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.189", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.189.tgz", + "integrity": "sha512-dQ6Zn4ll2NofGtxPXaDfY2laIa6NyCQdqXYHdwH90GJQW0LpJJib0ZU/ERtbb0XkBEmUD2eJtagbOie3pdMiPg==", + "dev": true, + "peer": true + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/htm": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/htm/-/htm-2.1.1.tgz", + "integrity": "sha512-jgvB9nvlOE5xpI1W+juW8DPCe+Pn7mRDNaE4EOQNFqROm0O3l5deOvefK8C8hUaL1OHIX45mDEBSj7BMt+22VQ==", + "peerDependencies": { + "preact": "*" + } + }, + "node_modules/hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" + }, + "node_modules/immer": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.4.tgz", + "integrity": "sha512-cuBuGK40P/sk5IzWa9QPUaAdvPHjkk1c+xYsd9oZw+YQQEV+10G0P5uMpGctZZKnyQ+ibRO08bD25nWLmYi2pw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==" + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true, + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jss": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.9.0.tgz", + "integrity": "sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "csstype": "^3.0.2", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/jss" + } + }, + "node_modules/jss-plugin-camel-case": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz", + "integrity": "sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-default-unit": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz", + "integrity": "sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-global": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz", + "integrity": "sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-nested": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz", + "integrity": "sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-props-sort": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz", + "integrity": "sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-rule-value-function": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz", + "integrity": "sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-vendor-prefixer": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz", + "integrity": "sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "css-vendor": "^2.0.8", + "jss": "10.9.0" + } + }, + "node_modules/jss/node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, + "node_modules/merge-anything": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-2.4.4.tgz", + "integrity": "sha512-l5XlriUDJKQT12bH+rVhAHjwIuXWdAIecGwsYjv2LJo+dA1AeRTmeQS+3QBpO6lEthBMDi2IUMpLC1yyRvGlwQ==", + "dependencies": { + "is-what": "^3.3.1" + } + }, + "node_modules/mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" + }, + "peerDependencies": { + "prop-types": "^15.0.0", + "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/mobx": { + "version": "5.15.7", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-5.15.7.tgz", + "integrity": "sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/mobx-react": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-6.3.1.tgz", + "integrity": "sha512-IOxdJGnRSNSJrL2uGpWO5w9JH5q5HoxEqwOF4gye1gmZYdjoYkkMzSGMDnRCUpN/BNzZcFoMdHXrjvkwO7KgaQ==", + "dependencies": { + "mobx-react-lite": "^2.2.0" + }, + "peerDependencies": { + "mobx": "^5.15.4 || ^4.15.4", + "react": "^16.8.0 || 16.9.0-alpha.0" + } + }, + "node_modules/mobx-react-lite": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-2.2.2.tgz", + "integrity": "sha512-2SlXALHIkyUPDsV4VTKVR9DW7K3Ksh1aaIv3NrNJygTbhXe2A9GrcKHZ2ovIiOp/BXilOcTYemfHHZubP431dg==", + "peerDependencies": { + "mobx": "^4.0.0 || ^5.0.0", + "react": "^16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/mobx-state-tree": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/mobx-state-tree/-/mobx-state-tree-3.17.3.tgz", + "integrity": "sha512-ph4ee/Lh1qUJqHEGkfdWdBAUGdG+VAu7xZbYX/+4qem5hSSpdeZYAJOcN3bhtgEH8Wh/ZxRpQVOLM0aMFXfBSw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": ">=4.8.0 <5.0.0 || >=5.8.0 <6.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true, + "peer": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/postcss": { + "version": "8.4.48", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.48.tgz", + "integrity": "sha512-GCRK8F6+Dl7xYniR5a4FYbpBzU8XnZVeowqsQFYdcXuSbChgiks7qybSkbvnaeqv0G0B+dd9/jJgH8kkLDQeEA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.10.0.tgz", + "integrity": "sha512-fszkg1iJJjq68I4lI8ZsmBiaoQiQHbxf1lNq+72EmC/mZOsFF5zn3k1yv9QGoFgIXzgsdSKtYymLJsrJPoamjQ==", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.1.tgz", + "integrity": "sha512-Wp3ner1aIVBpKg02C4AoLdBiw4kNaiFSYHr4wUF+fR7FWKAQzNri+iPfPp31sEhAtBfWoJrSxiEFzd5wp5zCgQ==", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/preact-router": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/preact-router/-/preact-router-3.2.1.tgz", + "integrity": "sha512-KEN2VN1DxUlTwzW5IFkF13YIA2OdQ2OvgJTkQREF+AA2NrHRLaGbB68EjS4IeZOa1shvQ1FvEm3bSLta4sXBhg==", + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + }, + "peerDependencies": { + "react": "^16.14.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-redux": { + "version": "7.2.8", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.8.tgz", + "integrity": "sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==", + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/react-router": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.3.tgz", + "integrity": "sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-Ov0tGPMBgqmbu5CDmN++tv2HQ9HlWDuWIIqn4b88gjlAN5IHI+4ZUZRcpz9Hl0azFIwihbLDYw1OiHGRo7ZIng==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.3", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", + "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redux": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", + "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, + "node_modules/reselect": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", + "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" + }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, + "node_modules/rollup": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.25.0.tgz", + "integrity": "sha512-uVbClXmR6wvx5R1M3Od4utyLUxrmOcEm3pAtMphn73Apq19PDtHpgZoEvqH2YnnaNUuvKmg2DgRd2Sqv+odyqg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.25.0", + "@rollup/rollup-android-arm64": "4.25.0", + "@rollup/rollup-darwin-arm64": "4.25.0", + "@rollup/rollup-darwin-x64": "4.25.0", + "@rollup/rollup-freebsd-arm64": "4.25.0", + "@rollup/rollup-freebsd-x64": "4.25.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.25.0", + "@rollup/rollup-linux-arm-musleabihf": "4.25.0", + "@rollup/rollup-linux-arm64-gnu": "4.25.0", + "@rollup/rollup-linux-arm64-musl": "4.25.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.25.0", + "@rollup/rollup-linux-riscv64-gnu": "4.25.0", + "@rollup/rollup-linux-s390x-gnu": "4.25.0", + "@rollup/rollup-linux-x64-gnu": "4.25.0", + "@rollup/rollup-linux-x64-musl": "4.25.0", + "@rollup/rollup-win32-arm64-msvc": "4.25.0", + "@rollup/rollup-win32-ia32-msvc": "4.25.0", + "@rollup/rollup-win32-x64-msvc": "4.25.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "peer": true + }, + "node_modules/sass": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.53.0.tgz", + "integrity": "sha512-zb/oMirbKhUgRQ0/GFz8TSAwRq2IlR29vOUJZOx0l8sV+CkHUfHa4u5nqrG+1VceZp7Jfj59SVW9ogdhTvJDcQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/styled-components": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-4.4.1.tgz", + "integrity": "sha512-RNqj14kYzw++6Sr38n7197xG33ipEOktGElty4I70IKzQF1jzaD1U4xQ+Ny/i03UUhHlC5NWEO+d8olRCDji6g==", + "hasInstallScript": true, + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@emotion/is-prop-valid": "^0.8.1", + "@emotion/unitless": "^0.7.0", + "babel-plugin-styled-components": ">= 1", + "css-to-react-native": "^2.2.2", + "memoize-one": "^5.0.0", + "merge-anything": "^2.2.4", + "prop-types": "^15.5.4", + "react-is": "^16.6.0", + "stylis": "^3.5.0", + "stylis-rule-sheet": "^0.0.10", + "supports-color": "^5.5.0" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, + "node_modules/stylis": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", + "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==" + }, + "node_modules/stylis-rule-sheet": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", + "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==", + "peerDependencies": { + "stylis": "^3.5.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/terser": { + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/tiny-invariant": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz", + "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz", + "integrity": "sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "peer": true, + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, + "node_modules/vite": { + "version": "5.4.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", + "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/zustand": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz", + "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + } + } +} diff --git a/demo/package.json b/demo/package.json new file mode 100644 index 0000000000..40aeb181e1 --- /dev/null +++ b/demo/package.json @@ -0,0 +1,36 @@ +{ + "name": "demo", + "main": "index.js", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@babel/plugin-proposal-class-properties": "^7.0.0-beta.55", + "@babel/plugin-proposal-decorators": "^7.4.0", + "vite": "^5.4.10" + }, + "dependencies": { + "@material-ui/core": "4.9.5", + "@reduxjs/toolkit": "^2.2.3", + "d3-scale": "^1.0.7", + "d3-selection": "^1.2.0", + "htm": "2.1.1", + "mobx": "^5.15.4", + "mobx-react": "^6.2.2", + "mobx-state-tree": "^3.16.0", + "preact-render-to-string": "^5.0.2", + "preact-router": "^3.0.0", + "react-redux": "^7.1.0", + "react-router": "^5.0.1", + "react-router-dom": "^5.0.1", + "redux": "^4.0.1", + "styled-components": "^4.2.0", + "zustand": "^4.5.2" + }, + "volta": { + "extends": "../package.json" + } +} diff --git a/demo/people/Readme.md b/demo/people/Readme.md new file mode 100644 index 0000000000..c18a1cba8f --- /dev/null +++ b/demo/people/Readme.md @@ -0,0 +1,3 @@ +# People demo page + +This section of our demo was originally made by [phaux](https://github.com/phaux) in the [web-app-boilerplate](https://github.com/phaux/web-app-boilerplate) repo. It has been slightly modified from it's original to better work inside of our demo app diff --git a/demo/people/index.tsx b/demo/people/index.tsx new file mode 100644 index 0000000000..9fd0de06cc --- /dev/null +++ b/demo/people/index.tsx @@ -0,0 +1,59 @@ +import { observer } from 'mobx-react'; +import { Component } from 'preact'; +import { Profile } from './profile'; +import { Link, Route, Router } from './router'; +import { store } from './store'; + +import './styles/index.scss'; + +@observer +export default class App extends Component { + componentDidMount() { + store.loadUsers().catch(console.error); + } + + render() { + return ( + +
    + +
    + + + +
    +
    +
    + ); + } +} diff --git a/demo/people/profile.tsx b/demo/people/profile.tsx new file mode 100644 index 0000000000..fb18f568f5 --- /dev/null +++ b/demo/people/profile.tsx @@ -0,0 +1,59 @@ +import { computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { Component } from 'preact'; +import { RouteChildProps } from './router'; +import { store } from './store'; + +export type ProfileProps = RouteChildProps; +@observer +export class Profile extends Component { + @observable id = ''; + @observable busy = false; + + componentDidMount() { + this.id = this.props.route; + } + + componentWillReceiveProps(props: ProfileProps) { + this.id = props.route; + } + + render() { + const user = this.user; + if (user == null) return null; + return ( +
    + +

    + {user.name.first} {user.name.last} +

    +
    +

    + {user.gender === 'female' ? '👩' : '👨'} {user.id} +

    +

    🖂 {user.email}

    +
    +

    + +

    +
    + ); + } + + @computed get user() { + return store.users.find(u => u.id === this.id); + } + + remove = async () => { + this.busy = true; + await new Promise(cb => setTimeout(cb, 1500)); + store.deleteUser(this.id); + this.busy = false; + }; +} diff --git a/demo/people/router.tsx b/demo/people/router.tsx new file mode 100644 index 0000000000..56fc4205bf --- /dev/null +++ b/demo/people/router.tsx @@ -0,0 +1,153 @@ +import { + ComponentChild, + ComponentFactory, + createContext, + FunctionalComponent, + h, + JSX +} from 'preact'; +import { + useCallback, + useContext, + useEffect, + useMemo, + useState +} from 'preact/hooks'; + +export type RouterData = { + match: string[]; + path: string[]; + navigate(path: string): void; +}; + +const RouterContext = createContext({ + match: [], + path: [], + navigate() {} +}); +export const useRouter = () => useContext(RouterContext); + +const useLocation = (cb: () => void) => { + useEffect(() => { + window.addEventListener('popstate', cb); + return () => { + window.removeEventListener('popstate', cb); + }; + }, [cb]); +}; + +export const Router: FunctionalComponent = props => { + const [path, setPath] = useState(location.pathname); + + const update = useCallback(() => { + setPath(location.pathname); + }, [setPath]); + + useLocation(update); + + const navigate = useCallback( + (path: string) => { + history.pushState(null, '', path); + update(); + }, + [update] + ); + + const router = useMemo( + () => ({ + match: [], + navigate, + path: path.split('/').filter(Boolean) + }), + [navigate, path] + ); + + return ; +}; + +export type RouteChildProps = { route: string }; +export type RouteProps = { + component?: ComponentFactory; + match: string; + render?(route: string): ComponentChild; +}; +export const Route: FunctionalComponent = props => { + const router = useRouter(); + const [dir, ...subpath] = router.path; + + if (dir == null) return null; + if (props.match !== '*' && dir !== props.match) return null; + + const children = useMemo(() => { + if (props.component) return ; + if (props.render) return props.render(dir); + return props.children; + }, [props.component, props.render, props.children, dir]); + + const innerRouter = useMemo( + () => ({ + ...router, + match: [...router.match, dir], + path: subpath + }), + [router.match, dir, subpath.join('/')] + ); + + return ; +}; + +export type LinkProps = JSX.HTMLAttributes & { + active?: boolean | string; +}; +export const Link: FunctionalComponent = props => { + const router = useRouter(); + + const classProps = [props.class, props.className]; + const originalClasses = useMemo(() => { + const classes = []; + for (const prop of classProps) if (prop) classes.push(...prop.split(/\s+/)); + return classes; + }, classProps); + + const activeClass = useMemo(() => { + if (!props.active || props.href == null) return undefined; + const href = props.href.split('/').filter(Boolean); + const path = + props.href[0] === '/' ? [...router.match, ...router.path] : router.path; + const isMatch = href.every((dir, i) => dir === path[i]); + if (isMatch) return props.active === true ? 'active' : props.active; + }, [originalClasses, props.active, props.href, router.match, router.path]); + + const classes = + activeClass == null ? originalClasses : [...originalClasses, activeClass]; + + const getHref = useCallback(() => { + if (props.href == null || props.href[0] === '/') return props.href; + const path = props.href.split('/').filter(Boolean); + return '/' + [...router.match, ...path].join('/'); + }, [router.match, props.href]); + + const handleClick = useCallback( + (ev: MouseEvent) => { + const href = getHref(); + if (props.onClick != null) props.onClick(ev); + if (ev.defaultPrevented) return; + if (href == null) return; + if (ev.button !== 0) return; + if (props.target != null && props.target !== '_self') return; + if (ev.metaKey || ev.altKey || ev.ctrlKey || ev.shiftKey) return; + ev.preventDefault(); + router.navigate(href); + }, + [getHref, router.navigate, props.onClick, props.target] + ); + + return ( + + ); +}; diff --git a/demo/people/store.ts b/demo/people/store.ts new file mode 100644 index 0000000000..0c3c81f362 --- /dev/null +++ b/demo/people/store.ts @@ -0,0 +1,85 @@ +import { flow, Instance, types } from 'mobx-state-tree'; + +const cmp = + (fn: (x: T) => U) => + (a: T, b: T): number => + fn(a) > fn(b) ? 1 : -1; + +const User = types.model({ + email: types.string, + gender: types.enumeration(['male', 'female']), + id: types.identifier, + name: types.model({ + first: types.string, + last: types.string + }), + picture: types.model({ + large: types.string + }) +}); + +const Store = types + .model({ + users: types.array(User), + usersOrder: types.enumeration(['name', 'id']) + }) + .views(self => ({ + getSortedUsers() { + if (self.usersOrder === 'name') + return self.users.slice().sort(cmp(x => x.name.first)); + if (self.usersOrder === 'id') + return self.users.slice().sort(cmp(x => x.id)); + throw Error(`Unknown ordering ${self.usersOrder}`); + } + })) + .actions(self => ({ + addUser: flow(function* () { + const data = yield fetch('https://randomuser.me/api?results=1') + .then(res => res.json()) + .then(data => + data.results.map((user: any) => ({ + ...user, + id: user.login.username + })) + ); + self.users.push(...data); + }), + loadUsers: flow(function* () { + const data = yield fetch( + `https://randomuser.me/api?seed=${12321}&results=12` + ) + .then(res => res.json()) + .then(data => + data.results.map((user: any) => ({ + ...user, + id: user.login.username + })) + ); + self.users.replace(data); + }), + deleteUser(id: string) { + const user = self.users.find(u => u.id === id); + if (user != null) self.users.remove(user); + }, + setUsersOrder(order: 'name' | 'id') { + self.usersOrder = order; + } + })); + +export type StoreType = Instance; +export const store = Store.create({ + usersOrder: 'name', + users: [] +}); + +// const { Provider, Consumer } = createContext(undefined as any) + +// export const StoreProvider: FunctionalComponent = props => { +// const store = Store.create({}) +// return +// } + +// export type StoreProps = {store: StoreType} +// export function injectStore(Child: AnyComponent): FunctionalComponent { +// return props => }/> +// } diff --git a/demo/people/styles/animations.scss b/demo/people/styles/animations.scss new file mode 100644 index 0000000000..c7ce8cb9c0 --- /dev/null +++ b/demo/people/styles/animations.scss @@ -0,0 +1,34 @@ +@keyframes popup { + from { + box-shadow: 0 0 0 black; + opacity: 0; + transform: scale(0.9); + } + to { + box-shadow: 0 30px 70px rgba(0, 0, 0, 0.5); + opacity: 1; + transform: none; + } +} + +@keyframes zoom { + from { + opacity: 0; + transform: scale(0.8); + } + to { + opacity: 1; + transform: none; + } +} + +@keyframes appear-from-left { + from { + opacity: 0; + transform: translateX(-25px); + } + to { + opacity: 1; + transform: none; + } +} diff --git a/demo/people/styles/app.scss b/demo/people/styles/app.scss new file mode 100644 index 0000000000..4473f9effd --- /dev/null +++ b/demo/people/styles/app.scss @@ -0,0 +1,100 @@ +#people-app { + position: relative; + overflow: hidden; + min-height: 100vh; + animation: popup 300ms cubic-bezier(0.3, 0.7, 0.3, 1) forwards; + background: var(--app-background); + --menu-width: 260px; + --menu-item-height: 50px; + + @media (min-width: 1280px) { + max-width: 1280px; + min-height: calc(100vh - 64px); + margin: 32px auto; + border-radius: 10px; + } + + > nav { + position: absolute; + display: flow-root; + width: var(--menu-width); + height: 100%; + background-color: var(--app-background-secondary); + overflow-x: hidden; + overflow-y: auto; + } + + > nav h4 { + padding-left: 16px; + font-weight: normal; + text-transform: uppercase; + } + + > nav ul { + position: relative; + } + + > nav li { + position: absolute; + width: 100%; + animation: zoom 200ms forwards; + opacity: 0; + transition: top 200ms; + } + + > nav li > a { + position: relative; + display: flex; + overflow: hidden; + flex-flow: row; + align-items: center; + margin-left: 16px; + border-right: 2px solid transparent; + border-bottom-left-radius: 48px; + border-top-left-radius: 48px; + text-transform: capitalize; + transition: border 500ms; + } + + > nav li > a:hover { + background-color: var(--app-highlight); + } + + > nav li > a::after { + position: absolute; + top: 0; + right: -2px; + bottom: 0; + left: 0; + background-image: radial-gradient( + circle, + var(--app-ripple) 1%, + transparent 1% + ); + background-position: center; + background-repeat: no-repeat; + background-size: 10000%; + content: ''; + opacity: 0; + transition: opacity 700ms, background 300ms; + } + + > nav li > a:active::after { + background-size: 100%; + opacity: 0.5; + transition: none; + } + + > nav li > a.active { + border-color: var(--app-primary); + background-color: var(--app-highlight); + } + + > nav li > a > * { + margin: 8px; + } + + #people-main { + padding-left: var(--menu-width); + } +} diff --git a/demo/people/styles/avatar.scss b/demo/people/styles/avatar.scss new file mode 100644 index 0000000000..437c6c482b --- /dev/null +++ b/demo/people/styles/avatar.scss @@ -0,0 +1,16 @@ +#people-app { + .avatar { + display: inline-block; + overflow: hidden; + width: var(--avatar-size, 32px); + height: var(--avatar-size, 32px); + background-color: var(--avatar-color, var(--app-primary)); + border-radius: 50%; + font-size: calc(var(--avatar-size, 32px) * 0.5); + line-height: var(--avatar-size, 32px); + object-fit: cover; + text-align: center; + text-transform: uppercase; + white-space: nowrap; + } +} diff --git a/demo/people/styles/button.scss b/demo/people/styles/button.scss new file mode 100644 index 0000000000..ce4313853f --- /dev/null +++ b/demo/people/styles/button.scss @@ -0,0 +1,115 @@ +#people-app { + button { + position: relative; + overflow: hidden; + min-width: 36px; + height: 36px; + padding: 0 16px; + border: none; + background-color: transparent; + border-radius: 4px; + color: var(--app-text); + font-family: 'Montserrat', sans-serif; + font-size: 14px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + transition: background 300ms, color 200ms; + white-space: nowrap; + } + + button::before { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: var(--app-ripple); + content: ''; + opacity: 0; + transition: opacity 200ms; + } + + button:hover:not(:disabled)::before { + opacity: 0.3; + transition: opacity 100ms; + } + + button:active:not(:disabled)::before { + opacity: 0.7; + transition: none; + } + + button::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-image: radial-gradient( + circle, + var(--app-ripple) 1%, + transparent 1% + ); + background-position: center; + background-repeat: no-repeat; + background-size: 20000%; + content: ''; + opacity: 0; + transition: opacity 700ms, background 400ms; + } + + button:active:not(:disabled)::after { + background-size: 100%; + opacity: 1; + transition: none; + } + + button.primary { + background-color: var(--app-primary); + box-shadow: 0 2px 6px var(--app-shadow); + } + + button.secondary { + background-color: var(--app-secondary); + box-shadow: 0 2px 6px var(--app-shadow); + } + + button:disabled { + color: var(--app-text-secondary); + } + + button.busy { + animation: stripes 500ms linear infinite; + background-image: repeating-linear-gradient( + 45deg, + var(--app-shadow) 0%, + var(--app-shadow) 25%, + transparent 25%, + transparent 50%, + var(--app-shadow) 50%, + var(--app-shadow) 75%, + transparent 75%, + transparent 100% + ); + color: var(--app-text); + /* letter-spacing: -.7em; */ + } + + button:disabled:not(.primary):not(.secondary).busy, + button:disabled.primary:not(.busy), + button:disabled.secondary:not(.busy) { + background-color: var(--app-background-disabled); + } + + @keyframes stripes { + from { + background-position-x: 0; + background-size: 16px 16px; + } + to { + background-position-x: 16px; + background-size: 16px 16px; + } + } +} diff --git a/demo/people/styles/index.scss b/demo/people/styles/index.scss new file mode 100644 index 0000000000..6d73aaa0a0 --- /dev/null +++ b/demo/people/styles/index.scss @@ -0,0 +1,169 @@ +@import 'app.scss'; +@import 'animations.scss'; +@import 'avatar.scss'; +@import 'profile.scss'; +@import 'button.scss'; + +// :root { +#people-app { + --app-background: #2f2b43; + --app-background-secondary: #353249; + --app-background-disabled: #555366; + --app-highlight: rgba(255, 255, 255, 0.1); + --app-ripple: rgba(255, 255, 255, 0.5); + --app-shadow: rgba(0, 0, 0, 0.15); + --app-text: #fff; + --app-text-secondary: #807e97; + --app-primary: #ff0087; + --app-secondary: #4d7cfe; + --app-tertiary: #00ec97; + --app-danger: #f3c835; + --spinner-size: 200px; +} + +* { + box-sizing: border-box; +} + +// body { +#people-app { + // display: flow-root; + // overflow: auto; + // min-height: 100vh; + // margin: 0; + // animation: background-light 5s ease-out forwards; + // /* very fancy background */ + // background: radial-gradient( + // circle 15px at 150px 90vh, + // rgba(255, 255, 255, 0.35), + // rgba(255, 255, 255, 0.35) 90%, + // transparent + // ), + // radial-gradient( + // circle 9px at 60px 50vh, + // rgba(255, 255, 255, 0.55), + // rgba(255, 255, 255, 0.55) 90%, + // transparent + // ), + // radial-gradient( + // circle 19px at 40vw 70px, + // rgba(255, 255, 255, 0.3), + // rgba(255, 255, 255, 0.3) 90%, + // transparent + // ), + // radial-gradient( + // circle 12px at 80vw 80px, + // rgba(255, 255, 255, 0.4), + // rgba(255, 255, 255, 0.4) 90%, + // transparent + // ), + // radial-gradient( + // circle 7px at 55vw calc(100vh - 95px), + // rgba(255, 255, 255, 0.6), + // rgba(255, 255, 255, 0.6) 90%, + // transparent + // ), + // radial-gradient( + // circle 14px at 25vw calc(100vh - 35px), + // rgba(255, 255, 255, 0.4), + // rgba(255, 255, 255, 0.4) 90%, + // transparent + // ), + // radial-gradient( + // circle 11px at calc(100vw - 95px) 55vh, + // rgba(255, 255, 255, 0.45), + // rgba(255, 255, 255, 0.45) 90%, + // transparent + // ), + // radial-gradient( + // circle 13px at calc(100vw - 35px) 85vh, + // rgba(255, 255, 255, 0.4), + // rgba(255, 255, 255, 0.4) 90%, + // transparent + // ), + // radial-gradient( + // circle 50vw at 0 -25%, + // rgba(255, 255, 255, 0.07), + // rgba(255, 255, 255, 0.07) 100%, + // transparent + // ), + // radial-gradient( + // circle 80vw at top left, + // rgba(255, 255, 255, 0.07), + // rgba(255, 255, 255, 0.07) 100%, + // transparent + // ), + // radial-gradient(circle at bottom right, #ef2fb8, transparent), + // radial-gradient(circle at top right, #c45af3, transparent), + // linear-gradient(#ee66ca, #ff47a6); + color: var(--app-text); + font-family: 'Montserrat', sans-serif; +} + +#people-app { + .spinner { + position: absolute; + top: 200px; + left: calc(50% - var(--spinner-size) / 2); + width: var(--spinner-size); + height: var(--spinner-size); + animation: zoom 250ms 500ms forwards ease-out; + opacity: 0; + transition: opacity 200ms, transform 200ms ease-in; + } + + .spinner.exit { + opacity: 0; + transform: scale(0.5); + } + + .spinner::before, + .spinner::after { + position: absolute; + top: 0; + left: 0; + width: calc(var(--spinner-size) / 3); + height: calc(var(--spinner-size) / 3); + animation: spinner 2s infinite ease-in-out; + background-color: rgba(255, 255, 255, 0.6); + content: ''; + } + + .spinner::after { + animation-delay: -1s; + } + + @keyframes spinner { + 25% { + transform: translateX(calc(var(--spinner-size) / 3 * 2 - 1px)) + rotate(-90deg) scale(0.5); + } + 50% { + transform: translateX(calc(var(--spinner-size) / 3 * 2 - 1px)) + translateY(calc(var(--spinner-size) / 3 * 2 - 1px)) rotate(-179deg); + } + 50.1% { + transform: translateX(calc(var(--spinner-size) / 3 * 2 - 1px)) + translateY(calc(var(--spinner-size) / 3 * 2 - 1px)) rotate(-180deg); + } + 75% { + transform: translateX(0) + translateY(calc(var(--spinner-size) / 3 * 2 - 1px)) rotate(-270deg) + scale(0.5); + } + 100% { + transform: rotate(-360deg); + } + } + + ul, + ol { + padding-left: 0; + list-style: none; + } + + a { + color: inherit; + text-decoration: none; + } +} diff --git a/demo/people/styles/profile.scss b/demo/people/styles/profile.scss new file mode 100644 index 0000000000..f74b76043b --- /dev/null +++ b/demo/people/styles/profile.scss @@ -0,0 +1,26 @@ +#people-app { + .profile { + display: flex; + flex-flow: column; + align-items: center; + margin: 32px 0; + animation: appear-from-left 0.5s forwards; + --avatar-size: 80px; + } + + .profile h2 { + text-transform: capitalize; + } + + .profile .details { + display: flex; + flex-flow: column; + align-items: stretch; + margin: 16px auto; + } + + .profile .details p { + margin-top: 8px; + margin-bottom: 8px; + } +} diff --git a/demo/preact.jsx b/demo/preact.jsx new file mode 100644 index 0000000000..eba291fbae --- /dev/null +++ b/demo/preact.jsx @@ -0,0 +1,39 @@ +import { + options, + createElement, + cloneElement, + Component as CevicheComponent, + render +} from 'preact'; + +options.vnode = vnode => { + vnode.nodeName = vnode.type; + vnode.attributes = vnode.props; + vnode.children = vnode._children || [].concat(vnode.props.children || []); +}; + +function asArray(arr) { + return Array.isArray(arr) ? arr : [arr]; +} + +function normalize(obj) { + if (Array.isArray(obj)) { + return obj.map(normalize); + } + if ('type' in obj && !('attributes' in obj)) { + obj.attributes = obj.props; + } + return obj; +} + +export function Component(props, context) { + CevicheComponent.call(this, props, context); + const render = this.render; + this.render = function (props, state, context) { + if (props.children) props.children = asArray(normalize(props.children)); + return render.call(this, props, state, context); + }; +} +Component.prototype = new CevicheComponent(); + +export { createElement, createElement as h, cloneElement, render }; diff --git a/demo/profiler.jsx b/demo/profiler.jsx new file mode 100644 index 0000000000..bb44269047 --- /dev/null +++ b/demo/profiler.jsx @@ -0,0 +1,88 @@ +import { createElement, Component, options } from 'preact'; + +function getPrimes(max) { + let sieve = [], + i, + j, + primes = []; + for (i = 2; i <= max; ++i) { + if (!sieve[i]) { + // i has not been marked -- it is prime + primes.push(i); + for (j = i << 1; j <= max; j += i) { + sieve[j] = true; + } + } + } + return primes.join(''); +} + +function Foo(props) { + return
    {props.children}
    ; +} + +function Bar() { + getPrimes(10000); + return ( +
    + ...yet another component +
    + ); +} + +function PrimeNumber(props) { + // Slow down rendering of this component + getPrimes(10); + + return ( +
    + I'm a slow component +
    + {props.children} +
    + ); +} + +export default class ProfilerDemo extends Component { + constructor() { + super(); + this.onClick = this.onClick.bind(this); + this.state = { counter: 0 }; + } + + componentDidMount() { + options._diff = vnode => (vnode.startTime = performance.now()); + options.diffed = vnode => (vnode.endTime = performance.now()); + } + + componentWillUnmount() { + delete options._diff; + delete options.diffed; + } + + onClick() { + this.setState(prev => ({ counter: ++prev.counter })); + } + + render() { + return ( +
    +

    ⚛ Preact

    +

    + Devtools Profiler integration 🕒 +

    + + + I'm a fast component + + + + I'm the fastest component 🎉 + Counter: {this.state.counter} +
    +
    + +
    + ); + } +} diff --git a/demo/pythagoras/index.jsx b/demo/pythagoras/index.jsx new file mode 100644 index 0000000000..627053b3b4 --- /dev/null +++ b/demo/pythagoras/index.jsx @@ -0,0 +1,84 @@ +import { Component } from 'preact'; +import { select as d3select, mouse as d3mouse } from 'd3-selection'; +import { scaleLinear } from 'd3-scale'; +import Pythagoras from './pythagoras'; + +export default class PythagorasDemo extends Component { + svg = { + width: 1280, + height: 600 + }; + + state = { + currentMax: 0, + baseW: 80, + heightFactor: 0, + lean: 0 + }; + + realMax = 11; + + svgRef = c => { + this.svgElement = c; + }; + + scaleFactor = scaleLinear().domain([this.svg.height, 0]).range([0, 0.8]); + + scaleLean = scaleLinear() + .domain([0, this.svg.width / 2, this.svg.width]) + .range([0.5, 0, -0.5]); + + onMouseMove = event => { + let [x, y] = d3mouse(this.svgElement); + + this.setState({ + heightFactor: this.scaleFactor(y), + lean: this.scaleLean(x) + }); + }; + + restart = () => { + this.setState({ currentMax: 0 }); + this.next(); + }; + + next = () => { + let { currentMax } = this.state; + + if (currentMax < this.realMax) { + this.setState({ currentMax: currentMax + 1 }); + this.timer = setTimeout(this.next, 500); + } + }; + + componentDidMount() { + this.selected = d3select(this.svgElement).on('mousemove', this.onMouseMove); + this.next(); + } + + componentWillUnmount() { + this.selected.on('mousemove', null); + clearTimeout(this.timer); + } + + render({}, { currentMax, baseW, heightFactor, lean }) { + let { width, height } = this.svg; + + return ( +
    + + + +
    + ); + } +} diff --git a/demo/pythagoras/pythagoras.jsx b/demo/pythagoras/pythagoras.jsx new file mode 100644 index 0000000000..01795c8f72 --- /dev/null +++ b/demo/pythagoras/pythagoras.jsx @@ -0,0 +1,96 @@ +import { interpolateViridis } from 'd3-scale'; + +Math.deg = function (radians) { + return radians * (180 / Math.PI); +}; + +const memoizedCalc = (function () { + const memo = {}; + + const key = ({ w, heightFactor, lean }) => `${w}-${heightFactor}-${lean}`; + + return args => { + let memoKey = key(args); + + if (memo[memoKey]) { + return memo[memoKey]; + } + + let { w, heightFactor, lean } = args; + let trigH = heightFactor * w; + + let result = { + nextRight: Math.sqrt(trigH ** 2 + (w * (0.5 + lean)) ** 2), + nextLeft: Math.sqrt(trigH ** 2 + (w * (0.5 - lean)) ** 2), + A: Math.deg(Math.atan(trigH / ((0.5 - lean) * w))), + B: Math.deg(Math.atan(trigH / ((0.5 + lean) * w))) + }; + + memo[memoKey] = result; + return result; + }; +})(); + +export default function Pythagoras({ + w, + x, + y, + heightFactor, + lean, + left, + right, + lvl, + maxlvl +}) { + if (lvl >= maxlvl || w < 1) { + return null; + } + + const { nextRight, nextLeft, A, B } = memoizedCalc({ + w, + heightFactor, + lean + }); + + let rotate = ''; + + if (left) { + rotate = `rotate(${-A} 0 ${w})`; + } else if (right) { + rotate = `rotate(${B} ${w} ${w})`; + } + + return ( + + + + + + + + ); +} diff --git a/demo/redux-toolkit.jsx b/demo/redux-toolkit.jsx new file mode 100644 index 0000000000..f33657cd1b --- /dev/null +++ b/demo/redux-toolkit.jsx @@ -0,0 +1,60 @@ +import { createElement } from 'preact'; +import { Provider, useSelector } from 'react-redux'; +import { configureStore, createSlice } from '@reduxjs/toolkit'; + +const initialState = { + value: 0 +}; +const counterSlice = createSlice({ + name: 'counter', + initialState, + reducers: { + increment: state => { + state.value += 1; + }, + decrement: state => { + state.value -= 1; + } + } +}); +const store = configureStore({ + reducer: { + counter: counterSlice.reducer + } +}); + +function Counter({ number }) { + const count = useSelector(state => state.counter.value); + return ( +
    + Counter #{number}:{count} +
    + ); +} + +export default function ReduxToolkit() { + function increment() { + store.dispatch(counterSlice.actions.increment()); + } + function decrement() { + store.dispatch(counterSlice.actions.decrement()); + } + function incrementAsync() { + setTimeout(() => { + store.dispatch(counterSlice.actions.increment()); + }, 1000); + } + return ( + +
    +

    Redux Toolkit

    +

    Counter

    + + + + + +
    +
    + ); +} diff --git a/demo/redux.jsx b/demo/redux.jsx new file mode 100644 index 0000000000..d63dfb73b8 --- /dev/null +++ b/demo/redux.jsx @@ -0,0 +1,48 @@ +import { createElement } from 'preact'; +import React from 'react'; +import { createStore } from 'redux'; +import { connect, Provider } from 'react-redux'; + +const store = createStore((state = { value: 0 }, action) => { + switch (action.type) { + case 'increment': + return { value: state.value + 1 }; + case 'decrement': + return { value: state.value - 1 }; + default: + return state; + } +}); + +class Child extends React.Component { + render() { + return ( +
    +
    Child #1: {this.props.foo}
    + +
    + ); + } +} +const ConnectedChild = connect(store => ({ foo: store.value }))(Child); + +class Child2 extends React.Component { + render() { + return
    Child #2: {this.props.foo}
    ; + } +} +const ConnectedChild2 = connect(store => ({ foo: store.value }))(Child2); + +export default function Redux() { + return ( +
    +

    Counter

    + + + +
    + + +
    + ); +} diff --git a/demo/reduxUpdate.jsx b/demo/reduxUpdate.jsx new file mode 100644 index 0000000000..37e1f9ce17 --- /dev/null +++ b/demo/reduxUpdate.jsx @@ -0,0 +1,61 @@ +import { createElement, Component } from 'preact'; +import { connect, Provider } from 'react-redux'; +import { createStore } from 'redux'; +import { HashRouter, Route, Link } from 'react-router-dom'; + +const store = createStore( + (state, action) => ({ ...state, display: action.display }), + { display: false } +); + +function _Redux({ showMe, counter }) { + if (!showMe) return null; + return
    showMe {counter}
    ; +} +const Redux = connect( + state => console.log('injecting', state.display) || { showMe: state.display } +)(_Redux); + +let display = false; +class Test extends Component { + componentDidUpdate(prevProps) { + if (this.props.start != prevProps.start) { + this.setState({ f: (this.props.start || 0) + 1 }); + setTimeout(() => this.setState({ i: (this.state.i || 0) + 1 })); + } + } + + render() { + const { f } = this.state; + return ( +
    + + Click me + + +
    + ); + } +} + +function App() { + return ( + + + } + /> + + + ); +} + +export default App; diff --git a/demo/reorder.jsx b/demo/reorder.jsx new file mode 100644 index 0000000000..35acc5ebef --- /dev/null +++ b/demo/reorder.jsx @@ -0,0 +1,104 @@ +import { createElement, Component } from 'preact'; + +function createItems(count = 10) { + let items = []; + for (let i = 0; i < count; i++) { + items.push({ + label: `Item #${i + 1}`, + key: i + 1 + }); + } + return items; +} + +function random() { + return Math.random() < 0.5 ? 1 : -1; +} + +export default class Reorder extends Component { + state = { + items: createItems(), + count: 1, + useKeys: false + }; + + shuffle = () => { + this.setState({ items: this.state.items.slice().sort(random) }); + }; + + swapTwo = () => { + let items = this.state.items.slice(), + first = Math.floor(Math.random() * items.length), + second; + do { + second = Math.floor(Math.random() * items.length); + } while (second === first); + let other = items[first]; + items[first] = items[second]; + items[second] = other; + this.setState({ items }); + }; + + reverse = () => { + this.setState({ items: this.state.items.slice().reverse() }); + }; + + setCount = e => { + this.setState({ count: Math.round(e.target.value) }); + }; + + rotate = () => { + let { items, count } = this.state; + items = items.slice(count).concat(items.slice(0, count)); + this.setState({ items }); + }; + + rotateBackward = () => { + let { items, count } = this.state, + len = items.length; + items = items.slice(len - count, len).concat(items.slice(0, len - count)); + this.setState({ items }); + }; + + toggleKeys = () => { + this.setState({ useKeys: !this.state.useKeys }); + }; + + renderItem = item => ( +
  • {item.label}
  • + ); + + render({}, { items, count, useKeys }) { + return ( +
    +
    + + + + + + + +
    +
      {items.map(this.renderItem)}
    +
    + ); + } +} diff --git a/demo/spiral.jsx b/demo/spiral.jsx new file mode 100644 index 0000000000..baaa052c43 --- /dev/null +++ b/demo/spiral.jsx @@ -0,0 +1,140 @@ +import { createElement, Component } from 'preact'; + +const COUNT = 500; +const LOOPS = 6; + +// Component.debounce = requestAnimationFrame; + +export default class Spiral extends Component { + state = { x: 0, y: 0, big: false, counter: 0 }; + + handleClick = e => { + console.log('click'); + }; + + increment = () => { + if (this.stop) return; + // this.setState({ counter: this.state.counter + 1 }, this.increment); + this.setState({ counter: this.state.counter + 1 }); + // this.forceUpdate(); + requestAnimationFrame(this.increment); + }; + + setMouse({ pageX: x, pageY: y }) { + this.setState({ x, y }); + return false; + } + + setBig(big) { + this.setState({ big }); + } + + componentDidMount() { + console.log('mount'); + + // let touch = navigator.maxTouchPoints > 1; + let touch = false; + + // set mouse position state on move: + addEventListener(touch ? 'touchmove' : 'mousemove', e => { + this.setMouse(e.touches ? e.touches[0] : e); + }); + + // holding the mouse down enables big mode: + addEventListener(touch ? 'touchstart' : 'mousedown', e => { + this.setBig(true); + e.preventDefault(); + }); + addEventListener(touch ? 'touchend' : 'mouseup', e => this.setBig(false)); + + requestAnimationFrame(this.increment); + } + + componentWillUnmount() { + console.log('unmount'); + this.stop = true; + } + + // componentDidUpdate() { + // // invoking setState() in componentDidUpdate() creates an animation loop: + // this.increment(); + // } + + // builds and returns a brand new DOM (every time) + render(props, { x, y, big, counter }) { + let max = + COUNT + + Math.round(Math.sin((counter / 90) * 2 * Math.PI) * COUNT * 0.5), + cursors = []; + + // the advantage of JSX is that you can use the entirety of JS to "template": + for (let i = max; i--; ) { + let f = (i / max) * LOOPS, + θ = f * 2 * Math.PI, + m = 20 + i * 2, + hue = (f * 255 + counter * 10) % 255; + cursors[i] = ( + + ); + } + + return ( +
    + + {cursors} +
    + ); + } +} + +/** Represents a single coloured dot. */ +class Cursor extends Component { + // get shared/pooled class object + getClass(big, label) { + let cl = 'cursor'; + if (big) cl += ' big'; + if (label) cl += ' label'; + return cl; + } + + // skip any pointless re-renders + shouldComponentUpdate(props) { + for (let i in props) + if (i !== 'children' && props[i] !== this.props[i]) return true; + return false; + } + + // first argument is "props", the attributes passed to + render({ x, y, label, color, big }) { + let inner = null; + if (label) + inner = ( + + {x},{y} + + ); + return ( +
    + {inner} +
    + ); + } +} + +// Addendum: disable dragging on mobile +addEventListener('touchstart', e => (e.preventDefault(), false)); diff --git a/demo/stateOrderBug.jsx b/demo/stateOrderBug.jsx new file mode 100644 index 0000000000..f71b5f2bf2 --- /dev/null +++ b/demo/stateOrderBug.jsx @@ -0,0 +1,76 @@ +import htm from 'htm'; +import { h } from 'preact'; +import { useState, useCallback } from 'preact/hooks'; + +const html = htm.bind(h); + +// configuration used to show behavior vs. workaround +let childFirst = true; +const Config = () => html` + +`; + +const Child = ({ items, setItems }) => { + let [pendingId, setPendingId] = useState(null); + if (!pendingId) { + setPendingId((pendingId = Math.random().toFixed(20).slice(2))); + } + + const onInput = useCallback( + evt => { + let val = evt.target.value, + _items = [...items, { _id: pendingId, val }]; + if (childFirst) { + setPendingId(null); + setItems(_items); + } else { + setItems(_items); + setPendingId(null); + } + }, + [childFirst, setPendingId, setItems, items, pendingId] + ); + + return html` +
    + ${items.map( + (item, idx) => html` + { + let val = evt.target.value, + _items = [...items]; + _items.splice(idx, 1, { ...item, val }); + setItems(_items); + }} + /> + ` + )} + + +
    + `; +}; + +const Parent = () => { + let [items, setItems] = useState([]); + return html` +
    <${Config} /><${Child} items=${items} setItems=${setItems} />
    + `; +}; + +export default Parent; diff --git a/demo/style.css b/demo/style.css new file mode 100644 index 0000000000..becadbf483 --- /dev/null +++ b/demo/style.css @@ -0,0 +1,24 @@ +html, +body { + font: 14px system-ui, sans-serif; +} +.list { + list-style: none; + padding: 0; +} +.list > li { + position: relative; + padding: 5px 10px; + animation: fadeIn 1s ease; +} +@keyframes fadeIn { + 0% { + box-shadow: inset 0 0 2px 2px red, 0 0 2px 2px red; + } +} +.list > .odd { + background-color: #def; +} +.list > .even { + background-color: #fed; +} diff --git a/demo/style.scss b/demo/style.scss new file mode 100644 index 0000000000..51e5c1bdf9 --- /dev/null +++ b/demo/style.scss @@ -0,0 +1,150 @@ +html, +body { + height: 100%; + margin: 0; + background: #eee; + font: 400 16px/1.3 'Helvetica Neue', helvetica, sans-serif; + text-rendering: optimizeSpeed; + color: #444; +} + +.app { + display: block; + flex-direction: column; + height: 100%; + + > header { + flex: 0; + background: #f9f9f9; + box-shadow: inset 0 -0.5px 0 0 rgba(0, 0, 0, 0.2), + 0 0.5px 0 0 rgba(255, 255, 255, 0.6); + + nav { + display: inline-block; + padding: 4px 7px; + + a { + display: inline-block; + margin: 2px; + padding: 4px 10px; + background-color: rgba(255, 255, 255, 0); + border-radius: 1em; + color: #6b1d8f; + text-decoration: none; + // transition: all 250ms ease; + transition: all 250ms cubic-bezier(0.2, 0, 0.4, 2); + &:hover { + background-color: rgba(255, 255, 255, 1); + box-shadow: 0 0 0 2px #6b1d8f; + } + &.active { + background-color: #6b1d8f; + color: white; + } + } + } + } + + > main { + flex: 1; + padding: 10px; + } +} + +h1 { + margin: 0; + color: #6b1d8f; + font-weight: 300; + font-size: 250%; +} + +input, +textarea { + box-sizing: border-box; + margin: 1px; + padding: 0.25em 0.5em; + background: #fff; + border: 1px solid #999; + border-radius: 3px; + font: inherit; + color: #000; + outline: none; + + &:focus { + border-color: #6b1d8f; + } +} + +button, +input[type='submit'], +input[type='reset'], +input[type='button'] { + box-sizing: border-box; + margin: 1px; + padding: 0.25em 0.8em; + background: #6b1d8f; + border: 1px solid #6b1d8f; + // border: none; + border-radius: 1.5em; + font: inherit; + color: white; + outline: none; + cursor: pointer; +} + +.cursor { + position: absolute; + left: 0; + top: 0; + width: 8px; + height: 8px; + margin: -5px 0 0 -5px; + border: 2px solid #f00; + border-radius: 50%; + transform-origin: 50% 50%; + pointer-events: none; + overflow: hidden; + font-size: 9px; + line-height: 25px; + text-indent: 15px; + white-space: nowrap; + + &:not(.label) { + contain: strict; + } + + &.label { + overflow: visible; + } + + // &.big { + // transform: scale(2); + // // width: 24px; + // // height: 24px; + // // margin: -13px 0 0 -13px; + // } + + .label { + position: absolute; + left: 0; + top: 0; + //transform: translateZ(0); + // z-index: 10; + } +} + +.animation-picker { + position: fixed; + display: inline-block; + right: 0; + top: 0; + padding: 10px; + background: #000; + color: #bbb; + z-index: 1000; + + select { + font-size: 100%; + margin-left: 5px; + } +} diff --git a/demo/styled-components.jsx b/demo/styled-components.jsx new file mode 100644 index 0000000000..f8433ba151 --- /dev/null +++ b/demo/styled-components.jsx @@ -0,0 +1,31 @@ +import { createElement } from 'preact'; +import styled, { css } from 'styled-components'; + +const Button = styled.button` + background: transparent; + border-radius: 3px; + border: 2px solid palevioletred; + color: palevioletred; + margin: 0.5em 1em; + padding: 0.25em 1em; + + ${props => + props.primary && + css` + background: palevioletred; + color: white; + `} +`; + +const Container = styled.div` + text-align: center; +`; + +export default function StyledComp() { + return ( + + + + + ); +} diff --git a/demo/suspense-router/bye.jsx b/demo/suspense-router/bye.jsx new file mode 100644 index 0000000000..a8561be447 --- /dev/null +++ b/demo/suspense-router/bye.jsx @@ -0,0 +1,9 @@ +import { Link } from './simple-router'; + +export default function Bye() { + return ( +
    + Bye! Go to Hello! +
    + ); +} diff --git a/demo/suspense-router/hello.jsx b/demo/suspense-router/hello.jsx new file mode 100644 index 0000000000..6b643e806e --- /dev/null +++ b/demo/suspense-router/hello.jsx @@ -0,0 +1,9 @@ +import { Link } from './simple-router'; + +export default function Hello() { + return ( +
    + Hello! Go to Bye! +
    + ); +} diff --git a/demo/suspense-router/index.jsx b/demo/suspense-router/index.jsx new file mode 100644 index 0000000000..b24b5c1235 --- /dev/null +++ b/demo/suspense-router/index.jsx @@ -0,0 +1,28 @@ +import { Suspense, lazy } from 'react'; + +import { Router, Route, Switch } from './simple-router'; + +let Hello = lazy(() => import('./hello.jsx')); +let Bye = lazy(() => import('./bye.jsx')); + +function Loading() { + return
    Hey! This is a fallback because we're loading things! :D
    ; +} + +export default function SuspenseRouterBug() { + return ( + +

    Suspense Router bug

    + }> + + + + + + + + + +
    + ); +} diff --git a/demo/suspense-router/simple-router.jsx b/demo/suspense-router/simple-router.jsx new file mode 100644 index 0000000000..ec86c16574 --- /dev/null +++ b/demo/suspense-router/simple-router.jsx @@ -0,0 +1,83 @@ +import { + createContext, + useState, + useContext, + Children, + useLayoutEffect +} from 'react'; + +const memoryHistory = { + /** + * @typedef {{ pathname: string }} Location + * @typedef {(location: Location) => void} HistoryListener + * @type {HistoryListener[]} + */ + listeners: [], + + /** + * @param {HistoryListener} listener + */ + listen(listener) { + const newLength = this.listeners.push(listener); + return () => this.listeners.splice(newLength - 1, 1); + }, + + /** + * @param {Location} to + */ + navigate(to) { + this.listeners.forEach(listener => listener(to)); + } +}; + +/** @type {import('react').Context<{ history: typeof memoryHistory; location: Location }>} */ +const RouterContext = createContext(null); + +export function Router({ history = memoryHistory, children }) { + const [location, setLocation] = useState({ pathname: '/' }); + + useLayoutEffect(() => { + return history.listen(newLocation => setLocation(newLocation)); + }, []); + + return ( + + {children} + + ); +} + +export function Switch(props) { + const { location } = useContext(RouterContext); + + let element = null; + Children.forEach(props.children, child => { + if (element == null && child.props.path == location.pathname) { + element = child; + } + }); + + return element; +} + +/** + * @param {{ children: any; path: string; exact?: boolean; }} props + */ +export function Route({ children, path, exact }) { + return children; +} + +export function Link({ to, children }) { + const { history } = useContext(RouterContext); + const onClick = event => { + event.preventDefault(); + event.stopPropagation(); + history.navigate({ pathname: to }); + }; + + return ( +
    + {children} + + ); +} diff --git a/demo/suspense.jsx b/demo/suspense.jsx new file mode 100644 index 0000000000..9b620fa402 --- /dev/null +++ b/demo/suspense.jsx @@ -0,0 +1,97 @@ +// eslint-disable-next-line no-unused-vars +import { + createElement, + Component, + memo, + Fragment, + Suspense, + lazy +} from 'react'; + +function LazyComp() { + return
    I'm (fake) lazy loaded
    ; +} + +const Lazy = lazy(() => Promise.resolve({ default: LazyComp })); + +function createSuspension(name, timeout, error) { + let done = false; + let prom; + + return { + name, + timeout, + start: () => { + if (!prom) { + prom = new Promise((res, rej) => { + setTimeout(() => { + done = true; + if (error) { + rej(error); + } else { + res(); + } + }, timeout); + }); + } + + return prom; + }, + getPromise: () => prom, + isDone: () => done + }; +} + +function CustomSuspense({ isDone, start, timeout, name }) { + if (!isDone()) { + throw start(); + } + + return ( +
    + Hello from CustomSuspense {name}, loaded after {timeout / 1000}s +
    + ); +} + +function init() { + return { + s1: createSuspension('1', 1000, null), + s2: createSuspension('2', 2000, null), + s3: createSuspension('3', 3000, null) + }; +} + +export default class DevtoolsDemo extends Component { + constructor(props) { + super(props); + this.state = init(); + this.onRerun = this.onRerun.bind(this); + } + + onRerun() { + this.setState(init()); + } + + render(props, state) { + return ( +
    +

    lazy()

    + Loading (fake) lazy loaded component...
    }> + + +

    Suspense

    +
    + +
    + Fallback 1}> + + Fallback 2}> + + + + + + ); + } +} diff --git a/demo/textFields.jsx b/demo/textFields.jsx new file mode 100644 index 0000000000..7c22618f84 --- /dev/null +++ b/demo/textFields.jsx @@ -0,0 +1,35 @@ +import React, { useState } from 'react'; +import TextField from '@material-ui/core/TextField'; + +const PatchedTextField = props => { + const [value, set] = useState(props.value); + return ( + set(e.target.value)} /> + ); +}; + +const TextFields = () => ( +
    + + + +
    +); + +export default TextFields; diff --git a/demo/todo.jsx b/demo/todo.jsx new file mode 100644 index 0000000000..189b4e80fe --- /dev/null +++ b/demo/todo.jsx @@ -0,0 +1,47 @@ +import { createElement, Component } from 'preact'; + +let counter = 0; + +export default class TodoList extends Component { + state = { todos: [], text: '' }; + + setText = e => { + this.setState({ text: e.target.value }); + }; + + addTodo = () => { + let { todos, text } = this.state; + todos = todos.concat({ text, id: ++counter }); + this.setState({ todos, text: '' }); + }; + + removeTodo = e => { + let id = e.target.getAttribute('data-id'); + this.setState({ todos: this.state.todos.filter(t => t.id != id) }); + }; + + render({}, { todos, text }) { + return ( +
    + + +
      + +
    + + ); + } +} + +class TodoItems extends Component { + render({ todos, removeTodo }) { + return todos.map(todo => ( +
  • + {' '} + {todo.text} +
  • + )); + } +} diff --git a/demo/tsconfig.json b/demo/tsconfig.json new file mode 100644 index 0000000000..74709e5064 --- /dev/null +++ b/demo/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "jsx": "react", + "jsxFactory": "h", + "baseUrl": ".", + "target": "es2018", + "module": "es2015", + "moduleResolution": "node", + "paths": { + "preact/hooks": ["../hooks/src/index.js"], + "preact": ["../src/index.js"] + } + } +} diff --git a/demo/vite.config.js b/demo/vite.config.js new file mode 100644 index 0000000000..38bc2274b1 --- /dev/null +++ b/demo/vite.config.js @@ -0,0 +1,47 @@ +import { defineConfig } from 'vite'; +import path from 'path'; + +const root = path.join(__dirname, '..'); +const resolvePkg = (...parts) => path.join(root, ...parts, 'src', 'index.js'); + +// https://vitejs.dev/config/ +/** @type {import('vite').UserConfig} */ +export default defineConfig({ + optimizeDeps: { + exclude: [ + 'preact', + 'preact/compat', + 'preact/debug', + 'preact/hooks', + 'preact/devtools', + 'preact/jsx-runtime', + 'preact/jsx-dev-runtime', + 'preact-router', + 'react', + 'react-dom' + ] + }, + resolve: { + alias: { + 'preact/debug/src/debug': path.join(root, 'debug', 'src', 'debug'), + 'preact/devtools/src/devtools': path.join( + root, + 'devtools', + 'src', + 'devtools' + ), + //'preact/debug': resolvePkg('debug'), + 'preact/devtools': resolvePkg('devtools'), + 'preact/hooks': resolvePkg('hooks'), + 'preact/jsx-runtime': resolvePkg('jsx-runtime'), + 'preact/jsx-dev-runtime': resolvePkg('jsx-runtime'), + preact: resolvePkg(''), + 'react-dom': resolvePkg('compat'), + react: resolvePkg('compat') + } + }, + esbuild: { + jsx: 'automatic', + jsxImportSource: 'preact' + } +}); diff --git a/demo/zustand.jsx b/demo/zustand.jsx new file mode 100644 index 0000000000..de06decb58 --- /dev/null +++ b/demo/zustand.jsx @@ -0,0 +1,53 @@ +import { createElement } from 'preact'; +import create from 'zustand'; + +const useStore = create(set => ({ + value: 0, + text: 'John', + setText: text => set(state => ({ ...state, text })), + increment: () => set(state => ({ value: state.value + 1 })), + decrement: () => set(state => ({ value: state.value - 1 })), + incrementAsync: async () => { + await new Promise(resolve => setTimeout(resolve, 1000)); + set(state => ({ value: state.value + 1 })); + } +})); + +function Counter({ number }) { + const value = useStore(state => state.value); + return ( +
    + Counter #{number}: {value} +
    + ); +} +function Text() { + const text = useStore(state => state.text); + const { setText } = useStore(); + function handleInput(e) { + setText(e.target.value); + } + return ( +
    + Text: {text} + +
    + ); +} + +export default function ZustandComponent() { + const { increment, decrement, incrementAsync } = useStore(); + + return ( +
    +

    Zustand

    +

    Counter

    + + + + + + +
    + ); +} diff --git a/devtools/LICENSE b/devtools/LICENSE new file mode 100644 index 0000000000..da5389a93c --- /dev/null +++ b/devtools/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-present Jason Miller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/devtools/devtools.js b/devtools/devtools.js deleted file mode 100644 index 512535d0bc..0000000000 --- a/devtools/devtools.js +++ /dev/null @@ -1,395 +0,0 @@ -/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ - -import { options } from 'preact'; - -// Internal helpers from preact -import { ATTR_KEY } from '../src/constants'; - -/** - * Return a ReactElement-compatible object for the current state of a preact - * component. - */ -function createReactElement(component) { - return { - type: component.constructor, - key: component.key, - ref: null, // Unsupported - props: component.props - }; -} - -/** - * Create a ReactDOMComponent-compatible object for a given DOM node rendered - * by preact. - * - * This implements the subset of the ReactDOMComponent interface that - * React DevTools requires in order to display DOM nodes in the inspector with - * the correct type and properties. - * - * @param {Node} node - */ -function createReactDOMComponent(node) { - const childNodes = node.nodeType === Node.ELEMENT_NODE ? - Array.from(node.childNodes) : []; - - const isText = node.nodeType === Node.TEXT_NODE; - - return { - // --- ReactDOMComponent interface - _currentElement: isText ? node.textContent : { - type: node.nodeName.toLowerCase(), - props: node[ATTR_KEY] - }, - _renderedChildren: childNodes.map(child => { - if (child._component) { - return updateReactComponent(child._component); - } - return updateReactComponent(child); - }), - _stringText: isText ? node.textContent : null, - - // --- Additional properties used by preact devtools - - // A flag indicating whether the devtools have been notified about the - // existence of this component instance yet. - // This is used to send the appropriate notifications when DOM components - // are added or updated between composite component updates. - _inDevTools: false, - node - }; -} - -/** - * Return the name of a component created by a `ReactElement`-like object. - * - * @param {ReactElement} element - */ -function typeName(element) { - if (typeof element.type === 'function') { - return element.type.displayName || element.type.name; - } - return element.type; -} - -/** - * Return a ReactCompositeComponent-compatible object for a given preact - * component instance. - * - * This implements the subset of the ReactCompositeComponent interface that - * the DevTools requires in order to walk the component tree and inspect the - * component's properties. - * - * See https://github.com/facebook/react-devtools/blob/e31ec5825342eda570acfc9bcb43a44258fceb28/backend/getData.js - */ -function createReactCompositeComponent(component) { - const _currentElement = createReactElement(component); - const node = component.base; - - let instance = { - // --- ReactDOMComponent properties - getName() { - return typeName(_currentElement); - }, - _currentElement: createReactElement(component), - props: component.props, - state: component.state, - forceUpdate: component.forceUpdate && component.forceUpdate.bind(component), - setState: component.setState && component.setState.bind(component), - - // --- Additional properties used by preact devtools - node - }; - - // React DevTools exposes the `_instance` field of the selected item in the - // component tree as `$r` in the console. `_instance` must refer to a - // React Component (or compatible) class instance with `props` and `state` - // fields and `setState()`, `forceUpdate()` methods. - instance._instance = component; - - // If the root node returned by this component instance's render function - // was itself a composite component, there will be a `_component` property - // containing the child component instance. - if (component._component) { - instance._renderedComponent = updateReactComponent(component._component); - } else { - // Otherwise, if the render() function returned an HTML/SVG element, - // create a ReactDOMComponent-like object for the DOM node itself. - instance._renderedComponent = updateReactComponent(node); - } - - return instance; -} - -/** - * Map of Component|Node to ReactDOMComponent|ReactCompositeComponent-like - * object. - * - * The same React*Component instance must be used when notifying devtools - * about the initial mount of a component and subsequent updates. - */ -let instanceMap = typeof Map==='function' && new Map(); - -/** - * Update (and create if necessary) the ReactDOMComponent|ReactCompositeComponent-like - * instance for a given preact component instance or DOM Node. - * - * @param {Component|Node} componentOrNode - */ -function updateReactComponent(componentOrNode) { - const newInstance = componentOrNode instanceof Node ? - createReactDOMComponent(componentOrNode) : - createReactCompositeComponent(componentOrNode); - if (instanceMap.has(componentOrNode)) { - let inst = instanceMap.get(componentOrNode); - Object.assign(inst, newInstance); - return inst; - } - instanceMap.set(componentOrNode, newInstance); - return newInstance; -} - -function nextRootKey(roots) { - return '.' + Object.keys(roots).length; -} - -/** - * Find all root component instances rendered by preact in `node`'s children - * and add them to the `roots` map. - * - * @param {DOMElement} node - * @param {[key: string] => ReactDOMComponent|ReactCompositeComponent} - */ -function findRoots(node, roots) { - Array.from(node.childNodes).forEach(child => { - if (child._component) { - roots[nextRootKey(roots)] = updateReactComponent(child._component); - } else { - findRoots(child, roots); - } - }); -} - -/** - * Create a bridge for exposing preact's component tree to React DevTools. - * - * It creates implementations of the interfaces that ReactDOM passes to - * devtools to enable it to query the component tree and hook into component - * updates. - * - * See https://github.com/facebook/react/blob/59ff7749eda0cd858d5ee568315bcba1be75a1ca/src/renderers/dom/ReactDOM.js - * for how ReactDOM exports its internals for use by the devtools and - * the `attachRenderer()` function in - * https://github.com/facebook/react-devtools/blob/e31ec5825342eda570acfc9bcb43a44258fceb28/backend/attachRenderer.js - * for how the devtools consumes the resulting objects. - */ -function createDevToolsBridge() { - // The devtools has different paths for interacting with the renderers from - // React Native, legacy React DOM and current React DOM. - // - // Here we emulate the interface for the current React DOM (v15+) lib. - - // ReactDOMComponentTree-like object - const ComponentTree = { - getNodeFromInstance(instance) { - return instance.node; - }, - getClosestInstanceFromNode(node) { - while (node && !node._component) { - node = node.parentNode; - } - return node ? updateReactComponent(node._component) : null; - } - }; - - // Map of root ID (the ID is unimportant) to component instance. - let roots = {}; - findRoots(document.body, roots); - - // ReactMount-like object - // - // Used by devtools to discover the list of root component instances and get - // notified when new root components are rendered. - const Mount = { - _instancesByReactRootID: roots, - - // Stub - React DevTools expects to find this method and replace it - // with a wrapper in order to observe new root components being added - _renderNewRootComponent(/* instance, ... */) { } - }; - - // ReactReconciler-like object - const Reconciler = { - // Stubs - React DevTools expects to find these methods and replace them - // with wrappers in order to observe components being mounted, updated and - // unmounted - mountComponent(/* instance, ... */) { }, - performUpdateIfNecessary(/* instance, ... */) { }, - receiveComponent(/* instance, ... */) { }, - unmountComponent(/* instance, ... */) { } - }; - - /** Notify devtools that a new component instance has been mounted into the DOM. */ - const componentAdded = component => { - const instance = updateReactComponent(component); - if (isRootComponent(component)) { - instance._rootID = nextRootKey(roots); - roots[instance._rootID] = instance; - Mount._renderNewRootComponent(instance); - } - visitNonCompositeChildren(instance, childInst => { - childInst._inDevTools = true; - Reconciler.mountComponent(childInst); - }); - Reconciler.mountComponent(instance); - }; - - /** Notify devtools that a component has been updated with new props/state. */ - const componentUpdated = component => { - const prevRenderedChildren = []; - let instance = instanceMap.get(component); - if (instance) { - visitNonCompositeChildren(instance, childInst => { - prevRenderedChildren.push(childInst); - }); - } - // Notify devtools about updates to this component and any non-composite - // children - instance = updateReactComponent(component); - - Reconciler.receiveComponent(instance); - visitNonCompositeChildren(instance, childInst => { - if (!childInst._inDevTools) { - // New DOM child component - childInst._inDevTools = true; - Reconciler.mountComponent(childInst); - } else { - // Updated DOM child component - Reconciler.receiveComponent(childInst); - } - }); - - // For any non-composite children that were removed by the latest render, - // remove the corresponding ReactDOMComponent-like instances and notify - // the devtools - prevRenderedChildren.forEach(childInst => { - if (!document.body.contains(childInst.node)) { - instanceMap.delete(childInst.node); - Reconciler.unmountComponent(childInst); - } - }); - }; - - /** Notify devtools that a component has been unmounted from the DOM. */ - const componentRemoved = component => { - const instance = updateReactComponent(component); - visitNonCompositeChildren(childInst => { - instanceMap.delete(childInst.node); - Reconciler.unmountComponent(childInst); - }); - Reconciler.unmountComponent(instance); - instanceMap.delete(component); - if (instance._rootID) { - delete roots[instance._rootID]; - } - }; - - return { - componentAdded, - componentUpdated, - componentRemoved, - - // Interfaces passed to devtools via __REACT_DEVTOOLS_GLOBAL_HOOK__.inject() - ComponentTree, - Mount, - Reconciler - }; -} - -/** - * Return `true` if a preact component is a top level component rendered by - * `render()` into a container Element. - */ -function isRootComponent(component) { - // `_parentComponent` is actually `__u` after minification - if (component._parentComponent || component.__u) { - // Component with a composite parent - return false; - } - if (component.base.parentElement && component.base.parentElement[ATTR_KEY]) { - // Component with a parent DOM element rendered by Preact - return false; - } - return true; -} - -/** - * Visit all child instances of a ReactCompositeComponent-like object that are - * not composite components (ie. they represent DOM elements or text) - * - * @param {Component} component - * @param {(Component) => void} visitor - */ -function visitNonCompositeChildren(component, visitor) { - if (!component) return; - if (component._renderedComponent) { - if (!component._renderedComponent._component) { - visitor(component._renderedComponent); - visitNonCompositeChildren(component._renderedComponent, visitor); - } - } else if (component._renderedChildren) { - component._renderedChildren.forEach(child => { - visitor(child); - if (!child._component) visitNonCompositeChildren(child, visitor); - }); - } -} - -/** - * Create a bridge between the preact component tree and React's dev tools - * and register it. - * - * After this function is called, the React Dev Tools should be able to detect - * "React" on the page and show the component tree. - * - * This function hooks into preact VNode creation in order to expose functional - * components correctly, so it should be called before the root component(s) - * are rendered. - * - * Returns a cleanup function which unregisters the hooks. - */ -export function initDevTools() { - if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') { - // React DevTools are not installed - return; - } - - // Notify devtools when preact components are mounted, updated or unmounted - const bridge = createDevToolsBridge(); - - const nextAfterMount = options.afterMount; - options.afterMount = component => { - bridge.componentAdded(component); - if (nextAfterMount) nextAfterMount(component); - }; - - const nextAfterUpdate = options.afterUpdate; - options.afterUpdate = component => { - bridge.componentUpdated(component); - if (nextAfterUpdate) nextAfterUpdate(component); - }; - - const nextBeforeUnmount = options.beforeUnmount; - options.beforeUnmount = component => { - bridge.componentRemoved(component); - if (nextBeforeUnmount) nextBeforeUnmount(component); - }; - - // Notify devtools about this instance of "React" - __REACT_DEVTOOLS_GLOBAL_HOOK__.inject(bridge); - - return () => { - options.afterMount = nextAfterMount; - options.afterUpdate = nextAfterUpdate; - options.beforeUnmount = nextBeforeUnmount; - }; -} diff --git a/devtools/index.js b/devtools/index.js deleted file mode 100644 index 7c361c8ee1..0000000000 --- a/devtools/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import { initDevTools } from './devtools'; - -initDevTools(); - diff --git a/devtools/mangle.json b/devtools/mangle.json new file mode 100644 index 0000000000..8352f38e63 --- /dev/null +++ b/devtools/mangle.json @@ -0,0 +1,21 @@ +{ + "help": { + "what is this file?": "It controls protected/private property mangling so that minified builds have consistent property names.", + "why are there duplicate minified properties?": "Most properties are only used on one type of objects, so they can have the same name since they will never collide. Doing this reduces size." + }, + "minify": { + "mangle": { + "properties": { + "regex": "^_[^_]", + "reserved": [ + "__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED", + "__REACT_DEVTOOLS_GLOBAL_HOOK__", + "__PREACT_DEVTOOLS__", + "_renderers", + "__source", + "__self" + ] + } + } + } +} diff --git a/devtools/package.json b/devtools/package.json new file mode 100644 index 0000000000..c12ac730f0 --- /dev/null +++ b/devtools/package.json @@ -0,0 +1,25 @@ +{ + "name": "preact-devtools", + "amdName": "preactDevtools", + "version": "1.0.0", + "private": true, + "description": "Preact bridge for Preact devtools", + "main": "dist/devtools.js", + "module": "dist/devtools.module.js", + "umd:main": "dist/devtools.umd.js", + "source": "src/index.js", + "license": "MIT", + "types": "src/index.d.ts", + "peerDependencies": { + "preact": "^10.0.0" + }, + "exports": { + ".": { + "types": "./src/index.d.ts", + "browser": "./dist/devtools.module.js", + "umd": "./dist/devtools.umd.js", + "import": "./dist/devtools.mjs", + "require": "./dist/devtools.js" + } + } +} diff --git a/devtools/src/devtools.js b/devtools/src/devtools.js new file mode 100644 index 0000000000..4803a00991 --- /dev/null +++ b/devtools/src/devtools.js @@ -0,0 +1,21 @@ +import { Component, Fragment, options } from 'preact'; + +export function initDevTools() { + const globalVar = + typeof globalThis !== 'undefined' + ? globalThis + : typeof window !== 'undefined' + ? window + : undefined; + + if ( + globalVar !== null && + globalVar !== undefined && + globalVar.__PREACT_DEVTOOLS__ + ) { + globalVar.__PREACT_DEVTOOLS__.attachPreact('10.25.0', options, { + Fragment, + Component + }); + } +} diff --git a/devtools/src/index.d.ts b/devtools/src/index.d.ts new file mode 100644 index 0000000000..230e5ab6ec --- /dev/null +++ b/devtools/src/index.d.ts @@ -0,0 +1,8 @@ +/** + * Customize the displayed name of a useState, useReducer or useRef hook + * in the devtools panel. + * + * @param value Wrapped native hook. + * @param name Custom name + */ +export function addHookName(value: T, name: string): T; diff --git a/devtools/src/index.js b/devtools/src/index.js new file mode 100644 index 0000000000..693429f56c --- /dev/null +++ b/devtools/src/index.js @@ -0,0 +1,15 @@ +import { options } from 'preact'; +import { initDevTools } from './devtools'; + +initDevTools(); + +/** + * Display a custom label for a custom hook for the devtools panel + * @type {(value: T, name: string) => T} + */ +export function addHookName(value, name) { + if (options._addHookName) { + options._addHookName(name); + } + return value; +} diff --git a/devtools/test/browser/addHookName.test.js b/devtools/test/browser/addHookName.test.js new file mode 100644 index 0000000000..28b06b0c3b --- /dev/null +++ b/devtools/test/browser/addHookName.test.js @@ -0,0 +1,51 @@ +import { createElement, render, options } from 'preact'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { useState } from 'preact/hooks'; +import { addHookName } from 'preact/devtools'; + +/** @jsx createElement */ + +describe('addHookName', () => { + /** @type {HTMLDivElement} */ + let scratch; + + beforeEach(() => { + scratch = setupScratch(); + }); + + afterEach(() => { + teardown(scratch); + delete options._addHookName; + }); + + it('should do nothing when no options hook is present', () => { + function useFoo() { + return addHookName(useState(0), 'foo'); + } + + function App() { + let [v] = useFoo(); + return
    {v}
    ; + } + + expect(() => render(, scratch)).to.not.throw(); + }); + + it('should call options hook with value', () => { + let spy = (options._addHookName = sinon.spy()); + + function useFoo() { + return addHookName(useState(0), 'foo'); + } + + function App() { + let [v] = useFoo(); + return
    {v}
    ; + } + + render(, scratch); + + expect(spy).to.be.calledOnce; + expect(spy).to.be.calledWith('foo'); + }); +}); diff --git a/hooks/LICENSE b/hooks/LICENSE new file mode 100644 index 0000000000..da5389a93c --- /dev/null +++ b/hooks/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-present Jason Miller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/hooks/mangle.json b/hooks/mangle.json new file mode 100644 index 0000000000..8352f38e63 --- /dev/null +++ b/hooks/mangle.json @@ -0,0 +1,21 @@ +{ + "help": { + "what is this file?": "It controls protected/private property mangling so that minified builds have consistent property names.", + "why are there duplicate minified properties?": "Most properties are only used on one type of objects, so they can have the same name since they will never collide. Doing this reduces size." + }, + "minify": { + "mangle": { + "properties": { + "regex": "^_[^_]", + "reserved": [ + "__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED", + "__REACT_DEVTOOLS_GLOBAL_HOOK__", + "__PREACT_DEVTOOLS__", + "_renderers", + "__source", + "__self" + ] + } + } + } +} diff --git a/hooks/package.json b/hooks/package.json new file mode 100644 index 0000000000..787927573e --- /dev/null +++ b/hooks/package.json @@ -0,0 +1,35 @@ +{ + "name": "preact-hooks", + "amdName": "preactHooks", + "version": "0.1.0", + "private": true, + "description": "Hook addon for Preact", + "main": "dist/hooks.js", + "module": "dist/hooks.module.js", + "umd:main": "dist/hooks.umd.js", + "source": "src/index.js", + "license": "MIT", + "types": "src/index.d.ts", + "scripts": { + "build": "microbundle build --raw", + "dev": "microbundle watch --raw --format cjs", + "test": "npm-run-all build --parallel test:karma", + "test:karma": "karma start test/karma.conf.js --single-run", + "test:karma:watch": "karma start test/karma.conf.js --no-single-run" + }, + "peerDependencies": { + "preact": "^10.0.0" + }, + "mangle": { + "regex": "^_" + }, + "exports": { + ".": { + "types": "./src/index.d.ts", + "browser": "./dist/hooks.module.js", + "umd": "./dist/hooks.umd.js", + "import": "./dist/hooks.mjs", + "require": "./dist/hooks.js" + } + } +} diff --git a/hooks/src/index.d.ts b/hooks/src/index.d.ts new file mode 100644 index 0000000000..7bb441edb2 --- /dev/null +++ b/hooks/src/index.d.ts @@ -0,0 +1,143 @@ +import { ErrorInfo, PreactContext, Ref, RefObject } from '../..'; + +type Inputs = ReadonlyArray; + +export type Dispatch = (value: A) => void; +export type StateUpdater = S | ((prevState: S) => S); + +/** + * Returns a stateful value, and a function to update it. + * @param initialState The initial value (or a function that returns the initial value) + */ +export function useState( + initialState: S | (() => S) +): [S, Dispatch>]; + +export function useState(): [ + S | undefined, + Dispatch> +]; + +export type Reducer = (prevState: S, action: A) => S; + +/** + * An alternative to `useState`. + * + * `useReducer` is usually preferable to `useState` when you have complex state logic that involves + * multiple sub-values. It also lets you optimize performance for components that trigger deep + * updates because you can pass `dispatch` down instead of callbacks. + * @param reducer Given the current state and an action, returns the new state + * @param initialState The initial value to store as state + */ +export function useReducer( + reducer: Reducer, + initialState: S +): [S, Dispatch]; + +/** + * An alternative to `useState`. + * + * `useReducer` is usually preferable to `useState` when you have complex state logic that involves + * multiple sub-values. It also lets you optimize performance for components that trigger deep + * updates because you can pass `dispatch` down instead of callbacks. + * @param reducer Given the current state and an action, returns the new state + * @param initialArg The initial argument to pass to the `init` function + * @param init A function that, given the `initialArg`, returns the initial value to store as state + */ +export function useReducer( + reducer: Reducer, + initialArg: I, + init: (arg: I) => S +): [S, Dispatch]; + +/** @deprecated Use the `Ref` type instead. */ +type PropRef = MutableRef; + +interface MutableRef { + current: T; +} + +/** + * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument + * (`initialValue`). The returned object will persist for the full lifetime of the component. + * + * Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable + * value around similar to how you’d use instance fields in classes. + * + * @param initialValue the initial value to store in the ref object + */ +export function useRef(initialValue: T): MutableRef; +export function useRef(initialValue: T | null): RefObject; +export function useRef(): MutableRef; + +type EffectCallback = () => void | (() => void); +/** + * Accepts a function that contains imperative, possibly effectful code. + * The effects run after browser paint, without blocking it. + * + * @param effect Imperative function that can return a cleanup function + * @param inputs If present, effect will only activate if the values in the list change (using ===). + */ +export function useEffect(effect: EffectCallback, inputs?: Inputs): void; + +type CreateHandle = () => object; + +/** + * @param ref The ref that will be mutated + * @param create The function that will be executed to get the value that will be attached to + * ref.current + * @param inputs If present, effect will only activate if the values in the list change (using ===). + */ +export function useImperativeHandle( + ref: Ref, + create: () => R, + inputs?: Inputs +): void; + +/** + * Accepts a function that contains imperative, possibly effectful code. + * Use this to read layout from the DOM and synchronously re-render. + * Updates scheduled inside `useLayoutEffect` will be flushed synchronously, after all DOM mutations but before the browser has a chance to paint. + * Prefer the standard `useEffect` hook when possible to avoid blocking visual updates. + * + * @param effect Imperative function that can return a cleanup function + * @param inputs If present, effect will only activate if the values in the list change (using ===). + */ +export function useLayoutEffect(effect: EffectCallback, inputs?: Inputs): void; + +/** + * Returns a memoized version of the callback that only changes if one of the `inputs` + * has changed (using ===). + */ +export function useCallback(callback: T, inputs: Inputs): T; + +/** + * Pass a factory function and an array of inputs. + * useMemo will only recompute the memoized value when one of the inputs has changed. + * This optimization helps to avoid expensive calculations on every render. + * If no array is provided, a new value will be computed whenever a new function instance is passed as the first argument. + */ +// for `inputs`, allow undefined, but don't make it optional as that is very likely a mistake +export function useMemo(factory: () => T, inputs: Inputs | undefined): T; + +/** + * Returns the current context value, as given by the nearest context provider for the given context. + * When the provider updates, this Hook will trigger a rerender with the latest context value. + * + * @param context The context you want to use + */ +export function useContext(context: PreactContext): T; + +/** + * Customize the displayed value in the devtools panel. + * + * @param value Custom hook name or object that is passed to formatter + * @param formatter Formatter to modify value before sending it to the devtools + */ +export function useDebugValue(value: T, formatter?: (value: T) => any): void; + +export function useErrorBoundary( + callback?: (error: any, errorInfo: ErrorInfo) => Promise | void +): [any, () => void]; + +export function useId(): string; diff --git a/hooks/src/index.js b/hooks/src/index.js new file mode 100644 index 0000000000..92a05f0a54 --- /dev/null +++ b/hooks/src/index.js @@ -0,0 +1,549 @@ +import { options as _options } from 'preact'; + +/** @type {number} */ +let currentIndex; + +/** @type {import('./internal').Component} */ +let currentComponent; + +/** @type {import('./internal').Component} */ +let previousComponent; + +/** @type {number} */ +let currentHook = 0; + +/** @type {Array} */ +let afterPaintEffects = []; + +// Cast to use internal Options type +const options = /** @type {import('./internal').Options} */ (_options); + +let oldBeforeDiff = options._diff; +let oldBeforeRender = options._render; +let oldAfterDiff = options.diffed; +let oldCommit = options._commit; +let oldBeforeUnmount = options.unmount; +let oldRoot = options._root; + +const RAF_TIMEOUT = 100; +let prevRaf; + +/** @type {(vnode: import('./internal').VNode) => void} */ +options._diff = vnode => { + currentComponent = null; + if (oldBeforeDiff) oldBeforeDiff(vnode); +}; + +options._root = (vnode, parentDom) => { + if (vnode && parentDom._children && parentDom._children._mask) { + vnode._mask = parentDom._children._mask; + } + + if (oldRoot) oldRoot(vnode, parentDom); +}; + +/** @type {(vnode: import('./internal').VNode) => void} */ +options._render = vnode => { + if (oldBeforeRender) oldBeforeRender(vnode); + + currentComponent = vnode._component; + currentIndex = 0; + + const hooks = currentComponent.__hooks; + if (hooks) { + if (previousComponent === currentComponent) { + hooks._pendingEffects = []; + currentComponent._renderCallbacks = []; + hooks._list.forEach(hookItem => { + if (hookItem._nextValue) { + hookItem._value = hookItem._nextValue; + } + hookItem._pendingArgs = hookItem._nextValue = undefined; + }); + } else { + hooks._pendingEffects.forEach(invokeCleanup); + hooks._pendingEffects.forEach(invokeEffect); + hooks._pendingEffects = []; + currentIndex = 0; + } + } + previousComponent = currentComponent; +}; + +/** @type {(vnode: import('./internal').VNode) => void} */ +options.diffed = vnode => { + if (oldAfterDiff) oldAfterDiff(vnode); + + const c = vnode._component; + if (c && c.__hooks) { + if (c.__hooks._pendingEffects.length) afterPaint(afterPaintEffects.push(c)); + c.__hooks._list.forEach(hookItem => { + if (hookItem._pendingArgs) { + hookItem._args = hookItem._pendingArgs; + } + hookItem._pendingArgs = undefined; + }); + } + previousComponent = currentComponent = null; +}; + +// TODO: Improve typing of commitQueue parameter +/** @type {(vnode: import('./internal').VNode, commitQueue: any) => void} */ +options._commit = (vnode, commitQueue) => { + commitQueue.some(component => { + try { + component._renderCallbacks.forEach(invokeCleanup); + component._renderCallbacks = component._renderCallbacks.filter(cb => + cb._value ? invokeEffect(cb) : true + ); + } catch (e) { + commitQueue.some(c => { + if (c._renderCallbacks) c._renderCallbacks = []; + }); + commitQueue = []; + options._catchError(e, component._vnode); + } + }); + + if (oldCommit) oldCommit(vnode, commitQueue); +}; + +/** @type {(vnode: import('./internal').VNode) => void} */ +options.unmount = vnode => { + if (oldBeforeUnmount) oldBeforeUnmount(vnode); + + const c = vnode._component; + if (c && c.__hooks) { + let hasErrored; + c.__hooks._list.forEach(s => { + try { + invokeCleanup(s); + } catch (e) { + hasErrored = e; + } + }); + c.__hooks = undefined; + if (hasErrored) options._catchError(hasErrored, c._vnode); + } +}; + +/** + * Get a hook's state from the currentComponent + * @param {number} index The index of the hook to get + * @param {number} type The index of the hook to get + * @returns {any} + */ +function getHookState(index, type) { + if (options._hook) { + options._hook(currentComponent, index, currentHook || type); + } + currentHook = 0; + + // Largely inspired by: + // * https://github.com/michael-klein/funcy.js/blob/f6be73468e6ec46b0ff5aa3cc4c9baf72a29025a/src/hooks/core_hooks.mjs + // * https://github.com/michael-klein/funcy.js/blob/650beaa58c43c33a74820a3c98b3c7079cf2e333/src/renderer.mjs + // Other implementations to look at: + // * https://codesandbox.io/s/mnox05qp8 + const hooks = + currentComponent.__hooks || + (currentComponent.__hooks = { + _list: [], + _pendingEffects: [] + }); + + if (index >= hooks._list.length) { + hooks._list.push({}); + } + + return hooks._list[index]; +} + +/** + * @template {unknown} S + * @param {import('./index').Dispatch>} [initialState] + * @returns {[S, (state: S) => void]} + */ +export function useState(initialState) { + currentHook = 1; + return useReducer(invokeOrReturn, initialState); +} + +/** + * @template {unknown} S + * @template {unknown} A + * @param {import('./index').Reducer} reducer + * @param {import('./index').Dispatch>} initialState + * @param {(initialState: any) => void} [init] + * @returns {[ S, (state: S) => void ]} + */ +export function useReducer(reducer, initialState, init) { + /** @type {import('./internal').ReducerHookState} */ + const hookState = getHookState(currentIndex++, 2); + hookState._reducer = reducer; + if (!hookState._component) { + hookState._value = [ + !init ? invokeOrReturn(undefined, initialState) : init(initialState), + + action => { + const currentValue = hookState._nextValue + ? hookState._nextValue[0] + : hookState._value[0]; + const nextValue = hookState._reducer(currentValue, action); + + if (currentValue !== nextValue) { + hookState._nextValue = [nextValue, hookState._value[1]]; + hookState._component.setState({}); + } + } + ]; + + hookState._component = currentComponent; + + if (!currentComponent._hasScuFromHooks) { + currentComponent._hasScuFromHooks = true; + let prevScu = currentComponent.shouldComponentUpdate; + const prevCWU = currentComponent.componentWillUpdate; + + // If we're dealing with a forced update `shouldComponentUpdate` will + // not be called. But we use that to update the hook values, so we + // need to call it. + currentComponent.componentWillUpdate = function (p, s, c) { + if (this._force) { + let tmp = prevScu; + // Clear to avoid other sCU hooks from being called + prevScu = undefined; + updateHookState(p, s, c); + prevScu = tmp; + } + + if (prevCWU) prevCWU.call(this, p, s, c); + }; + + // This SCU has the purpose of bailing out after repeated updates + // to stateful hooks. + // we store the next value in _nextValue[0] and keep doing that for all + // state setters, if we have next states and + // all next states within a component end up being equal to their original state + // we are safe to bail out for this specific component. + /** + * + * @type {import('./internal').Component["shouldComponentUpdate"]} + */ + // @ts-ignore - We don't use TS to downtranspile + // eslint-disable-next-line no-inner-declarations + function updateHookState(p, s, c) { + if (!hookState._component.__hooks) return true; + + /** @type {(x: import('./internal').HookState) => x is import('./internal').ReducerHookState} */ + const isStateHook = x => !!x._component; + const stateHooks = + hookState._component.__hooks._list.filter(isStateHook); + + const allHooksEmpty = stateHooks.every(x => !x._nextValue); + // When we have no updated hooks in the component we invoke the previous SCU or + // traverse the VDOM tree further. + if (allHooksEmpty) { + return prevScu ? prevScu.call(this, p, s, c) : true; + } + + // We check whether we have components with a nextValue set that + // have values that aren't equal to one another this pushes + // us to update further down the tree + let shouldUpdate = hookState._component.props !== p; + stateHooks.forEach(hookItem => { + if (hookItem._nextValue) { + const currentValue = hookItem._value[0]; + hookItem._value = hookItem._nextValue; + hookItem._nextValue = undefined; + if (currentValue !== hookItem._value[0]) shouldUpdate = true; + } + }); + + return prevScu + ? prevScu.call(this, p, s, c) || shouldUpdate + : shouldUpdate; + } + + currentComponent.shouldComponentUpdate = updateHookState; + } + } + + return hookState._nextValue || hookState._value; +} + +/** + * @param {import('./internal').Effect} callback + * @param {unknown[]} args + * @returns {void} + */ +export function useEffect(callback, args) { + /** @type {import('./internal').EffectHookState} */ + const state = getHookState(currentIndex++, 3); + if (!options._skipEffects && argsChanged(state._args, args)) { + state._value = callback; + state._pendingArgs = args; + + currentComponent.__hooks._pendingEffects.push(state); + } +} + +/** + * @param {import('./internal').Effect} callback + * @param {unknown[]} args + * @returns {void} + */ +export function useLayoutEffect(callback, args) { + /** @type {import('./internal').EffectHookState} */ + const state = getHookState(currentIndex++, 4); + if (!options._skipEffects && argsChanged(state._args, args)) { + state._value = callback; + state._pendingArgs = args; + + currentComponent._renderCallbacks.push(state); + } +} + +/** @type {(initialValue: unknown) => unknown} */ +export function useRef(initialValue) { + currentHook = 5; + return useMemo(() => ({ current: initialValue }), []); +} + +/** + * @param {object} ref + * @param {() => object} createHandle + * @param {unknown[]} args + * @returns {void} + */ +export function useImperativeHandle(ref, createHandle, args) { + currentHook = 6; + useLayoutEffect( + () => { + if (typeof ref == 'function') { + ref(createHandle()); + return () => ref(null); + } else if (ref) { + ref.current = createHandle(); + return () => (ref.current = null); + } + }, + args == null ? args : args.concat(ref) + ); +} + +/** + * @template {unknown} T + * @param {() => T} factory + * @param {unknown[]} args + * @returns {T} + */ +export function useMemo(factory, args) { + /** @type {import('./internal').MemoHookState} */ + const state = getHookState(currentIndex++, 7); + if (argsChanged(state._args, args)) { + state._value = factory(); + state._args = args; + state._factory = factory; + } + + return state._value; +} + +/** + * @param {() => void} callback + * @param {unknown[]} args + * @returns {() => void} + */ +export function useCallback(callback, args) { + currentHook = 8; + return useMemo(() => callback, args); +} + +/** + * @param {import('./internal').PreactContext} context + */ +export function useContext(context) { + const provider = currentComponent.context[context._id]; + // We could skip this call here, but than we'd not call + // `options._hook`. We need to do that in order to make + // the devtools aware of this hook. + /** @type {import('./internal').ContextHookState} */ + const state = getHookState(currentIndex++, 9); + // The devtools needs access to the context object to + // be able to pull of the default value when no provider + // is present in the tree. + state._context = context; + if (!provider) return context._defaultValue; + // This is probably not safe to convert to "!" + if (state._value == null) { + state._value = true; + provider.sub(currentComponent); + } + return provider.props.value; +} + +/** + * Display a custom label for a custom hook for the devtools panel + * @type {(value: T, cb?: (value: T) => string | number) => void} + */ +export function useDebugValue(value, formatter) { + if (options.useDebugValue) { + options.useDebugValue( + formatter ? formatter(value) : /** @type {any}*/ (value) + ); + } +} + +/** + * @param {(error: unknown, errorInfo: import('preact').ErrorInfo) => void} cb + * @returns {[unknown, () => void]} + */ +export function useErrorBoundary(cb) { + /** @type {import('./internal').ErrorBoundaryHookState} */ + const state = getHookState(currentIndex++, 10); + const errState = useState(); + state._value = cb; + if (!currentComponent.componentDidCatch) { + currentComponent.componentDidCatch = (err, errorInfo) => { + if (state._value) state._value(err, errorInfo); + errState[1](err); + }; + } + return [ + errState[0], + () => { + errState[1](undefined); + } + ]; +} + +/** @type {() => string} */ +export function useId() { + /** @type {import('./internal').IdHookState} */ + const state = getHookState(currentIndex++, 11); + if (!state._value) { + // Grab either the root node or the nearest async boundary node. + /** @type {import('./internal.d').VNode} */ + let root = currentComponent._vnode; + while (root !== null && !root._mask && root._parent !== null) { + root = root._parent; + } + + let mask = root._mask || (root._mask = [0, 0]); + state._value = 'P' + mask[0] + '-' + mask[1]++; + } + + return state._value; +} + +/** + * After paint effects consumer. + */ +function flushAfterPaintEffects() { + let component; + while ((component = afterPaintEffects.shift())) { + if (!component._parentDom || !component.__hooks) continue; + try { + component.__hooks._pendingEffects.forEach(invokeCleanup); + component.__hooks._pendingEffects.forEach(invokeEffect); + component.__hooks._pendingEffects = []; + } catch (e) { + component.__hooks._pendingEffects = []; + options._catchError(e, component._vnode); + } + } +} + +let HAS_RAF = typeof requestAnimationFrame == 'function'; + +/** + * Schedule a callback to be invoked after the browser has a chance to paint a new frame. + * Do this by combining requestAnimationFrame (rAF) + setTimeout to invoke a callback after + * the next browser frame. + * + * Also, schedule a timeout in parallel to the the rAF to ensure the callback is invoked + * even if RAF doesn't fire (for example if the browser tab is not visible) + * + * @param {() => void} callback + */ +function afterNextFrame(callback) { + const done = () => { + clearTimeout(timeout); + if (HAS_RAF) cancelAnimationFrame(raf); + setTimeout(callback); + }; + const timeout = setTimeout(done, RAF_TIMEOUT); + + let raf; + if (HAS_RAF) { + raf = requestAnimationFrame(done); + } +} + +// Note: if someone used options.debounceRendering = requestAnimationFrame, +// then effects will ALWAYS run on the NEXT frame instead of the current one, incurring a ~16ms delay. +// Perhaps this is not such a big deal. +/** + * Schedule afterPaintEffects flush after the browser paints + * @param {number} newQueueLength + * @returns {void} + */ +function afterPaint(newQueueLength) { + if (newQueueLength === 1 || prevRaf !== options.requestAnimationFrame) { + prevRaf = options.requestAnimationFrame; + (prevRaf || afterNextFrame)(flushAfterPaintEffects); + } +} + +/** + * @param {import('./internal').HookState} hook + * @returns {void} + */ +function invokeCleanup(hook) { + // A hook cleanup can introduce a call to render which creates a new root, this will call options.vnode + // and move the currentComponent away. + const comp = currentComponent; + let cleanup = hook._cleanup; + if (typeof cleanup == 'function') { + hook._cleanup = undefined; + cleanup(); + } + + currentComponent = comp; +} + +/** + * Invoke a Hook's effect + * @param {import('./internal').EffectHookState} hook + * @returns {void} + */ +function invokeEffect(hook) { + // A hook call can introduce a call to render which creates a new root, this will call options.vnode + // and move the currentComponent away. + const comp = currentComponent; + hook._cleanup = hook._value(); + currentComponent = comp; +} + +/** + * @param {unknown[]} oldArgs + * @param {unknown[]} newArgs + * @returns {boolean} + */ +function argsChanged(oldArgs, newArgs) { + return ( + !oldArgs || + oldArgs.length !== newArgs.length || + newArgs.some((arg, index) => arg !== oldArgs[index]) + ); +} + +/** + * @template Arg + * @param {Arg} arg + * @param {(arg: Arg) => any} f + * @returns {any} + */ +function invokeOrReturn(arg, f) { + return typeof f == 'function' ? f(arg) : f; +} diff --git a/hooks/src/internal.d.ts b/hooks/src/internal.d.ts new file mode 100644 index 0000000000..5b612dbda6 --- /dev/null +++ b/hooks/src/internal.d.ts @@ -0,0 +1,95 @@ +import { Reducer, StateUpdater } from '.'; + +export { PreactContext }; + +export interface Options extends globalThis.Options { + /** Attach a hook that is invoked before a vnode is diffed. */ + _diff?(vnode: VNode): void; + diffed?(vnode: VNode): void; + /** Attach a hook that is invoked before a vnode has rendered. */ + _render?(vnode: VNode): void; + /** Attach a hook that is invoked after a tree was mounted or was updated. */ + _commit?(vnode: VNode, commitQueue: Component[]): void; + _unmount?(vnode: VNode): void; + /** Attach a hook that is invoked before a hook's state is queried. */ + _hook?(component: Component, index: number, type: HookType): void; +} + +// Hook tracking + +export interface ComponentHooks { + /** The list of hooks a component uses */ + _list: HookState[]; + /** List of Effects to be invoked after the next frame is rendered */ + _pendingEffects: EffectHookState[]; +} + +export interface Component extends globalThis.Component { + __hooks?: ComponentHooks; + // Extend to include HookStates + _renderCallbacks?: Array void)>; + _hasScuFromHooks?: boolean; +} + +export interface VNode extends globalThis.VNode { + _mask?: [number, number]; + _component?: Component; // Override with our specific Component type +} + +export type HookState = + | EffectHookState + | MemoHookState + | ReducerHookState + | ContextHookState + | ErrorBoundaryHookState + | IdHookState; + +interface BaseHookState { + _value?: unknown; + _nextValue?: undefined; + _pendingValue?: undefined; + _args?: undefined; + _pendingArgs?: undefined; + _component?: undefined; + _cleanup?: undefined; +} + +export type Effect = () => void | Cleanup; +export type Cleanup = () => void; + +export interface EffectHookState extends BaseHookState { + _value?: Effect; + _args?: unknown[]; + _pendingArgs?: unknown[]; + _cleanup?: Cleanup | void; +} + +export interface MemoHookState extends BaseHookState { + _value?: T; + _pendingValue?: T; + _args?: unknown[]; + _pendingArgs?: unknown[]; + _factory?: () => T; +} + +export interface ReducerHookState + extends BaseHookState { + _nextValue?: [S, StateUpdater]; + _value?: [S, StateUpdater]; + _component?: Component; + _reducer?: Reducer; +} + +export interface ContextHookState extends BaseHookState { + /** Whether this hooks as subscribed to updates yet */ + _value?: boolean; + _context?: PreactContext; +} + +export interface ErrorBoundaryHookState extends BaseHookState { + _value?: (error: unknown, errorInfo: ErrorInfo) => void; +} + +export interface IdHookState extends BaseHookState { + _value?: string; +} diff --git a/hooks/test/_util/useEffectUtil.js b/hooks/test/_util/useEffectUtil.js new file mode 100644 index 0000000000..f04edc2d75 --- /dev/null +++ b/hooks/test/_util/useEffectUtil.js @@ -0,0 +1,10 @@ +export function scheduleEffectAssert(assertFn) { + return new Promise(resolve => { + requestAnimationFrame(() => + setTimeout(() => { + assertFn(); + resolve(); + }, 0) + ); + }); +} diff --git a/hooks/test/browser/combinations.test.js b/hooks/test/browser/combinations.test.js new file mode 100644 index 0000000000..fff51cd80a --- /dev/null +++ b/hooks/test/browser/combinations.test.js @@ -0,0 +1,520 @@ +import { setupRerender, act } from 'preact/test-utils'; +import { createElement, render, Component, createContext } from 'preact'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { + useState, + useReducer, + useEffect, + useLayoutEffect, + useRef, + useMemo, + useContext +} from 'preact/hooks'; +import { scheduleEffectAssert } from '../_util/useEffectUtil'; + +/** @jsx createElement */ + +describe('combinations', () => { + /** @type {HTMLDivElement} */ + let scratch; + + /** @type {() => void} */ + let rerender; + + beforeEach(() => { + scratch = setupScratch(); + rerender = setupRerender(); + }); + + afterEach(() => { + teardown(scratch); + }); + + it('can mix useState hooks', () => { + const states = {}; + const setStates = {}; + + function Parent() { + const [state1, setState1] = useState(1); + const [state2, setState2] = useState(2); + + Object.assign(states, { state1, state2 }); + Object.assign(setStates, { setState1, setState2 }); + + return ; + } + + function Child() { + const [state3, setState3] = useState(3); + const [state4, setState4] = useState(4); + + Object.assign(states, { state3, state4 }); + Object.assign(setStates, { setState3, setState4 }); + + return null; + } + + render(, scratch); + expect(states).to.deep.equal({ + state1: 1, + state2: 2, + state3: 3, + state4: 4 + }); + + setStates.setState2(n => n * 10); + setStates.setState3(n => n * 10); + rerender(); + expect(states).to.deep.equal({ + state1: 1, + state2: 20, + state3: 30, + state4: 4 + }); + }); + + it('can rerender asynchronously from within an effect', () => { + const didRender = sinon.spy(); + + function Comp() { + const [counter, setCounter] = useState(0); + + useEffect(() => { + if (counter === 0) setCounter(1); + }); + + didRender(counter); + return null; + } + + render(, scratch); + + return scheduleEffectAssert(() => { + rerender(); + expect(didRender).to.have.been.calledTwice.and.calledWith(1); + }); + }); + + it('can rerender synchronously from within a layout effect', () => { + const didRender = sinon.spy(); + + function Comp() { + const [counter, setCounter] = useState(0); + + useLayoutEffect(() => { + if (counter === 0) setCounter(1); + }); + + didRender(counter); + return null; + } + + render(, scratch); + rerender(); + + expect(didRender).to.have.been.calledTwice.and.calledWith(1); + }); + + it('can access refs from within a layout effect callback', () => { + let refAtLayoutTime; + + function Comp() { + const input = useRef(); + + useLayoutEffect(() => { + refAtLayoutTime = input.current; + }); + + return ; + } + + render(, scratch); + + expect(refAtLayoutTime.value).to.equal('hello'); + }); + + it('can use multiple useState and useReducer hooks', () => { + let states = []; + let dispatchState4; + + function reducer1(state, action) { + switch (action.type) { + case 'increment': + return state + action.count; + } + } + + function reducer2(state, action) { + switch (action.type) { + case 'increment': + return state + action.count * 2; + } + } + + function Comp() { + const [state1] = useState(0); + const [state2] = useReducer(reducer1, 10); + const [state3] = useState(1); + const [state4, dispatch] = useReducer(reducer2, 20); + + dispatchState4 = dispatch; + states.push(state1, state2, state3, state4); + + return null; + } + + render(, scratch); + + expect(states).to.deep.equal([0, 10, 1, 20]); + + states = []; + + dispatchState4({ type: 'increment', count: 10 }); + rerender(); + + expect(states).to.deep.equal([0, 10, 1, 40]); + }); + + it('ensures useEffect always schedule after the next paint following a redraw effect, when using the default debounce strategy', () => { + let effectCount = 0; + + function Comp() { + const [counter, setCounter] = useState(0); + + useEffect(() => { + if (counter === 0) setCounter(1); + effectCount++; + }); + + return null; + } + + render(, scratch); + + return scheduleEffectAssert(() => { + expect(effectCount).to.equal(1); + }); + }); + + it('should not reuse functional components with hooks', () => { + let updater = { first: undefined, second: undefined }; + function Foo(props) { + let [v, setter] = useState(0); + updater[props.id] = () => setter(++v); + return
    {v}
    ; + } + + let updateParent; + class App extends Component { + constructor(props) { + super(props); + this.state = { active: true }; + updateParent = () => this.setState(p => ({ active: !p.active })); + } + + render() { + return ( +
    + {this.state.active && } + +
    + ); + } + } + + render(, scratch); + act(() => updater.second()); + expect(scratch.textContent).to.equal('01'); + + updateParent(); + rerender(); + expect(scratch.textContent).to.equal('1'); + + updateParent(); + rerender(); + + expect(scratch.textContent).to.equal('01'); + }); + + it('should have a right call order with correct dom ref', () => { + let i = 0, + set; + const calls = []; + + function Inner() { + useLayoutEffect(() => { + calls.push('layout inner call ' + scratch.innerHTML); + return () => calls.push('layout inner dispose ' + scratch.innerHTML); + }); + useEffect(() => { + calls.push('effect inner call ' + scratch.innerHTML); + return () => calls.push('effect inner dispose ' + scratch.innerHTML); + }); + return hello {i}; + } + + function Outer() { + i++; + const [state, setState] = useState(false); + set = () => setState(!state); + useLayoutEffect(() => { + calls.push('layout outer call ' + scratch.innerHTML); + return () => calls.push('layout outer dispose ' + scratch.innerHTML); + }); + useEffect(() => { + calls.push('effect outer call ' + scratch.innerHTML); + return () => calls.push('effect outer dispose ' + scratch.innerHTML); + }); + return ; + } + + act(() => render(, scratch)); + expect(calls).to.deep.equal([ + 'layout inner call hello 1', + 'layout outer call hello 1', + 'effect inner call hello 1', + 'effect outer call hello 1' + ]); + + // NOTE: this order is (at the time of writing) intentionally different from + // React. React calls all disposes across all components, and then invokes all + // effects across all components. We call disposes and effects in order of components: + // for each component, call its disposes and then its effects. If presented with a + // compelling use case to support inter-component dispose dependencies, then rewrite this + // test to test React's order. In other words, if there is a use case to support calling + // all disposes across components then re-order the lines below to demonstrate the desired behavior. + + act(() => set()); + expect(calls).to.deep.equal([ + 'layout inner call hello 1', + 'layout outer call hello 1', + 'effect inner call hello 1', + 'effect outer call hello 1', + 'layout inner dispose hello 2', + 'layout inner call hello 2', + 'layout outer dispose hello 2', + 'layout outer call hello 2', + 'effect inner dispose hello 2', + 'effect inner call hello 2', + 'effect outer dispose hello 2', + 'effect outer call hello 2' + ]); + }); + + // TODO: I actually think this is an acceptable failure, because we update child first and then parent + // the effects are out of order + it.skip('should run effects child-first even for children separated by memoization', () => { + let ops = []; + + /** @type {() => void} */ + let updateChild; + /** @type {() => void} */ + let updateParent; + + function Child() { + const [, setCount] = useState(0); + updateChild = () => setCount(c => c + 1); + useEffect(() => { + ops.push('child effect'); + }); + return
    Child
    ; + } + + function Parent() { + const [, setCount] = useState(0); + updateParent = () => setCount(c => c + 1); + const memoedChild = useMemo(() => , []); + useEffect(() => { + ops.push('parent effect'); + }); + return ( +
    +
    Parent
    + {memoedChild} +
    + ); + } + + act(() => render(, scratch)); + expect(ops).to.deep.equal(['child effect', 'parent effect']); + + ops = []; + updateChild(); + updateParent(); + act(() => rerender()); + + expect(ops).to.deep.equal(['child effect', 'parent effect']); + }); + + it('should not block hook updates when context updates are enqueued', () => { + const Ctx = createContext({ + value: 0, + setValue: /** @type {*} */ () => {} + }); + + let triggerSubmit = () => {}; + function Child() { + const ctx = useContext(Ctx); + const [shouldSubmit, setShouldSubmit] = useState(false); + triggerSubmit = () => setShouldSubmit(true); + + useEffect(() => { + if (shouldSubmit) { + // Update parent state and child state at the same time + ctx.setValue(v => v + 1); + setShouldSubmit(false); + } + }, [shouldSubmit]); + + return

    {ctx.value}

    ; + } + + function App() { + const [value, setValue] = useState(0); + const ctx = useMemo(() => { + return { value, setValue }; + }, [value]); + return ( + + + + ); + } + + act(() => { + render(, scratch); + }); + + expect(scratch.textContent).to.equal('0'); + + act(() => { + triggerSubmit(); + }); + expect(scratch.textContent).to.equal('1'); + + // This is where the update wasn't applied + act(() => { + triggerSubmit(); + }); + expect(scratch.textContent).to.equal('2'); + }); + + it('parent and child refs should be set before all effects', () => { + const anchorId = 'anchor'; + const tooltipId = 'tooltip'; + const effectLog = []; + + let useRef2 = sinon.spy(init => { + const realRef = useRef(init); + const ref = useRef(init); + Object.defineProperty(ref, 'current', { + get: () => realRef.current, + set: value => { + realRef.current = value; + effectLog.push('set ref ' + value?.tagName); + } + }); + return ref; + }); + + function Tooltip({ anchorRef, children }) { + // For example, used to manually position the tooltip + const tooltipRef = useRef2(null); + + useLayoutEffect(() => { + expect(anchorRef.current?.id).to.equal(anchorId); + expect(tooltipRef.current?.id).to.equal(tooltipId); + effectLog.push('tooltip layout effect'); + }, [anchorRef, tooltipRef]); + useEffect(() => { + expect(anchorRef.current?.id).to.equal(anchorId); + expect(tooltipRef.current?.id).to.equal(tooltipId); + effectLog.push('tooltip effect'); + }, [anchorRef, tooltipRef]); + + return ( +
    +
    + {children} +
    +
    + ); + } + + function App() { + // For example, used to define what element to anchor the tooltip to + const anchorRef = useRef2(null); + + useLayoutEffect(() => { + expect(anchorRef.current?.id).to.equal(anchorId); + effectLog.push('anchor layout effect'); + }, [anchorRef]); + useEffect(() => { + expect(anchorRef.current?.id).to.equal(anchorId); + effectLog.push('anchor effect'); + }, [anchorRef]); + + return ( +
    +

    + More info +

    + a tooltip +
    + ); + } + + act(() => { + render(, scratch); + }); + + expect(effectLog).to.deep.equal([ + 'set ref P', + 'set ref DIV', + 'tooltip layout effect', + 'anchor layout effect', + 'tooltip effect', + 'anchor effect' + ]); + }); + + it('should not loop infinitely', () => { + const actions = []; + let toggle; + function App() { + const [value, setValue] = useState(false); + + const data = useMemo(() => { + actions.push('memo'); + return {}; + }, [value]); + + const [prevData, setPreviousData] = useState(data); + if (prevData !== data) { + setPreviousData(data); + } + + actions.push('render'); + toggle = () => setValue(!value); + return
    Value: {JSON.stringify(value)}
    ; + } + + act(() => { + render(, scratch); + }); + expect(actions).to.deep.equal(['memo', 'render']); + expect(scratch.innerHTML).to.deep.equal('
    Value: false
    '); + + act(() => { + toggle(); + }); + expect(actions).to.deep.equal([ + 'memo', + 'render', + 'memo', + 'render', + 'render' + ]); + expect(scratch.innerHTML).to.deep.equal('
    Value: true
    '); + }); +}); diff --git a/hooks/test/browser/componentDidCatch.test.js b/hooks/test/browser/componentDidCatch.test.js new file mode 100644 index 0000000000..bcf8f5c263 --- /dev/null +++ b/hooks/test/browser/componentDidCatch.test.js @@ -0,0 +1,60 @@ +import { createElement, render, Component } from 'preact'; +import { act } from 'preact/test-utils'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { useEffect } from 'preact/hooks'; + +/** @jsx createElement */ + +describe('errorInfo', () => { + /** @type {HTMLDivElement} */ + let scratch; + + beforeEach(() => { + scratch = setupScratch(); + }); + + afterEach(() => { + teardown(scratch); + }); + + it('should pass errorInfo on hook unmount error', () => { + let info; + let update; + class Receiver extends Component { + constructor(props) { + super(props); + this.state = { error: null, i: 0 }; + update = this.setState.bind(this); + } + componentDidCatch(error, errorInfo) { + info = errorInfo; + this.setState({ error }); + } + render() { + if (this.state.error) return
    ; + if (this.state.i === 0) return ; + return null; + } + } + + function ThrowErr() { + useEffect(() => { + return () => { + throw new Error('fail'); + }; + }, []); + + return

    ; + } + + act(() => { + render(, scratch); + }); + + act(() => { + update({ i: 1 }); + }); + + expect(info).to.deep.equal({}); + }); +}); diff --git a/hooks/test/browser/errorBoundary.test.js b/hooks/test/browser/errorBoundary.test.js new file mode 100644 index 0000000000..415d76cc91 --- /dev/null +++ b/hooks/test/browser/errorBoundary.test.js @@ -0,0 +1,232 @@ +import { Fragment, createElement, render } from 'preact'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { useErrorBoundary, useLayoutEffect, useState } from 'preact/hooks'; +import { setupRerender } from 'preact/test-utils'; + +/** @jsx createElement */ + +describe('errorBoundary', () => { + /** @type {HTMLDivElement} */ + let scratch, rerender; + + beforeEach(() => { + scratch = setupScratch(); + rerender = setupRerender(); + }); + + afterEach(() => { + teardown(scratch); + }); + + it('catches errors', () => { + let resetErr, + success = false; + const Throws = () => { + throw new Error('test'); + }; + + const App = props => { + const [err, reset] = useErrorBoundary(); + resetErr = reset; + return err ?

    Error

    : success ?

    Success

    : ; + }; + + render(, scratch); + rerender(); + expect(scratch.innerHTML).to.equal('

    Error

    '); + + success = true; + resetErr(); + rerender(); + expect(scratch.innerHTML).to.equal('

    Success

    '); + }); + + it('calls the errorBoundary callback', () => { + const spy = sinon.spy(); + const error = new Error('test'); + const Throws = () => { + throw error; + }; + + const App = props => { + const [err] = useErrorBoundary(spy); + return err ?

    Error

    : ; + }; + + render(, scratch); + rerender(); + expect(scratch.innerHTML).to.equal('

    Error

    '); + expect(spy).to.be.calledOnce; + expect(spy).to.be.calledWith(error, {}); + }); + + it('returns error', () => { + const error = new Error('test'); + const Throws = () => { + throw error; + }; + + let returned; + const App = () => { + const [err] = useErrorBoundary(); + returned = err; + return err ?

    Error

    : ; + }; + + render(, scratch); + rerender(); + expect(returned).to.equal(error); + }); + + it('does not leave a stale closure', () => { + const spy = sinon.spy(), + spy2 = sinon.spy(); + let resetErr; + const error = new Error('test'); + const Throws = () => { + throw error; + }; + + const App = props => { + const [err, reset] = useErrorBoundary(props.onError); + resetErr = reset; + return err ?

    Error

    : ; + }; + + render(, scratch); + rerender(); + expect(scratch.innerHTML).to.equal('

    Error

    '); + expect(spy).to.be.calledOnce; + expect(spy).to.be.calledWith(error); + + resetErr(); + render(, scratch); + rerender(); + expect(spy).to.be.calledOnce; + expect(spy2).to.be.calledOnce; + expect(spy2).to.be.calledWith(error); + expect(scratch.innerHTML).to.equal('

    Error

    '); + }); + + it('does not invoke old effects when a cleanup callback throws an error and is handled', () => { + let throwErr = false; + let thrower = sinon.spy(() => { + if (throwErr) { + throw new Error('test'); + } + }); + let badEffect = sinon.spy(() => thrower); + let goodEffect = sinon.spy(); + + function EffectThrowsError() { + useLayoutEffect(badEffect); + return Test; + } + + function Child({ children }) { + useLayoutEffect(goodEffect); + return children; + } + + function App() { + const [err] = useErrorBoundary(); + return err ? ( +

    Error

    + ) : ( + + + + ); + } + + render(, scratch); + expect(scratch.innerHTML).to.equal('Test'); + expect(badEffect).to.be.calledOnce; + expect(goodEffect).to.be.calledOnce; + + throwErr = true; + render(, scratch); + rerender(); + expect(scratch.innerHTML).to.equal('

    Error

    '); + expect(thrower).to.be.calledOnce; + expect(badEffect).to.be.calledOnce; + expect(goodEffect).to.be.calledOnce; + }); + + it('should not duplicate in lists where an item throws and the parent catches and returns a differing type', () => { + const baseTodos = [ + { text: 'first item', completed: false }, + { text: 'Test the feature', completed: false }, + { text: 'another item', completed: false } + ]; + + function TodoList() { + const [todos, setTodos] = useState([...baseTodos]); + + return ( + +
      + {todos.map((todo, index) => ( + { + todos[index] = { + ...todos[index], + completed: !todos[index].completed + }; + setTodos([...todos]); + }} + todo={todo} + index={index} + /> + ))} +
    +
    + ); + } + + function TodoItem(props) { + const [error] = useErrorBoundary(); + + if (error) { + return
  • An error occurred: {error}
  • ; + } + + return ; + } + let set; + function TodoItemInner({ todo, index, toggleTodo }) { + if (todo.completed) { + throw new Error('Todo completed!'); + } + + if (index === 1) { + set = toggleTodo; + } + + return ( +
  • + +
  • + ); + } + + render(, scratch); + expect(scratch.innerHTML).to.equal( + '
    ' + ); + + set(); + rerender(); + expect(scratch.innerHTML).to.equal( + '
    • An error occurred:
    ' + ); + }); +}); diff --git a/hooks/test/browser/hooks.options.test.js b/hooks/test/browser/hooks.options.test.js new file mode 100644 index 0000000000..ca88d1f4dc --- /dev/null +++ b/hooks/test/browser/hooks.options.test.js @@ -0,0 +1,154 @@ +import { + afterDiffSpy, + beforeRenderSpy, + unmountSpy, + hookSpy +} from '../../../test/_util/optionSpies'; + +import { setupRerender, act } from 'preact/test-utils'; +import { createElement, render, createContext, options } from 'preact'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { + useState, + useReducer, + useEffect, + useLayoutEffect, + useRef, + useImperativeHandle, + useMemo, + useCallback, + useContext, + useErrorBoundary +} from 'preact/hooks'; + +/** @jsx createElement */ + +describe('hook options', () => { + /** @type {HTMLDivElement} */ + let scratch; + + /** @type {() => void} */ + let rerender; + + /** @type {() => void} */ + let increment; + + beforeEach(() => { + scratch = setupScratch(); + rerender = setupRerender(); + + afterDiffSpy.resetHistory(); + unmountSpy.resetHistory(); + beforeRenderSpy.resetHistory(); + hookSpy.resetHistory(); + }); + + afterEach(() => { + teardown(scratch); + }); + + function App() { + const [count, setCount] = useState(0); + increment = () => setCount(prevCount => prevCount + 1); + return
    {count}
    ; + } + + it('should call old options on mount', () => { + render(, scratch); + + expect(beforeRenderSpy).to.have.been.called; + expect(afterDiffSpy).to.have.been.called; + }); + + it('should call old options.diffed on update', () => { + render(, scratch); + + increment(); + rerender(); + + expect(beforeRenderSpy).to.have.been.called; + expect(afterDiffSpy).to.have.been.called; + }); + + it('should call old options on unmount', () => { + render(, scratch); + render(null, scratch); + + expect(unmountSpy).to.have.been.called; + }); + + it('should detect hooks', () => { + const USE_STATE = 1; + const USE_REDUCER = 2; + const USE_EFFECT = 3; + const USE_LAYOUT_EFFECT = 4; + const USE_REF = 5; + const USE_IMPERATIVE_HANDLE = 6; + const USE_MEMO = 7; + const USE_CALLBACK = 8; + const USE_CONTEXT = 9; + const USE_ERROR_BOUNDARY = 10; + + const Ctx = createContext(null); + + function App() { + useState(0); + useReducer(x => x, 0); + useEffect(() => null, []); + useLayoutEffect(() => null, []); + const ref = useRef(null); + useImperativeHandle(ref, () => null); + useMemo(() => null, []); + useCallback(() => null, []); + useContext(Ctx); + useErrorBoundary(() => null); + } + + render( + + + , + scratch + ); + + expect(hookSpy.args.map(arg => [arg[1], arg[2]])).to.deep.equal([ + [0, USE_STATE], + [1, USE_REDUCER], + [2, USE_EFFECT], + [3, USE_LAYOUT_EFFECT], + [4, USE_REF], + [5, USE_IMPERATIVE_HANDLE], + [6, USE_MEMO], + [7, USE_CALLBACK], + [8, USE_CONTEXT], + [9, USE_ERROR_BOUNDARY], + // Belongs to useErrorBoundary that uses multiple native hooks. + [10, USE_STATE] + ]); + }); + + describe('Effects', () => { + beforeEach(() => { + options._skipEffects = options.__s = true; + }); + + afterEach(() => { + options._skipEffects = options.__s = false; + }); + + it('should skip effect hooks', () => { + const spy = sinon.spy(); + function App() { + useEffect(spy, []); + useLayoutEffect(spy, []); + return null; + } + + act(() => { + render(, scratch); + }); + + expect(spy.callCount).to.equal(0); + }); + }); +}); diff --git a/hooks/test/browser/useCallback.test.js b/hooks/test/browser/useCallback.test.js new file mode 100644 index 0000000000..151fed9af2 --- /dev/null +++ b/hooks/test/browser/useCallback.test.js @@ -0,0 +1,41 @@ +import { createElement, render } from 'preact'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { useCallback } from 'preact/hooks'; + +/** @jsx createElement */ + +describe('useCallback', () => { + /** @type {HTMLDivElement} */ + let scratch; + + beforeEach(() => { + scratch = setupScratch(); + }); + + afterEach(() => { + teardown(scratch); + }); + + it('only recomputes the callback when inputs change', () => { + const callbacks = []; + + function Comp({ a, b }) { + const cb = useCallback(() => a + b, [a, b]); + callbacks.push(cb); + return null; + } + + render(, scratch); + render(, scratch); + + expect(callbacks[0]).to.equal(callbacks[1]); + expect(callbacks[0]()).to.equal(2); + + render(, scratch); + render(, scratch); + + expect(callbacks[1]).to.not.equal(callbacks[2]); + expect(callbacks[2]).to.equal(callbacks[3]); + expect(callbacks[2]()).to.equal(3); + }); +}); diff --git a/hooks/test/browser/useContext.test.js b/hooks/test/browser/useContext.test.js new file mode 100644 index 0000000000..67b7851406 --- /dev/null +++ b/hooks/test/browser/useContext.test.js @@ -0,0 +1,351 @@ +import { createElement, render, createContext, Component } from 'preact'; +import { act } from 'preact/test-utils'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { useContext, useEffect, useState } from 'preact/hooks'; + +/** @jsx createElement */ + +describe('useContext', () => { + /** @type {HTMLDivElement} */ + let scratch; + + beforeEach(() => { + scratch = setupScratch(); + }); + + afterEach(() => { + teardown(scratch); + }); + + it('gets values from context', () => { + const values = []; + const Context = createContext(13); + + function Comp() { + const value = useContext(Context); + values.push(value); + return null; + } + + render(, scratch); + render( + + + , + scratch + ); + render( + + + , + scratch + ); + + expect(values).to.deep.equal([13, 42, 69]); + }); + + it('should use default value', () => { + const Foo = createContext(42); + const spy = sinon.spy(); + + function App() { + spy(useContext(Foo)); + return
    ; + } + + render(, scratch); + expect(spy).to.be.calledWith(42); + }); + + it('should update when value changes with nonUpdating Component on top', done => { + const spy = sinon.spy(); + const Ctx = createContext(0); + + class NoUpdate extends Component { + shouldComponentUpdate() { + return false; + } + render() { + return this.props.children; + } + } + + function App(props) { + return ( + + + + + + ); + } + + function Comp() { + const value = useContext(Ctx); + spy(value); + return

    {value}

    ; + } + + render(, scratch); + expect(spy).to.be.calledOnce; + expect(spy).to.be.calledWith(0); + render(, scratch); + + // Wait for enqueued hook update + setTimeout(() => { + // Should not be called a third time + expect(spy).to.be.calledTwice; + expect(spy).to.be.calledWith(1); + done(); + }, 0); + }); + + it('should only update when value has changed', done => { + const spy = sinon.spy(); + const Ctx = createContext(0); + + function App(props) { + return ( + + + + ); + } + + function Comp() { + const value = useContext(Ctx); + spy(value); + return

    {value}

    ; + } + + render(, scratch); + expect(spy).to.be.calledOnce; + expect(spy).to.be.calledWith(0); + render(, scratch); + + expect(spy).to.be.calledTwice; + expect(spy).to.be.calledWith(1); + + // Wait for enqueued hook update + setTimeout(() => { + // Should not be called a third time + expect(spy).to.be.calledTwice; + done(); + }, 0); + }); + + it('should allow multiple context hooks at the same time', () => { + const Foo = createContext(0); + const Bar = createContext(10); + const spy = sinon.spy(); + const unmountspy = sinon.spy(); + + function Comp() { + const foo = useContext(Foo); + const bar = useContext(Bar); + spy(foo, bar); + useEffect(() => () => unmountspy()); + + return
    ; + } + + render( + + + + + , + scratch + ); + + expect(spy).to.be.calledOnce; + expect(spy).to.be.calledWith(0, 10); + + render( + + + + + , + scratch + ); + + expect(spy).to.be.calledTwice; + expect(unmountspy).not.to.be.called; + }); + + it('should only subscribe a component once', () => { + const values = []; + const Context = createContext(13); + let provider, subSpy; + + function Comp() { + const value = useContext(Context); + values.push(value); + return null; + } + + render(, scratch); + + render( + (provider = p)} value={42}> + + , + scratch + ); + subSpy = sinon.spy(provider, 'sub'); + + render( + + + , + scratch + ); + expect(subSpy).to.not.have.been.called; + + expect(values).to.deep.equal([13, 42, 69]); + }); + + it('should maintain context', done => { + const context = createContext(null); + const { Provider } = context; + const first = { name: 'first' }; + const second = { name: 'second' }; + + const Input = () => { + const config = useContext(context); + + // Avoid eslint complaining about unused first value + const state = useState('initial'); + const set = state[1]; + + useEffect(() => { + // Schedule the update on the next frame + requestAnimationFrame(() => { + set('irrelevant'); + }); + }, [config]); + + return
    {config.name}
    ; + }; + + const App = props => { + const [config, setConfig] = useState({}); + + useEffect(() => { + setConfig(props.config); + }, [props.config]); + + return ( + + + + ); + }; + + act(() => { + render(, scratch); + + // Create a new div to append the `second` case + const div = scratch.appendChild(document.createElement('div')); + render(, div); + }); + + // Push the expect into the next frame + requestAnimationFrame(() => { + expect(scratch.innerHTML).equal( + '
    first
    second
    ' + ); + done(); + }); + }); + + it('should not rerender consumers that have been unmounted', () => { + const context = createContext(0); + const Provider = context.Provider; + + const Inner = sinon.spy(() => { + const value = useContext(context); + return
    {value}
    ; + }); + + let toggleConsumer; + let changeValue; + class App extends Component { + constructor() { + super(); + + this.state = { value: 0, show: true }; + changeValue = value => this.setState({ value }); + toggleConsumer = () => this.setState(({ show }) => ({ show: !show })); + } + render(props, state) { + return ( + +
    {state.show ? : null}
    +
    + ); + } + } + + render(, scratch); + expect(scratch.innerHTML).to.equal('
    0
    '); + expect(Inner).to.have.been.calledOnce; + + act(() => changeValue(1)); + expect(scratch.innerHTML).to.equal('
    1
    '); + expect(Inner).to.have.been.calledTwice; + + act(() => toggleConsumer()); + expect(scratch.innerHTML).to.equal('
    '); + expect(Inner).to.have.been.calledTwice; + + act(() => changeValue(2)); + expect(scratch.innerHTML).to.equal('
    '); + expect(Inner).to.have.been.calledTwice; + }); + + it('should rerender when reset to defaultValue', () => { + const defaultValue = { state: 'hi' }; + const context = createContext(defaultValue); + let set; + + const Consumer = () => { + const ctx = useContext(context); + return

    {ctx.state}

    ; + }; + + class NoUpdate extends Component { + shouldComponentUpdate() { + return false; + } + + render() { + return ; + } + } + + const Provider = () => { + const [state, setState] = useState(defaultValue); + set = setState; + return ( + + + + ); + }; + + render(, scratch); + expect(scratch.innerHTML).to.equal('

    hi

    '); + + act(() => { + set({ state: 'bye' }); + }); + expect(scratch.innerHTML).to.equal('

    bye

    '); + + act(() => { + set(defaultValue); + }); + expect(scratch.innerHTML).to.equal('

    hi

    '); + }); +}); diff --git a/hooks/test/browser/useDebugValue.test.js b/hooks/test/browser/useDebugValue.test.js new file mode 100644 index 0000000000..d19ff6b79e --- /dev/null +++ b/hooks/test/browser/useDebugValue.test.js @@ -0,0 +1,71 @@ +import { createElement, render, options } from 'preact'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { useDebugValue, useState } from 'preact/hooks'; + +/** @jsx createElement */ + +describe('useDebugValue', () => { + /** @type {HTMLDivElement} */ + let scratch; + + beforeEach(() => { + scratch = setupScratch(); + }); + + afterEach(() => { + teardown(scratch); + delete options.useDebugValue; + }); + + it('should do nothing when no options hook is present', () => { + function useFoo() { + useDebugValue('foo'); + return useState(0); + } + + function App() { + let [v] = useFoo(); + return
    {v}
    ; + } + + expect(() => render(, scratch)).to.not.throw(); + }); + + it('should call options hook with value', () => { + let spy = (options.useDebugValue = sinon.spy()); + + function useFoo() { + useDebugValue('foo'); + return useState(0); + } + + function App() { + let [v] = useFoo(); + return
    {v}
    ; + } + + render(, scratch); + + expect(spy).to.be.calledOnce; + expect(spy).to.be.calledWith('foo'); + }); + + it('should apply optional formatter', () => { + let spy = (options.useDebugValue = sinon.spy()); + + function useFoo() { + useDebugValue('foo', x => x + 'bar'); + return useState(0); + } + + function App() { + let [v] = useFoo(); + return
    {v}
    ; + } + + render(, scratch); + + expect(spy).to.be.calledOnce; + expect(spy).to.be.calledWith('foobar'); + }); +}); diff --git a/hooks/test/browser/useEffect.test.js b/hooks/test/browser/useEffect.test.js new file mode 100644 index 0000000000..ce4bb73ab3 --- /dev/null +++ b/hooks/test/browser/useEffect.test.js @@ -0,0 +1,639 @@ +import { act, teardown as teardownAct } from 'preact/test-utils'; +import { createElement, render, Fragment, Component } from 'preact'; +import { useEffect, useState, useRef } from 'preact/hooks'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { useEffectAssertions } from './useEffectAssertions.test'; +import { scheduleEffectAssert } from '../_util/useEffectUtil'; + +/** @jsx createElement */ + +describe('useEffect', () => { + /** @type {HTMLDivElement} */ + let scratch; + + beforeEach(() => { + scratch = setupScratch(); + }); + + afterEach(() => { + teardown(scratch); + }); + + useEffectAssertions(useEffect, scheduleEffectAssert); + + it('calls the effect immediately if another render is about to start', () => { + const cleanupFunction = sinon.spy(); + const callback = sinon.spy(() => cleanupFunction); + + function Comp() { + useEffect(callback); + return null; + } + + render(, scratch); + render(, scratch); + + expect(cleanupFunction).to.be.not.called; + expect(callback).to.be.calledOnce; + + render(, scratch); + + expect(cleanupFunction).to.be.calledOnce; + expect(callback).to.be.calledTwice; + }); + + it('cancels the effect when the component get unmounted before it had the chance to run it', () => { + const cleanupFunction = sinon.spy(); + const callback = sinon.spy(() => cleanupFunction); + + function Comp() { + useEffect(callback); + return null; + } + + render(, scratch); + render(null, scratch); + + return scheduleEffectAssert(() => { + expect(cleanupFunction).to.not.be.called; + expect(callback).to.not.be.called; + }); + }); + + it('should execute multiple effects in same component in the right order', () => { + let executionOrder = []; + const App = ({ i }) => { + executionOrder = []; + useEffect(() => { + executionOrder.push('action1'); + return () => executionOrder.push('cleanup1'); + }, [i]); + useEffect(() => { + executionOrder.push('action2'); + return () => executionOrder.push('cleanup2'); + }, [i]); + return

    Test

    ; + }; + act(() => render(, scratch)); + act(() => render(, scratch)); + expect(executionOrder).to.deep.equal([ + 'cleanup1', + 'cleanup2', + 'action1', + 'action2' + ]); + }); + + it('should execute effects in parent if child throws in effect', async () => { + let executionOrder = []; + + const Child = () => { + useEffect(() => { + executionOrder.push('child'); + throw new Error('test'); + }, []); + + useEffect(() => { + executionOrder.push('child after throw'); + return () => executionOrder.push('child after throw cleanup'); + }, []); + + return

    Test

    ; + }; + + const Parent = () => { + useEffect(() => { + executionOrder.push('parent'); + return () => executionOrder.push('parent cleanup'); + }, []); + return ; + }; + + class ErrorBoundary extends Component { + componentDidCatch(error) { + this.setState({ error }); + } + + render({ children }, { error }) { + return error ?
    error
    : children; + } + } + + act(() => + render( + + + , + scratch + ) + ); + + expect(executionOrder).to.deep.equal(['child', 'parent', 'parent cleanup']); + expect(scratch.innerHTML).to.equal('
    error
    '); + }); + + it('should throw an error upwards', () => { + const spy = sinon.spy(); + let errored = false; + + const Page1 = () => { + const [state, setState] = useState('loading'); + useEffect(() => { + setState('loaded'); + }, []); + return

    {state}

    ; + }; + + const Page2 = () => { + useEffect(() => { + throw new Error('err'); + }, []); + return

    invisible

    ; + }; + + class App extends Component { + componentDidCatch(err) { + spy(); + errored = err; + this.forceUpdate(); + } + + render(props, state) { + if (errored) { + return

    Error

    ; + } + + return {props.page === 1 ? : }; + } + } + + act(() => render(, scratch)); + expect(spy).to.not.be.called; + expect(scratch.innerHTML).to.equal('

    loaded

    '); + + act(() => render(, scratch)); + expect(spy).to.be.calledOnce; + expect(scratch.innerHTML).to.equal('

    Error

    '); + errored = false; + + act(() => render(, scratch)); + expect(spy).to.be.calledOnce; + expect(scratch.innerHTML).to.equal('

    loaded

    '); + }); + + it('should throw an error upwards from return', () => { + const spy = sinon.spy(); + let errored = false; + + const Page1 = () => { + const [state, setState] = useState('loading'); + useEffect(() => { + setState('loaded'); + }, []); + return

    {state}

    ; + }; + + const Page2 = () => { + useEffect(() => { + return () => { + throw new Error('err'); + }; + }, []); + return

    Load

    ; + }; + + class App extends Component { + componentDidCatch(err) { + spy(); + errored = err; + this.forceUpdate(); + } + + render(props, state) { + if (errored) { + return

    Error

    ; + } + + return {props.page === 1 ? : }; + } + } + + act(() => render(, scratch)); + expect(scratch.innerHTML).to.equal('

    Load

    '); + + act(() => render(, scratch)); + expect(spy).to.be.calledOnce; + expect(scratch.innerHTML).to.equal('

    Error

    '); + }); + + it('catches errors when error is invoked during render', () => { + const spy = sinon.spy(); + let errored; + + function Comp() { + useEffect(() => { + throw new Error('hi'); + }); + return null; + } + + class App extends Component { + componentDidCatch(err) { + spy(); + errored = err; + this.forceUpdate(); + } + + render(props, state) { + if (errored) { + return

    Error

    ; + } + + return ; + } + } + + render(, scratch); + act(() => { + render(, scratch); + }); + expect(spy).to.be.calledOnce; + expect(errored).to.be.an('Error').with.property('message', 'hi'); + expect(scratch.innerHTML).to.equal('

    Error

    '); + }); + + it('should allow creating a new root', () => { + const root = document.createElement('div'); + const global = document.createElement('div'); + scratch.appendChild(root); + scratch.appendChild(global); + + const Modal = props => { + let [, setCanProceed] = useState(true); + let ChildProp = props.content; + + return ( +
    + +
    + ); + }; + + const Inner = () => { + useEffect(() => { + render(
    global
    , global); + }, []); + + return
    Inner
    ; + }; + + act(() => { + render( + { + props.setCanProceed(false); + return ; + }} + />, + root + ); + }); + + expect(scratch.innerHTML).to.equal( + '
    Inner
    global
    ' + ); + }); + + it('should not crash when effect returns truthy non-function value', () => { + const callback = sinon.spy(() => 'truthy'); + function Comp() { + useEffect(callback); + return null; + } + + render(, scratch); + render(, scratch); + + expect(callback).to.have.been.calledOnce; + + render(
    Replacement
    , scratch); + }); + + it('support render roots from an effect', async () => { + let promise, increment; + + const Counter = () => { + const [count, setCount] = useState(0); + const renderRoot = useRef(); + useEffect(() => { + if (count > 0) { + const div = renderRoot.current; + return () => render(, div); + } + return () => 'test'; + }, [count]); + + increment = () => { + setCount(x => x + 1); + promise = new Promise(res => { + setTimeout(() => { + setCount(x => x + 1); + res(); + }); + }); + }; + + return ( +
    +
    Count: {count}
    +
    +
    + ); + }; + + const Dummy = () =>
    dummy
    ; + + render(, scratch); + + expect(scratch.innerHTML).to.equal( + '
    Count: 0
    ' + ); + + act(() => { + increment(); + }); + await promise; + act(() => {}); + expect(scratch.innerHTML).to.equal( + '
    Count: 2
    dummy
    ' + ); + }); + + it('hooks should be called in right order', async () => { + teardownAct(); + + let increment; + + const Counter = () => { + const [count, setCount] = useState(0); + useState('binggo!!'); + const renderRoot = useRef(); + useEffect(() => { + const div = renderRoot.current; + render(, div); + }, [count]); + + increment = () => { + setCount(x => x + 1); + return Promise.resolve().then(() => setCount(x => x + 1)); + }; + + return ( +
    +
    Count: {count}
    +
    +
    + ); + }; + + const Dummy = () => { + useState(); + return
    dummy
    ; + }; + + render(, scratch); + + expect(scratch.innerHTML).to.equal( + '
    Count: 0
    ' + ); + /** Using the act function will affect the timing of the useEffect */ + await increment(); + + expect(scratch.innerHTML).to.equal( + '
    Count: 2
    dummy
    ' + ); + }); + + it('handles errors correctly', () => { + class ErrorBoundary extends Component { + constructor(props) { + super(props); + this.state = { error: null }; + } + + componentDidCatch(error) { + this.setState({ error: 'oh no' }); + } + + render() { + return this.state.error ? ( +

    Error! {this.state.error}

    + ) : ( + this.props.children + ); + } + } + + let update; + const firstEffectSpy = sinon.spy(); + const firstEffectcleanup = sinon.spy(); + const secondEffectSpy = sinon.spy(); + const secondEffectcleanup = sinon.spy(); + + const MainContent = () => { + const [val, setVal] = useState(false); + + update = () => setVal(!val); + useEffect(() => { + firstEffectSpy(); + return () => { + firstEffectcleanup(); + throw new Error('oops'); + }; + }, [val]); + + useEffect(() => { + secondEffectSpy(); + return () => { + secondEffectcleanup(); + }; + }, []); + + return

    Hello world

    ; + }; + + act(() => { + render( + + + , + scratch + ); + }); + + expect(firstEffectSpy).to.be.calledOnce; + expect(secondEffectSpy).to.be.calledOnce; + + act(() => { + update(); + }); + + expect(firstEffectSpy).to.be.calledOnce; + expect(secondEffectSpy).to.be.calledOnce; + expect(firstEffectcleanup).to.be.calledOnce; + expect(secondEffectcleanup).to.be.calledOnce; + }); + + it('orders effects effectively', () => { + const calls = []; + const GrandChild = ({ id }) => { + useEffect(() => { + calls.push(`${id} - Effect`); + return () => { + calls.push(`${id} - Cleanup`); + }; + }, [id]); + return

    {id}

    ; + }; + + const Child = ({ id }) => { + useEffect(() => { + calls.push(`${id} - Effect`); + return () => { + calls.push(`${id} - Cleanup`); + }; + }, [id]); + return ( + + + + + ); + }; + + function Parent() { + useEffect(() => { + calls.push('Parent - Effect'); + return () => { + calls.push('Parent - Cleanup'); + }; + }, []); + return ( +
    + +
    + +
    + +
    + ); + } + + act(() => { + render(, scratch); + }); + + expect(calls).to.deep.equal([ + 'Child-1-GrandChild-1 - Effect', + 'Child-1-GrandChild-2 - Effect', + 'Child-1 - Effect', + 'Child-2-GrandChild-1 - Effect', + 'Child-2-GrandChild-2 - Effect', + 'Child-2 - Effect', + 'Child-3-GrandChild-1 - Effect', + 'Child-3-GrandChild-2 - Effect', + 'Child-3 - Effect', + 'Parent - Effect' + ]); + }); + + it('should cancel effects from a disposed render', () => { + const calls = []; + const App = () => { + const [greeting, setGreeting] = useState('bye'); + + useEffect(() => { + calls.push('doing effect' + greeting); + return () => { + calls.push('cleaning up' + greeting); + }; + }, [greeting]); + + if (greeting === 'bye') { + setGreeting('hi'); + } + + return

    {greeting}

    ; + }; + + act(() => { + render(, scratch); + }); + expect(calls.length).to.equal(1); + expect(calls).to.deep.equal(['doing effecthi']); + }); + + it('should not rerun committed effects', () => { + const calls = []; + const App = ({ i }) => { + const [greeting, setGreeting] = useState('hi'); + + useEffect(() => { + calls.push('doing effect' + greeting); + return () => { + calls.push('cleaning up' + greeting); + }; + }, []); + + if (i === 2) { + setGreeting('bye'); + } + + return

    {greeting}

    ; + }; + + act(() => { + render(, scratch); + }); + expect(calls.length).to.equal(1); + expect(calls).to.deep.equal(['doing effecthi']); + + act(() => { + render(, scratch); + }); + }); + + it('should not schedule effects that have no change', () => { + const calls = []; + let set; + const App = ({ i }) => { + const [greeting, setGreeting] = useState('hi'); + set = setGreeting; + + useEffect(() => { + calls.push('doing effect' + greeting); + return () => { + calls.push('cleaning up' + greeting); + }; + }, [greeting]); + + if (greeting === 'bye') { + setGreeting('hi'); + } + + return

    {greeting}

    ; + }; + + act(() => { + render(, scratch); + }); + expect(calls.length).to.equal(1); + expect(calls).to.deep.equal(['doing effecthi']); + + act(() => { + set('bye'); + }); + expect(calls.length).to.equal(1); + expect(calls).to.deep.equal(['doing effecthi']); + }); +}); diff --git a/hooks/test/browser/useEffectAssertions.test.js b/hooks/test/browser/useEffectAssertions.test.js new file mode 100644 index 0000000000..74ba2322e6 --- /dev/null +++ b/hooks/test/browser/useEffectAssertions.test.js @@ -0,0 +1,142 @@ +import { setupRerender } from 'preact/test-utils'; +import { createElement, render } from 'preact'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; + +/** @jsx createElement */ + +// Common behaviors between all effect hooks +export function useEffectAssertions(useEffect, scheduleEffectAssert) { + /** @type {HTMLDivElement} */ + let scratch; + + /** @type {() => void} */ + let rerender; + + beforeEach(() => { + scratch = setupScratch(); + rerender = setupRerender(); + }); + + afterEach(() => { + teardown(scratch); + }); + + it('performs the effect after every render by default', () => { + const callback = sinon.spy(); + + function Comp() { + useEffect(callback); + return null; + } + + render(, scratch); + + return scheduleEffectAssert(() => expect(callback).to.be.calledOnce) + .then(() => scheduleEffectAssert(() => expect(callback).to.be.calledOnce)) + .then(() => render(, scratch)) + .then(() => + scheduleEffectAssert(() => expect(callback).to.be.calledTwice) + ); + }); + + it('performs the effect only if one of the inputs changed', () => { + const callback = sinon.spy(); + + function Comp(props) { + useEffect(callback, [props.a, props.b]); + return null; + } + + render(, scratch); + + return scheduleEffectAssert(() => expect(callback).to.be.calledOnce) + .then(() => render(, scratch)) + .then(() => scheduleEffectAssert(() => expect(callback).to.be.calledOnce)) + .then(() => render(, scratch)) + .then(() => + scheduleEffectAssert(() => expect(callback).to.be.calledTwice) + ) + .then(() => render(, scratch)) + .then(() => + scheduleEffectAssert(() => expect(callback).to.be.calledTwice) + ); + }); + + it('performs the effect at mount time and never again if an empty input Array is passed', () => { + const callback = sinon.spy(); + + function Comp() { + useEffect(callback, []); + return null; + } + + render(, scratch); + render(, scratch); + + expect(callback).to.be.calledOnce; + + return scheduleEffectAssert(() => expect(callback).to.be.calledOnce) + .then(() => render(, scratch)) + .then(() => + scheduleEffectAssert(() => expect(callback).to.be.calledOnce) + ); + }); + + it('calls the cleanup function followed by the effect after each render', () => { + const cleanupFunction = sinon.spy(); + const callback = sinon.spy(() => cleanupFunction); + + function Comp() { + useEffect(callback); + return null; + } + + render(, scratch); + + return scheduleEffectAssert(() => { + expect(cleanupFunction).to.be.not.called; + expect(callback).to.be.calledOnce; + }) + .then(() => scheduleEffectAssert(() => expect(callback).to.be.calledOnce)) + .then(() => render(, scratch)) + .then(() => + scheduleEffectAssert(() => { + expect(cleanupFunction).to.be.calledOnce; + expect(callback).to.be.calledTwice; + expect(callback.lastCall.calledAfter(cleanupFunction.lastCall)); + }) + ); + }); + + it('cleanups the effect when the component get unmounted if the effect was called before', () => { + const cleanupFunction = sinon.spy(); + const callback = sinon.spy(() => cleanupFunction); + + function Comp() { + useEffect(callback); + return null; + } + + render(, scratch); + + return scheduleEffectAssert(() => { + render(null, scratch); + rerender(); + expect(cleanupFunction).to.be.calledOnce; + }); + }); + + it('works with closure effect callbacks capturing props', () => { + const values = []; + + function Comp(props) { + useEffect(() => values.push(props.value)); + return null; + } + + render(, scratch); + render(, scratch); + + return scheduleEffectAssert(() => expect(values).to.deep.equal([1, 2])); + }); +} diff --git a/hooks/test/browser/useId.test.js b/hooks/test/browser/useId.test.js new file mode 100644 index 0000000000..390f955df7 --- /dev/null +++ b/hooks/test/browser/useId.test.js @@ -0,0 +1,480 @@ +import { createElement, Fragment, hydrate, render } from 'preact'; +import { useId, useReducer, useState } from 'preact/hooks'; +import { setupRerender } from 'preact/test-utils'; +import { render as rts } from 'preact-render-to-string'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; + +/** @jsx createElement */ + +describe('useId', () => { + /** @type {HTMLDivElement} */ + let scratch, rerender; + + beforeEach(() => { + scratch = setupScratch(); + rerender = setupRerender(); + }); + + afterEach(() => { + teardown(scratch); + }); + + it('keeps the id consistent after an update', () => { + function Comp() { + const id = useId(); + return
    ; + } + + render(, scratch); + const id = scratch.firstChild.getAttribute('id'); + expect(scratch.firstChild.getAttribute('id')).to.equal(id); + + render(, scratch); + expect(scratch.firstChild.getAttribute('id')).to.equal(id); + }); + + it('ids are unique according to dom-depth', () => { + function Child() { + const id = useId(); + const spanId = useId(); + return ( +
    + h +
    + ); + } + + function Comp() { + const id = useId(); + return ( +
    + +
    + ); + } + + render(, scratch); + expect(scratch.innerHTML).to.equal( + '
    h
    ' + ); + + render(, scratch); + expect(scratch.innerHTML).to.equal( + '
    h
    ' + ); + }); + + it('ids are unique across siblings', () => { + function Child() { + const id = useId(); + return h; + } + + function Comp() { + const id = useId(); + return ( +
    + + + +
    + ); + } + + render(, scratch); + expect(scratch.innerHTML).to.equal( + '
    hhh
    ' + ); + + render(, scratch); + expect(scratch.innerHTML).to.equal( + '
    hhh
    ' + ); + }); + + it('correctly handles new elements', () => { + let set; + function Child() { + const id = useId(); + return h; + } + + function Stateful() { + const [state, setState] = useState(false); + set = setState; + return ( +
    + + {state && } +
    + ); + } + + function Comp() { + const id = useId(); + return ( +
    + +
    + ); + } + + render(, scratch); + expect(scratch.innerHTML).to.equal( + '
    h
    ' + ); + + set(true); + rerender(); + expect(scratch.innerHTML).to.equal( + '
    hh
    ' + ); + }); + + it('matches with rts', () => { + const ChildFragmentReturn = ({ children }) => { + return {children}; + }; + + const ChildReturn = ({ children }) => { + return children; + }; + + const SomeMessage = ({ msg }) => { + const id = useId(); + return ( +

    + {msg} {id} +

    + ); + }; + + const Stateful = () => { + const [count, add] = useReducer(c => c + 1, 0); + const id = useId(); + return ( +
    + id: {id}, count: {count} + +
    + ); + }; + + const Component = ({ showStateful = false }) => { + const rootId = useId(); + const paragraphId = useId(); + + return ( +
    + ID: {rootId} +

    Hello world id: {paragraphId}

    + {showStateful ? ( + + ) : ( + + + + + + + + + + )} + + + + + + + + + + + + +
    + ); + }; + + const rtsOutput = rts(); + render(, scratch); + expect(rtsOutput === scratch.innerHTML).to.equal(true); + }); + + it('matches with rts after hydration', () => { + const ChildFragmentReturn = ({ children }) => { + return {children}; + }; + + const ChildReturn = ({ children }) => { + return children; + }; + + const SomeMessage = ({ msg }) => { + const id = useId(); + return ( +

    + {msg} {id} +

    + ); + }; + + const Stateful = () => { + const [count, add] = useReducer(c => c + 1, 0); + const id = useId(); + return ( +
    + id: {id}, count: {count} + +
    + ); + }; + + const Component = ({ showStateful = false }) => { + const rootId = useId(); + const paragraphId = useId(); + + return ( +
    + ID: {rootId} +

    Hello world id: {paragraphId}

    + {showStateful ? ( + + ) : ( + + + + + + + + + + )} + + + + + + + + + + + + +
    + ); + }; + + const rtsOutput = rts(); + + scratch.innerHTML = rtsOutput; + hydrate(, scratch); + expect(rtsOutput).to.equal(scratch.innerHTML); + }); + + it('should be unique across Fragments', () => { + const ids = []; + function Foo() { + const id = useId(); + ids.push(id); + return

    {id}

    ; + } + + function App() { + return ( +
    + + + + +
    + ); + } + + render(, scratch); + + expect(ids[0]).not.to.equal(ids[1]); + }); + + it('should match implicite Fragments with RTS', () => { + function Foo() { + const id = useId(); + return

    {id}

    ; + } + + function Bar(props) { + return props.children; + } + + function App() { + return ( + + + + + + + ); + } + + const rtsOutput = rts(); + + scratch.innerHTML = rtsOutput; + hydrate(, scratch); + expect(rtsOutput).to.equal(scratch.innerHTML); + }); + + it('should skip component top level Fragment child', () => { + const Wrapper = ({ children }) => { + return {children}; + }; + + const ids = []; + function Foo() { + const id = useId(); + ids.push(id); + return

    {id}

    ; + } + + function App() { + const id = useId(); + ids.push(id); + return ( +
    +

    {id}

    + + + +
    + ); + } + + render(, scratch); + expect(ids[0]).not.to.equal(ids[1]); + }); + + it('should skip over HTML', () => { + const ids = []; + + function Foo() { + const id = useId(); + ids.push(id); + return

    {id}

    ; + } + + function App() { + return ( +
    + + + + + + +
    + ); + } + + render(, scratch); + expect(ids[0]).not.to.equal(ids[1]); + }); + + it('should reset for each renderToString roots', () => { + const ids = []; + + function Foo() { + const id = useId(); + ids.push(id); + return

    {id}

    ; + } + + function App() { + return ( +
    + + + + + + +
    + ); + } + + const res1 = rts(); + const res2 = rts(); + expect(res1).to.equal(res2); + }); + + it('should work with conditional components', () => { + function Foo() { + const id = useId(); + return

    {id}

    ; + } + function Bar() { + const id = useId(); + return

    {id}

    ; + } + + let update; + function App() { + const [v, setV] = useState(false); + update = setV; + return
    {!v ? : }
    ; + } + + render(, scratch); + const first = scratch.innerHTML; + + update(v => !v); + rerender(); + expect(first).not.to.equal(scratch.innerHTML); + }); + + it('should return a unique id across invocations of render', () => { + const Id = () => { + const id = useId(); + return
    My id is {id}
    ; + }; + + const App = props => { + return ( +
    + + {props.secondId ? : null} +
    + ); + }; + + render(createElement(App, { secondId: false }), scratch); + expect(scratch.innerHTML).to.equal('
    My id is P0-0
    '); + render(createElement(App, { secondId: true }), scratch); + expect(scratch.innerHTML).to.equal( + '
    My id is P0-0
    My id is P0-1
    ' + ); + }); + + it('should not crash for rendering null after a non-null render', () => { + const Id = () => { + const id = useId(); + return
    My id is {id}
    ; + }; + + const App = props => { + return ( +
    + + {props.secondId ? : null} +
    + ); + }; + + render(createElement(App, { secondId: false }), scratch); + expect(scratch.innerHTML).to.equal('
    My id is P0-0
    '); + render(null, scratch); + expect(scratch.innerHTML).to.equal(''); + }); +}); diff --git a/hooks/test/browser/useImperativeHandle.test.js b/hooks/test/browser/useImperativeHandle.test.js new file mode 100644 index 0000000000..ac71065696 --- /dev/null +++ b/hooks/test/browser/useImperativeHandle.test.js @@ -0,0 +1,222 @@ +import { createElement, render } from 'preact'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { useImperativeHandle, useRef, useState } from 'preact/hooks'; +import { setupRerender } from 'preact/test-utils'; + +/** @jsx createElement */ + +describe('useImperativeHandle', () => { + /** @type {HTMLDivElement} */ + let scratch; + + /** @type {() => void} */ + let rerender; + + beforeEach(() => { + scratch = setupScratch(); + rerender = setupRerender(); + }); + + afterEach(() => { + teardown(scratch); + }); + + it('Mutates given ref', () => { + let ref; + + function Comp() { + ref = useRef({}); + useImperativeHandle(ref, () => ({ test: () => 'test' }), []); + return

    Test

    ; + } + + render(, scratch); + expect(ref.current).to.have.property('test'); + expect(ref.current.test()).to.equal('test'); + }); + + it('calls createHandle after every render by default', () => { + let ref, + createHandleSpy = sinon.spy(); + + function Comp() { + ref = useRef({}); + useImperativeHandle(ref, createHandleSpy); + return

    Test

    ; + } + + render(, scratch); + expect(createHandleSpy).to.have.been.calledOnce; + + render(, scratch); + expect(createHandleSpy).to.have.been.calledTwice; + + render(, scratch); + expect(createHandleSpy).to.have.been.calledThrice; + }); + + it('calls createHandle only on mount if an empty array is passed', () => { + let ref, + createHandleSpy = sinon.spy(); + + function Comp() { + ref = useRef({}); + useImperativeHandle(ref, createHandleSpy, []); + return

    Test

    ; + } + + render(, scratch); + expect(createHandleSpy).to.have.been.calledOnce; + + render(, scratch); + expect(createHandleSpy).to.have.been.calledOnce; + }); + + it('Updates given ref when args change', () => { + let ref, + createHandleSpy = sinon.spy(); + + function Comp({ a }) { + ref = useRef({}); + useImperativeHandle( + ref, + () => { + createHandleSpy(); + return { test: () => 'test' + a }; + }, + [a] + ); + return

    Test

    ; + } + + render(, scratch); + expect(createHandleSpy).to.have.been.calledOnce; + expect(ref.current).to.have.property('test'); + expect(ref.current.test()).to.equal('test0'); + + render(, scratch); + expect(createHandleSpy).to.have.been.calledTwice; + expect(ref.current).to.have.property('test'); + expect(ref.current.test()).to.equal('test1'); + + render(, scratch); + expect(createHandleSpy).to.have.been.calledThrice; + expect(ref.current).to.have.property('test'); + expect(ref.current.test()).to.equal('test0'); + }); + + it('Updates given ref when passed-in ref changes', () => { + let ref1, ref2; + + /** @type {(arg: any) => void} */ + let setRef; + + /** @type {() => void} */ + let updateState; + + const createHandleSpy = sinon.spy(() => ({ + test: () => 'test' + })); + + function Comp() { + ref1 = useRef({}); + ref2 = useRef({}); + + const [ref, setRefInternal] = useState(ref1); + setRef = setRefInternal; + + let [value, setState] = useState(0); + updateState = () => setState((value + 1) % 2); + + useImperativeHandle(ref, createHandleSpy, []); + return

    Test

    ; + } + + render(, scratch); + expect(createHandleSpy).to.have.been.calledOnce; + + updateState(); + rerender(); + expect(createHandleSpy).to.have.been.calledOnce; + + setRef(ref2); + rerender(); + expect(createHandleSpy).to.have.been.calledTwice; + + updateState(); + rerender(); + expect(createHandleSpy).to.have.been.calledTwice; + + setRef(ref1); + rerender(); + expect(createHandleSpy).to.have.been.calledThrice; + }); + + it('should not update ref when args have not changed', () => { + let ref, + createHandleSpy = sinon.spy(() => ({ test: () => 'test' })); + + function Comp() { + ref = useRef({}); + useImperativeHandle(ref, createHandleSpy, [1]); + return

    Test

    ; + } + + render(, scratch); + expect(createHandleSpy).to.have.been.calledOnce; + expect(ref.current.test()).to.equal('test'); + + render(, scratch); + expect(createHandleSpy).to.have.been.calledOnce; + expect(ref.current.test()).to.equal('test'); + }); + + it('should not throw with nullish ref', () => { + function Comp() { + useImperativeHandle(null, () => ({ test: () => 'test' }), [1]); + return

    Test

    ; + } + + expect(() => render(, scratch)).to.not.throw(); + }); + + it('should reset ref object to null when the component get unmounted', () => { + let ref, + createHandleSpy = sinon.spy(() => ({ test: () => 'test' })); + + function Comp() { + ref = useRef({}); + useImperativeHandle(ref, createHandleSpy, [1]); + return

    Test

    ; + } + + render(, scratch); + expect(createHandleSpy).to.have.been.calledOnce; + expect(ref.current).to.not.equal(null); + + render(
    , scratch); + expect(createHandleSpy).to.have.been.calledOnce; + expect(ref.current).to.equal(null); + }); + + it('should reset ref callback to null when the component get unmounted', () => { + const ref = sinon.spy(); + const handle = { test: () => 'test' }; + const createHandleSpy = sinon.spy(() => handle); + + function Comp() { + useImperativeHandle(ref, createHandleSpy, [1]); + return

    Test

    ; + } + + render(, scratch); + expect(createHandleSpy).to.have.been.calledOnce; + expect(ref).to.have.been.calledWith(handle); + + ref.resetHistory(); + + render(
    , scratch); + expect(createHandleSpy).to.have.been.calledOnce; + expect(ref).to.have.been.calledWith(null); + }); +}); diff --git a/hooks/test/browser/useLayoutEffect.test.js b/hooks/test/browser/useLayoutEffect.test.js new file mode 100644 index 0000000000..f20fa1198a --- /dev/null +++ b/hooks/test/browser/useLayoutEffect.test.js @@ -0,0 +1,516 @@ +import { act } from 'preact/test-utils'; +import { createElement, render, Fragment, Component } from 'preact'; +import { + setupScratch, + teardown, + serializeHtml +} from '../../../test/_util/helpers'; +import { useEffectAssertions } from './useEffectAssertions.test'; +import { useLayoutEffect, useRef, useState } from 'preact/hooks'; + +/** @jsx createElement */ + +describe('useLayoutEffect', () => { + /** @type {HTMLDivElement} */ + let scratch; + + beforeEach(() => { + scratch = setupScratch(); + }); + + afterEach(() => { + teardown(scratch); + }); + + // Layout effects fire synchronously + const scheduleEffectAssert = assertFn => + new Promise(resolve => { + assertFn(); + resolve(); + }); + + useEffectAssertions(useLayoutEffect, scheduleEffectAssert); + + it('calls the effect immediately after render', () => { + const cleanupFunction = sinon.spy(); + const callback = sinon.spy(() => cleanupFunction); + + function Comp() { + useLayoutEffect(callback); + return null; + } + + render(, scratch); + render(, scratch); + + expect(cleanupFunction).to.be.calledOnce; + expect(callback).to.be.calledTwice; + + render(, scratch); + + expect(cleanupFunction).to.be.calledTwice; + expect(callback).to.be.calledThrice; + }); + + it('works on a nested component', () => { + const callback = sinon.spy(); + + function Parent() { + return ( +
    + +
    + ); + } + + function Child() { + useLayoutEffect(callback); + return null; + } + + render(, scratch); + + expect(callback).to.be.calledOnce; + }); + + it('should execute multiple layout effects in same component in the right order', () => { + let executionOrder = []; + const App = ({ i }) => { + executionOrder = []; + useLayoutEffect(() => { + executionOrder.push('action1'); + return () => executionOrder.push('cleanup1'); + }, [i]); + useLayoutEffect(() => { + executionOrder.push('action2'); + return () => executionOrder.push('cleanup2'); + }, [i]); + return

    Test

    ; + }; + render(, scratch); + render(, scratch); + expect(executionOrder).to.deep.equal([ + 'cleanup1', + 'cleanup2', + 'action1', + 'action2' + ]); + }); + + it('should correctly display DOM', () => { + function AutoResizeTextareaLayoutEffect(props) { + const ref = useRef(null); + useLayoutEffect(() => { + // IE & Edge put textarea's value as child of textarea when reading innerHTML so use + // cross browser serialize helper + const actualHtml = serializeHtml(scratch); + const expectedHTML = `

    ${props.value}

    `; + expect(actualHtml).to.equal(expectedHTML); + expect(document.body.contains(ref.current)).to.equal(true); + }); + return ( + +

    {props.value}

    +

    Stats