Full Code of pastelsky/bundlephobia for AI

bundlephobia cdd5899bb265 cached
199 files
406.0 KB
116.4k tokens
349 symbols
1 requests
Download .txt
Showing preview only (452K chars total). Download the full file or copy to clipboard to get everything.
Repository: pastelsky/bundlephobia
Branch: bundlephobia
Commit: cdd5899bb265
Files: 199
Total size: 406.0 KB

Directory structure:
gitextract_j7l_cmv1/

├── .eslintrc.json
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── 1-package-build-failure---inaccurate-sizes.md
│   │   ├── 2-similar-package-suggestion.md
│   │   ├── 3-feature-request---improvement.md
│   │   └── 4-bug_report.md
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .travis.yml
├── .yarnrc.yml
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── __tests__/
│   ├── errors-cache.test.js
│   ├── stress-test.sh
│   └── utils.test.js
├── bin/
│   ├── generate-sitemap.js
│   ├── getResults.js
│   └── updateHistoricalData.js
├── build-service/
│   ├── .yarnrc.yml
│   ├── index.js
│   └── package.json
├── cache-service/
│   ├── .gitignore
│   ├── cache.utils.js
│   ├── index.js
│   ├── middlewares/
│   │   ├── exports-size.middleware.js
│   │   └── package-size.middleware.js
│   └── package.json
├── client/
│   ├── analytics.ts
│   ├── api.ts
│   ├── assets/
│   │   └── public/
│   │       ├── browserconfig.xml
│   │       ├── manifest.json
│   │       ├── open-search-description.xml
│   │       ├── robots.txt
│   │       └── sitemap.xml
│   ├── components/
│   │   ├── AnnouncementBanner/
│   │   │   ├── AnnouncementBanner.scss
│   │   │   ├── AnnouncementBanner.tsx
│   │   │   └── index.ts
│   │   ├── AutocompleteInput/
│   │   │   ├── AutocompleteInput.scss
│   │   │   ├── AutocompleteInput.tsx
│   │   │   ├── components/
│   │   │   │   └── SuggestionItem.tsx
│   │   │   ├── hooks/
│   │   │   │   ├── useAutocompleteInput.ts
│   │   │   │   └── useFontSize.ts
│   │   │   └── index.ts
│   │   ├── AutocompleteInputBox/
│   │   │   ├── AutocompleteInputBox.scss
│   │   │   ├── AutocompleteInputBox.tsx
│   │   │   └── index.ts
│   │   ├── BarGraph/
│   │   │   ├── BarGraph.scss
│   │   │   ├── BarGraph.tsx
│   │   │   └── index.ts
│   │   ├── BarVersion/
│   │   │   ├── BarVersion.scss
│   │   │   └── BarVersion.tsx
│   │   ├── BlogLayout/
│   │   │   ├── BlogLayout.scss
│   │   │   ├── BlogLayout.tsx
│   │   │   └── index.ts
│   │   ├── BuildProgressIndicator/
│   │   │   ├── BuildProgressIndicator.scss
│   │   │   ├── BuildProgressIndicator.tsx
│   │   │   └── index.ts
│   │   ├── Header/
│   │   │   ├── Header.tsx
│   │   │   └── index.ts
│   │   ├── Icons/
│   │   │   ├── SearchIcon.tsx
│   │   │   ├── SideEffectIcon.scss
│   │   │   ├── SideEffectIcon.tsx
│   │   │   ├── TreeShakeIcon.scss
│   │   │   └── TreeShakeIcon.tsx
│   │   ├── JumpingDots/
│   │   │   ├── JumpingDots.scss
│   │   │   ├── JumpingDots.tsx
│   │   │   └── index.ts
│   │   ├── Layout/
│   │   │   ├── Layout.scss
│   │   │   ├── Layout.tsx
│   │   │   └── index.ts
│   │   ├── MetaTags.tsx
│   │   ├── PageNav/
│   │   │   ├── PageNav.tsx
│   │   │   └── index.ts
│   │   ├── ProgressHex/
│   │   │   ├── ProgressHex.scss
│   │   │   ├── ProgressHex.tsx
│   │   │   ├── index.ts
│   │   │   └── progress-hex-timeline.ts
│   │   ├── QuickStatsBar/
│   │   │   ├── QuickStatsBar.scss
│   │   │   ├── QuickStatsBar.tsx
│   │   │   └── index.ts
│   │   ├── ResultLayout/
│   │   │   ├── ResultLayout.scss
│   │   │   ├── ResultLayout.tsx
│   │   │   └── index.ts
│   │   ├── Separator.tsx
│   │   ├── SimilarPackageCard/
│   │   │   ├── SimilarPackageCard.scss
│   │   │   ├── SimilarPackageCard.tsx
│   │   │   └── index.ts
│   │   ├── Stat/
│   │   │   ├── Stat.scss
│   │   │   ├── Stat.tsx
│   │   │   └── index.ts
│   │   ├── Treemap/
│   │   │   ├── Treemap.tsx
│   │   │   ├── TreemapSquare.tsx
│   │   │   ├── index.ts
│   │   │   └── squarify.js
│   │   └── Warning/
│   │       ├── Warning.scss
│   │       ├── Warning.tsx
│   │       └── index.ts
│   └── config/
│       ├── colors.ts
│       └── scanBlacklist.ts
├── index.js
├── index.ts
├── next.config.js
├── nodemon.json
├── package.json
├── pages/
│   ├── _app.page.tsx
│   ├── _document.page.tsx
│   ├── blog/
│   │   ├── components/
│   │   │   ├── Article.tsx
│   │   │   ├── ContentfulProvider.tsx
│   │   │   └── Post.tsx
│   │   ├── digital-ocean-partnership.page.tsx
│   │   └── index.page.tsx
│   ├── compare/
│   │   ├── ComparePage.js
│   │   ├── ComparePage.scss
│   │   └── index.js
│   ├── index.page.tsx
│   ├── index.scss
│   ├── package/
│   │   └── [...packageString]/
│   │       ├── ResultPage.js
│   │       ├── ResultPage.scss
│   │       ├── components/
│   │       │   ├── ExportAnalysisSection/
│   │       │   │   ├── ExportAnalysisSection.js
│   │       │   │   ├── ExportAnalysisSection.scss
│   │       │   │   └── index.js
│   │       │   ├── InterLinksSection/
│   │       │   │   ├── InterLinksSection.js
│   │       │   │   ├── InterLinksSection.scss
│   │       │   │   ├── InterLinksSectionCard/
│   │       │   │   │   ├── InterLinksSectionCard.js
│   │       │   │   │   ├── InterLinksSectionCard.scss
│   │       │   │   │   └── index.js
│   │       │   │   └── index.js
│   │       │   ├── SimilarPackagesSection/
│   │       │   │   ├── SimilarPackagesSection.js
│   │       │   │   ├── SimilarPackagesSection.scss
│   │       │   │   └── index.js
│   │       │   └── TreemapSection.js
│   │       └── index.page.js
│   ├── scan/
│   │   ├── Scan.js
│   │   ├── Scan.scss
│   │   └── index.page.js
│   └── scan-results/
│       ├── ScanResults.js
│       ├── ScanResults.scss
│       └── index.page.js
├── process.yml
├── scripts/
│   ├── README.md
│   ├── cleanup-old-keys.ts
│   ├── generate-top-packages.js
│   ├── package.json
│   └── populate-v3.js
├── server/
│   ├── CustomError.js
│   ├── Logger.js
│   ├── Queue.js
│   ├── api/
│   │   └── BuildService.js
│   ├── config.js
│   ├── data/
│   │   └── similar-packages/
│   │       ├── date-time.js
│   │       ├── index.js
│   │       ├── markdown.js
│   │       └── storage.js
│   ├── init.js
│   ├── middlewares/
│   │   ├── exports.middleware.js
│   │   ├── exportsSizes.middleware.js
│   │   ├── generateImg.middleware.js
│   │   ├── jsonCache.middleware.js
│   │   ├── rateLimit.middleware.js
│   │   ├── requestLogger.middleware.js
│   │   ├── results/
│   │   │   ├── blockBlacklist.middleware.js
│   │   │   ├── build.middleware.js
│   │   │   ├── cachedResponse.middleware.js
│   │   │   ├── error.middleware.js
│   │   │   ├── index.js
│   │   │   └── resolvePackage.middleware.js
│   │   └── similar-packages/
│   │       ├── fixtures.js
│   │       └── similarPackages.middleware.js
│   └── worker.js
├── stylesheets/
│   ├── base.scss
│   ├── colors.scss
│   ├── index.scss
│   ├── mixins.scss
│   └── variables.scss
├── test-packages/
│   ├── blacklist-error/
│   │   ├── index.js
│   │   └── package.json
│   ├── build-error/
│   │   ├── index.js
│   │   └── package.json
│   ├── entry-point-error/
│   │   ├── package.json
│   │   └── random-js-file.js
│   └── missing-dependency-error/
│       ├── index.js
│       └── package.json
├── tsconfig.json
├── tsconfig.server.json
├── types/
│   ├── amplitude.d.ts
│   ├── index.ts
│   └── react-contentful.d.ts
└── utils/
    ├── cache.utils.js
    ├── common.utils.js
    ├── draw.utils.js
    ├── firebase.utils.js
    ├── index.js
    ├── rebuild.utils.js
    └── server.utils.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc.json
================================================
{
  "extends": ["next", "prettier"],
  "plugins": [],
  "root": true,
  "rules": {}
}


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: pastelsky
open_collective: bundlephobia
ko_fi: # Replace with a single Ko-fi username


================================================
FILE: .github/ISSUE_TEMPLATE/1-package-build-failure---inaccurate-sizes.md
================================================
---
name: Package Build failure / inaccurate sizes
about: Unexpected failures that are not explained by the FAQ's page - https://github.com/pastelsky/bundlephobia#faq
title: '<error-name>: <package-name> fails to <condition>'
labels: ''
assignees: pastelsky
---

## Package name

### Entire (stringified) error that I see in my browser console

```

```


================================================
FILE: .github/ISSUE_TEMPLATE/2-similar-package-suggestion.md
================================================
---
name: Similar package suggestion
about: Suggest a better alternative to a popular package
title: 'Package suggestion: <alternative> for <package / category>'
labels: similar suggestion
assignees: ''
---

**Package name**

**Alternative to**

<!-- Name popular package(s) this package is an alternative to. -->

**Quality check**

<!-- All of the below factors are necessary for acceptance -->

- [ ] Package has sufficient overlap in functionality to act as a replacement.
- [ ] Package is actively maintained, and/or stable for use.
- [ ] Package has at least 1000 weekly downloads on NPM or is relatively popular on GitHub.
- [ ] This package is a better alternative to what is already suggested for this category (please explain why), or the category is new.


================================================
FILE: .github/ISSUE_TEMPLATE/3-feature-request---improvement.md
================================================
---
name: Feature request / Improvement
about: Suggest an idea for this project
title: ''
labels: Improvement
assignees: ''
---

**Please describe the feature/suggestion**

**Describe the solution you'd like**

**Describe any alternatives you've considered**


================================================
FILE: .github/ISSUE_TEMPLATE/4-bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---

**Describe the bug**

**To Reproduce**

**Expected behavior**


================================================
FILE: .github/workflows/ci.yml
================================================
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: CI

on:
  push:
    branches: [bundlephobia]
  pull_request:
    branches: [bundlephobia]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [24.x]

    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - name: Enable Corepack
        run: corepack enable
      - name: Install project dependencies
        run: yarn install
      - name: Run lint
        run: yarn lint
      - run: yarn build
        env:
          CI: true


================================================
FILE: .gitignore
================================================
.next
.env
build
out
now.json

# package directories
node_modules

# Serverless directories
.serverless

# Logs
yarn-error.log
logs
**/.yarn/cache
**/.yarn/install-state.gz

# JetBrains IDE
.idea

# TypeScript
tsconfig.tsbuildinfo
next-env.d.ts

# keys
scripts/keys

# Maintenance scripts and logs
maintenance_logs/
top-packages.json
populate-v3-*.json


================================================
FILE: .npmrc
================================================
registry=https://registry.npmjs.org


================================================
FILE: .nvmrc
================================================
v24.13.0


================================================
FILE: .prettierignore
================================================
.next
bin


================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
  - '24'


================================================
FILE: .yarnrc.yml
================================================
enableInlineBuilds: true

nodeLinker: node-modules

npmRegistryServer: 'https://registry.npmjs.org'


================================================
FILE: CONTRIBUTING.md
================================================
Thanks for looking to help 👋. Have a nice time contributing to bundlephobia.
If you've any queries regarding setup or contributing, feel free to open an issue.
I'll try my best to answer as soon as I can.

Note: This repository only contains the frontend, and the request server.
If you're looking to make changes to the core logic – building of packages and size calculation, you need to look here instead - [package-build-stats](https://github.com/pastelsky/package-build-stats)

## Running locally

### Adding the necessary keys (Optional)

Add a `.env` file to the root with Algolia credentials. The server should still run without this, but some features might be disabled.

```ini
# App Id for NPM Registry
ALGOLIA_APP_ID=OFCNCOG2CU

# API Key
ALGOLIA_API_KEY=<api-key-obtained-from-algolia>
```

In addition, one can specify -

```ini
BUILD_SERVICE_ENDPOINT=<endpoint-to-service>
```

In the absence of such an endpoint, packages will be built locally using the [`getPackageStats` function](https://github.com/pastelsky/package-build-stats)
and

```ini
CACHE_SERVICE_ENDPOINT=<endpoint-to-service>

FIREBASE_API_KEY=<apiKey>
FIREBASE_AUTH_DOMAIN=<domain>
FIREBASE_DATABASE_URL=<url>
```

for caching to work (optional).

### Canvas compile issues

Bundlephobia relies on [`canvas`](https://www.npmjs.com/package/canvas) which may need to be built from source (depending on your platform). If so, [install the required packages listed in their docs](https://github.com/Automattic/node-canvas#compiling).

### Commands

| script           | description                        |
| ---------------- | :--------------------------------- |
| `yarn run dev`   | Start a development server locally |
| `yarn run build` | Build for production               |
| `yarn run prod`  | Start a production server locally  |


================================================
FILE: ISSUE_TEMPLATE.md
================================================
## Type

<!-- Bug / Feature Request -->

## Package name

### Entire error (stringified) I see in my browser console


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Shubham Kanodia

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<p align="center">
    <img src="https://cdn.rawgit.com/pastelsky/bundlephobia/bundlephobia/client/assets/site-logo.svg" alt="" width="290" height="235" />
</p>
<p align="center">
  <img src="https://img.shields.io/npm/v/package-build-stats.svg" />
  <img src="https://img.shields.io/npm/l/package-build-stats.svg" />
  <a href="https://discord.gg/trbWvVet44">
    <img src="https://badgen.net/badge/icon/discord?icon=discord&label"/>
  </a>
</p>
<p align="center">
  <a href="https://bundlephobia.com"> bundlephobia.com </a> <br />
</p>
<p align="center">
  Know the performance impact of including an npm package in your app's bundle.
</p>

<p align="center">
    <b><a href="https://github.com/pastelsky/bundlephobia/issues/683"> Bundlephobia's looking for contributors and co-maintainers </a> </b>
</p>

## Features

- Works with ES6 packages
- Can build css and scss packages as well (beta)
- Reports historical trends
- See package composition

## Badges

- [badgen.net](https://badgen.net/#bundlephobia) - example size of react: ![react](https://badgen.net/bundlephobia/minzip/react)
- [shields.io](https://shields.io/#/examples/size) - example size of react: ![react](https://img.shields.io/bundlephobia/minzip/react.svg)

## Built using bundlephobia

- Size in browser - As seen on package searches at [yarnpkg.com](https://yarnpkg.com)
- [bundlephobia-cli](https://github.com/AdrieanKhisbe/bundle-phobia-cli) - A Command Line client for bundlephobia
- [importcost](https://atom.io/packages/importcost) - An Atom plugin to display size of imported packages
- [JS Bundle Size Cross-Browser Extension](https://github.com/vicrazumov/js-bundle-size) - Chrome and Firefox extension automatically adding package size to the github and npm pages.
- [npmcharts.com](https://npmcharts.com/compare/bundle-phobia-cli) - bundle size stats at top of page
- [Rollpkg](https://github.com/rafgraph/rollpkg) - A build tool to create packages with Rollup and TypeScript

## Support

Liked bundlephobia? Used it's API to build something cool? Let us know!

We could use some 💛 and sponsorship on –

<a href="https://github.com/sponsors/pastelsky">
  <img src="https://opencollective.com/bundlephobia/tiers/backer.svg"/>
</a>

## FAQ

#### 1. Why does search for package X throw `MissingDependencyError` ?

This error is thrown if a package `require`s a dependency without adding it in its dependencies or peerDependencies list. In the absence of such a definition, we cannot reliably report the size of the package - since we cannot resolve any information about the package.

In such a case, it's best to report an issue with the package author asking the missing package to be added to its `package.json`

#### 2. I see a `BuildError` for package X, but I'm not sure why.

You can see a detailed stack trace in your devtools console, and [open an issue](https://github.com/pastelsky/bundlephobia/issues/new) with the relevant details. Working on a more ideal solution for this.

## Contributing

See [Contributing](https://github.com/pastelsky/bundlephobia/blob/bundlephobia/CONTRIBUTING.md)

## Sponsors

<a href="https://www.digitalocean.com?utm_medium=opensource&utm_source=bundlephobia"><img width="100px" src="https://upload.wikimedia.org/wikipedia/commons/f/ff/DigitalOcean_logo.svg"/></a>


================================================
FILE: __tests__/errors-cache.test.js
================================================
const fetch = require('node-fetch')

const baseURL = 'http://127.0.0.1:5000/api/size?package='

describe('build api', () => {
  beforeEach(function () {
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000
  })

  it('builds correct packages', async done => {
    const resultURL = baseURL + 'react@16.5.0'
    const result = await fetch(resultURL)
    const errorJSON = await result.json()

    expect(result.status).toBe(200)
    expect(result.headers.get('cache-control')).toBe('max-age=86400')

    expect(errorJSON).toEqual({
      scoped: false,
      name: 'react',
      version: '16.5.0',
      description:
        'React is a JavaScript library for building user interfaces.',
      repository: 'https://github.com/facebook/react',
      dependencyCount: 4,
      hasJSNext: false,
      hasJSModule: false,
      hasSideEffects: true,
      size: 5951,
      gzip: 2528,
      dependencySizes: [{ name: 'react', approximateSize: 5957 }],
    })

    done()
  })

  it('handles hash bang in the beginning of packages', async done => {
    const resultURL = baseURL + '@bundlephobia/test-build-error'
    const result = await fetch(resultURL)
    const resultJSON = await result.json()

    expect(result.status).toBe(200)
    expect(result.headers.get('cache-control')).toBe('max-age=86400')

    expect(resultJSON.size).toBe(183)
    expect(resultJSON.gzip).toBe(153)

    done()
  })

  it('gives right error messages on when trying to build blocklisted packages', async done => {
    const resultURL = baseURL + 'polymer-cli'
    const result = await fetch(resultURL)
    const errorJSON = await result.json()

    expect(result.status).toBe(403)
    expect(result.headers.get('cache-control')).toBe('max-age=60')

    expect(errorJSON.error.code).toBe('BlocklistedPackageError')
    expect(errorJSON.error.message).toBe(
      'The package you were looking for is blocklisted due to suspicious activity in the past'
    )

    done()
  })

  it('gives right error messages on when trying to build entry point error ', async done => {
    const resultURL = baseURL + '@bundlephobia/test-entry-point-error'
    const result = await fetch(resultURL)
    const errorJSON = await result.json()

    expect(result.status).toBe(500)
    expect(result.headers.get('cache-control')).toBe('max-age=3600')

    expect(errorJSON.error.code).toBe('EntryPointError')
    expect(errorJSON.error.message).toBe(
      "We could not guess a valid entry point for this package. Perhaps the author hasn't specified one in its package.json ?"
    )

    done()
  })

  it('ignores errors when trying to build packages with missing dependency errors', async done => {
    const resultURL = baseURL + '@bundlephobia/missing-dependency-error'
    const result = await fetch(resultURL)
    const resultJSON = await result.json()

    expect(result.status).toBe(200)
    expect(result.headers.get('cache-control')).toBe('max-age=86400')

    expect(resultJSON.size).toBe(243)
    expect(resultJSON.gzip).toBe(178)
    expect(resultJSON.ignoredMissingDependencies).toStrictEqual([
      'missing-package',
    ])
    done()
  })

  it("gives right error messages on when trying to build packages that don't exist", async done => {
    const resultURL = baseURL + '@bundlephobia/does-not-exist'
    const result = await fetch(resultURL)
    const errorJSON = await result.json()

    expect(result.status).toBe(404)
    expect(result.headers.get('cache-control')).toBe('max-age=60')

    expect(errorJSON.error.code).toBe('PackageNotFoundError')
    expect(errorJSON.error.message).toBe(
      "The package you were looking for doesn't exist."
    )

    done()
  })

  it("gives right error messages on when trying to build packages versions that don't exist", async done => {
    const resultURL = baseURL + '@bundlephobia/test-entry-point-error@459.0.0'
    const result = await fetch(resultURL)
    const errorJSON = await result.json()

    expect(result.status).toBe(404)
    expect(result.headers.get('cache-control')).toBe('max-age=60')
    expect(errorJSON.error.code).toBe('PackageVersionMismatchError')

    done()
  })
})


================================================
FILE: __tests__/stress-test.sh
================================================
(curl localhost:5000/api/size?package=react && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=preact && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=d3 && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=c3 && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=inferno && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=react-router && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=localforage && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=request && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=lodash && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=moment && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=antd && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=vue && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=react-clipboard && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=react-ink && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=glamorous && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=preact-compat && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=inferno-compat && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=immutable && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=redux && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=mobx && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=mobx-react && echo '\n') &
sleep 3
(curl localhost:5000/api/size?package=styled-components && echo '\n') &
wait
#sleep 5
#(curl localhost:5000/api/size?package=backbone && echo '\n') &
#sleep 5
#(curl localhost:5000/api/size?package=jquery && echo '\n') &
#sleep 5
#(curl localhost:5000/api/size?package=formik && echo '\n') &
#sleep 5
#(curl localhost:5000/api/size?package=animejs && echo '\n') &
#sleep 5
#(curl localhost:5000/api/size?package=three && echo '\n') &
#sleep 5
#(curl localhost:5000/api/size?package=babylonjs && echo '\n') &
#sleep 5
#(curl localhost:5000/api/size?package=emotion && echo '\n') &
#sleep 5
#(curl localhost:5000/api/size?package=react-treeview && echo '\n') &
#sleep 5
#(curl localhost:5000/api/size?package=react-bootstrap && echo '\n') &
#sleep 5
#(curl localhost:5000/api/size?package=vuex && echo '\n') &
#sleep 5
#(curl localhost:5000/api/size?package=mojs && echo '\n') &
#sleep 5
#(curl localhost:5000/api/size?normalize.css && echo '\n') &


================================================
FILE: __tests__/utils.test.js
================================================
import { parsePackageString } from '../utils/common.utils'

describe('parsePackageString', () => {
  it('handles scoped packages correctly', () => {
    expect(parsePackageString('@babel/core@9.8.0')).toEqual({
      scoped: true,
      name: '@babel/core',
      version: '9.8.0',
    })
  })

  it('handles scoped packages without versions correctly', () => {
    expect(parsePackageString('@babel/core')).toEqual({
      scoped: true,
      name: '@babel/core',
      version: null,
    })
  })

  it('handles regular packages correctly', () => {
    expect(parsePackageString('react@15.6.1')).toEqual({
      scoped: false,
      name: 'react',
      version: '15.6.1',
    })
  })

  it('handles regular packages without version correctly', () => {
    expect(parsePackageString('react')).toEqual({
      scoped: false,
      name: 'react',
      version: null,
    })
  })

  it('handles special characters in name properly', () => {
    expect(parsePackageString('chart.js@5.6.0')).toEqual({
      scoped: false,
      name: 'chart.js',
      version: '5.6.0',
    })
  })

  it('handles special characters in version properly', () => {
    expect(parsePackageString('chart.js@0.7.0-beta')).toEqual({
      scoped: false,
      name: 'chart.js',
      version: '0.7.0-beta',
    })
  })
})


================================================
FILE: bin/generate-sitemap.js
================================================
const { SitemapStream, streamToPromise } = require( 'sitemap' )
const { Readable } = require( 'stream' )
const { writeFileSync } = require('fs')
const path = require('path')

// Source: https://analytics.amplitude.com/bundlephobia/chart/3tbq2vm/edit/jmy3u6h
const popularPackages = [
  "react",
  "moment",
  "lodash",
  "react-dom",
  "axios",
  "@material-ui/core",
  "date-fns",
  "dayjs",
  "vue",
  "redux",
  "react-query",
  "swiper",
  "react-hook-form",
  "styled-components",
  "formik",
  "framer-motion",
  "yup",
  "angular",
  "antd",
  "preact",
  "react-spring",
  "rxjs",
  "jquery",
  "chart.js",
  "firebase",
  "glider-js",
  "chroma-js",
  "react-select",
  "@google/model-viewer",
  "bootstrap",
  "react-redux",
  "@apollo/client",
  "three",
  "luxon",
  "uuid",
  "tailwindcss",
  "swr",
  "mobx",
  "react-slick",
  "d3",
  "react-router-dom",
  "@angular/core",
  "recoil",
  "immer",
  "express",
  "classnames",
  "react-datepicker",
  "recharts",
  "svelte",
  "@chakra-ui/react",
  "react-final-form",
  "xstate",
  "zustand",
  "slick-carousel",
  "next",
  "@reduxjs/toolkit",
  "react-transition-group",
  "ramda",
  "gsap",
  "lodash-es",
  "query-string",
  "emotion",
  "react-beautiful-dnd",
  "react-router",
  "react-dnd",
  "react-bootstrap",
  "react-window",
  "@react-google-maps/api",
  "qs",
  "react-motion",
  "joi",
  "slate",
  "moment-timezone",
  "chartist",
  "react-i18next",
  "react-table",
  "react-virtualized",
  "lottie-web",
  "node-fetch",
  "@emotion/styled",
  "react-toastify",
  "xlsx",
  "i18next",
  "flickity",
  "@emotion/react",
  "@material-ui/styles",
  "animejs",
  "react-intl",
  "@hookstate/core",
  "dompurify",
  "@popperjs/core",
  "graphql",
  "highcharts",
  "js-cookie",
  "vuetify",
  "clsx",
  "@sentry/browser",
  "draft-js",
  "zod",
  "material-ui",
  "superstruct",
  "downshift",
  "redux-saga",
  "@headlessui/react",
  "redux-thunk",
  "nanoid",
  "libphonenumber-js",
  "redux-toolkit",
  "react-markdown",
  "react-day-picker",
  "react-use",
  "urql",
  "quill",
  "@material-ui/icons",
  "react-modal",
  "marked",
  "react-icons",
  "momentjs",
  "ajv",
  "jspdf",
  "react-dropzone",
  "react-popper",
  "jotai",
  "react-tooltip",
  "sanitize-html",
  "crypto-js",
  "react-move",
  "react-helmet",
  "apexcharts",
  "react-chartjs-2",
  "react-multi-carousel",
  "fuse.js",
  "alpinejs",
  "aws-sdk",
  "react-intersection-observer",
  "react-responsive-modal",
  "core-js",
  "tippy.js",
  "react-responsive-carousel",
  "react-dates",
  "popper.js",
  "graphql-request",
  "frappe-charts",
  "exceljs",
  "chartjs",
  "react-form",
  "vuex",
  "uplot",
  "date-fns-tz",
  "phin",
  "react-player",
  "keen-slider",
  "underscore",
  "final-form",
  "@angular/material",
  "react-number-format",
  "immutable",
  "xss",
  "chakra-ui",
  "lodash.debounce",
  "typescript",
  "echarts",
  "bulma",
  "jsonschema",
  "@xstate/react",
  "react-pdf",
  "got",
  "victory",
  "lazysizes",
  "validator",
  "lit-html",
  "effector",
  "numeral",
  "lit-element",
  "mobx-react",
  "polished"
]

const otherPages = ['', '/scan']

const links = [
  ...otherPages.map(page => ({
    url: page,
    changefreq: 'weekly',
    priority: 1
  })),
  ...popularPackages.map(package => ({
    url: `/package/${package}`,
    changefreq: 'weekly',
    priority: 0.7
  })),
]

// Create a stream to write to
const stream = new SitemapStream( { hostname: 'https://bundlephobia.com' } )

// Return a promise that resolves with your XML string
const sitemapPromise = streamToPromise(Readable.from(links).pipe(stream)).then((data) =>
  data.toString()
)

sitemapPromise
  .then((sitemap) => {
  writeFileSync(path.join(__dirname, '..', 'client', 'assets', 'public', 'sitemap.xml'), sitemap, 'utf8')
})
  .catch((err) => {
    console.error(err)
    process.exit(1)
  })


================================================
FILE: bin/getResults.js
================================================
const firebase = require('firebase')
const { encodeFirebaseKey, decodeFirebaseKey } = require('../utils/index')
const fs = require('fs')
require('dotenv').config()

const firebaseConfig = {
  apiKey: process.env.FIREBASE_API_KEY,
  authDomain: process.env.FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.FIREBASE_DATABASE_URL,
}

firebase.initializeApp(firebaseConfig)

function getFirebaseStoreFromDisk() {
  try {
    return require('./data/firebase-modules.json')
  } catch (err) {
    console.log('not found on disk')
    return null
  }
}

async function getFirebaseStoreFromNetwork() {
  const modulesRef = firebase.database().ref('modules-v2')
  const lastEntry = Object.keys(
    await modulesRef
      .limitToLast(1)
      .once('value')
      .then(snapshot => snapshot.val())
  )[0]

  const firstEntry = Object.keys(
    await modulesRef
      .limitToFirst(1)
      .once('value')
      .then(snapshot => snapshot.val())
  )[0]

  let currentLastEntry = firstEntry
  let allData = {}
  let counter = 0

  console.log('fetching from ', firstEntry, ' to ', lastEntry)

  while (currentLastEntry !== lastEntry) {
    counter += 20000
    const snapshot = await firebase
      .database()
      .ref('modules-v2')
      .orderByKey()
      .startAt(currentLastEntry)
      .limitToFirst(20000)
      .once('value')
      .then(snapshot => snapshot.val())

    const packageNames = Object.keys(snapshot)
    currentLastEntry = packageNames[packageNames.length - 1]
    console.log(
      'Fetched records till ',
      counter,
      currentLastEntry,
      'total of ',
      Object.keys(snapshot),
      ' packages.'
    )
    allData = { ...allData, ...snapshot }
  }

  fs.mkdirSync(__dirname + '/data', { recursive: true })

  fs.writeFileSync(
    __dirname + '/data/firebase-modules.json',
    JSON.stringify(allData, null, 2),
    'utf8'
  )
  return allData
}

async function getResults() {
  let firebaseStore = getFirebaseStoreFromDisk()
  console.log('loaded firebase store')
  return Object.keys(firebaseStore).flatMap(packageName =>
    Object.keys(firebaseStore[packageName]).map(
      version => firebaseStore[packageName][version]
    )
  )
}

async function getPackages() {
  let firebaseStore =
    getFirebaseStoreFromDisk() || (await getFirebaseStoreFromNetwork())
  const packages = Object.keys(firebaseStore).map(
    packageName => firebaseStore[packageName]
  )
  console.log('fetched ', Object.keys(firebaseStore), ' packages ')
  return packages
}

module.exports = { getResults, getPackages }


================================================
FILE: bin/updateHistoricalData.js
================================================
#!/usr/bin/env node

const firebase = require('firebase')
const FirebaseUtils = require('../utils/firebase.utils')
const trending = require('trending-github')
const fetch = require('node-fetch')
const debug = require('debug')('bp:trending-fetch')
const GithubAPI = require('github')
const isEmptyObject = require('is-empty-object')
const promiseSeries = require('promise.series')

require('dotenv').config()

const github = new GithubAPI({
  debug: false
})

github.authenticate({
  type: 'oauth',
  key: process.env.GITHUB_CLIENT_ID,
  secret: process.env.GITHUB_CLIENT_SECRET
})

const firebaseConfig = {
  apiKey: process.env.FIREBASE_API_KEY,
  authDomain: process.env.FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.FIREBASE_DATABASE_URL
}

firebase.initializeApp(firebaseConfig)

const firebaseUtils = new FirebaseUtils(firebase)
const port = process.env.PORT || 5000

async function getPackageFromRepo(author, name) {
  const {
    data: { content }
  } = await github.repos.getContent({
    repo: author,
    owner: name,
    path: 'package.json'
  })

  if (content) {
    const decodedContent = Buffer.from(content, 'base64').toString('utf8')
    return JSON.parse(decodedContent).name
  }
}

async function getGithubTrendingPackages() {
  const repos = await trending('daily', 'javascript')
  const packages = await Promise.all(
    repos.map(repo => getPackageFromRepo(repo.author, repo.name))
  )
  return packages.filter(pack => pack)
}

async function getTrendingSearches() {
  const limit = 20
  let trendingSearches = []
  const searches = await firebaseUtils.getDailySearches()

  if (searches) {
    trendingSearches = Object.keys(searches)
      .sort(
        (packageA, packageB) =>
          searches[packageB].count - searches[packageA].count
      )
      .slice(0, limit)
  }

  return trendingSearches
}

async function updateHistoricalData() {
  try {
    const [githubTrendingPackages, searchTrendingPackages] = await Promise.all([
      getGithubTrendingPackages(),
      getTrendingSearches()
    ])

    const popularPackages = [
      ...new Set(githubTrendingPackages.concat(searchTrendingPackages))
    ]
    console.log('popular', popularPackages)
  } catch (err) {
    console.log(err)
  }
}

async function getVersionsToBuild(name) {
  const versionsToBuild = []
  const res = await fetch(
    `http://localhost:${port}/api/package-history?package=${name}`
  )
  const versionInfo = await res.json()

  Object.keys(versionInfo).forEach(version => {
    if (isEmptyObject(versionInfo[version])) {
      versionsToBuild.push(version)
    }
  })

  return versionsToBuild
}

async function getVersionsToBuild(name) {
  const versionsToBuild = []
  const res = await fetch(
    `http://localhost:${port}/api/package-history?package=${name}`
  )
  const versionInfo = await res.json()

  Object.keys(versionInfo).forEach(version => {
    if (isEmptyObject(versionInfo[version])) {
      versionsToBuild.push(version)
    }
  })

  return versionsToBuild
}

async function buildPackage(name, version) {
  debug('building package %s %s', name, version)
  const versionsToBuild = []
  const res = await fetch(
    `http://localhost:${port}/api/size?package=${name + '@' + version}`
  )
  debug('result %s %s %O', name, version, await res.json())
}

async function buildPackageFromGithub(name, author) {
  debug('building repo %s', name)
  const packageName = await getPackageFromRepo(name, author)

  if (packageName) {
    const versions = await getVersionsToBuild(packageName)
    debug('versions to build for %s — %o', packageName, versions)
    await promiseSeries(
      versions.map(version => () => buildPackage(packageName, version))
    )
  } else {
    debug('skipped repo %s', name)
  }
}

async function mostPopuplarGithubRepos() {
  const repos = await github.search.repos({
    q: 'language:javascript+npm in:readme+size:1000..50000+mirror:false',
    sort: 'stars',
    page: 1,
    per_page: 10
  })

  debug(
    'Popular GitHub Repos %o',
    repos.data.items.map(r => r.name)
  )

  try {
    const promises = repos.data.items.map(({ name, owner }) => () =>
      buildPackageFromGithub(name, owner.login)
    )
    await promiseSeries(promises)
  } catch (err) {
    console.log(err)
  }
}

mostPopuplarGithubRepos()


================================================
FILE: build-service/.yarnrc.yml
================================================
compressionLevel: mixed

enableGlobalCache: false


================================================
FILE: build-service/index.js
================================================
import 'dotenv-defaults/config.js'
import Fastify from 'fastify'
import {
  getPackageStats,
  getAllPackageExports,
  getPackageExportSizes,
  eventQueue,
} from 'package-build-stats'
import Amplitude from '@amplitude/node'

const fastify = Fastify()

if (process.env.AMPLITUDE_API_KEY) {
  const client = Amplitude.init(process.env.AMPLITUDE_API_KEY)

  eventQueue.on('*', (event, details) => {
    client.logEvent({
      event_type: event,
      user_id: 'build-service',
      event_properties: {
        ...details,
      },
    })
  })

  setInterval(() => {
    client.flush()
  }, 5000)
}

fastify.get('/size', async (req, res) => {
  const packageString = decodeURIComponent(req.query.p)
  try {
    const result = await getPackageStats(packageString, {
      installTimeout: 60000,
    })
    return res.code(200).send(result)
  } catch (err) {
    console.log(err)
    const errorToSend = 'toJSON' in err ? err.toJSON() : err
    return res.code(500).send(errorToSend)
  }
})

fastify.get('/exports-sizes', async (req, res) => {
  const packageString = decodeURIComponent(req.query.p)

  try {
    const result = await getPackageExportSizes(packageString, {
      installTimeout: 60000,
    })
    return res.code(200).send(result)
  } catch (err) {
    console.log(err)
    const errorToSend = 'toJSON' in err ? err.toJSON() : err
    return res.code(500).send(errorToSend)
  }
})

fastify.get('/exports', async (req, res) => {
  const packageString = decodeURIComponent(req.query.p)

  try {
    const result = await getAllPackageExports(packageString, {
      installTimeout: 60000,
    })
    return res.code(200).send(result)
  } catch (err) {
    console.log(err)
    const errorToSend = 'toJSON' in err ? err.toJSON() : err
    return res.code(500).send(errorToSend)
  }
})

fastify
  .listen({ port: 7002 })
  .then(() => {
    console.log(`server listening on ${fastify.server.address().port}`)
  })
  .catch(err => {
    console.error(err)
    process.exit(1)
  })


================================================
FILE: build-service/package.json
================================================
{
  "name": "build-service",
  "version": "1.0.0",
  "type": "module",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "@amplitude/node": "^1.10.2",
    "dotenv-defaults": "^2.0.2",
    "fastify": "^4.25.2",
    "now-logs": "^0.0.7",
    "package-build-stats": "8.2.5"
  },
  "engines": {
    "node": ">= 9.x.x",
    "npm": ">= 5.5.x"
  },
  "scripts": {
    "start": "DEBUG=bp:* node index",
    "dev": "DEBUG=bp:* node index"
  }
}


================================================
FILE: cache-service/.gitignore
================================================


================================================
FILE: cache-service/cache.utils.js
================================================
function encodeFirebaseKey(key) {
  return key.replace(/[.]/g, ',').replace(/\//g, '__')
}

module.exports = { encodeFirebaseKey }


================================================
FILE: cache-service/index.js
================================================
require('dotenv-defaults').config()
const firebase = require('firebase')
const fastify = require('fastify')()
const {
  getPackageSizeMiddlware,
  postPackageSizeMiddlware,
} = require('./middlewares/package-size.middleware')
const {
  getExportsSizeMiddlware,
  postExportsSizeMiddleware,
} = require('./middlewares/exports-size.middleware')

const firebaseConfig = {
  apiKey: process.env.FIREBASE_API_KEY,
  authDomain: process.env.FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.FIREBASE_DATABASE_URL,
}

firebase.initializeApp(firebaseConfig)

fastify.get('/package-cache', getPackageSizeMiddlware)
fastify.post('/package-cache', postPackageSizeMiddlware)

fastify.get('/exports-cache', getExportsSizeMiddlware)
fastify.post('/exports-cache', postExportsSizeMiddleware)

fastify
  .listen({ port: 7001 })
  .then(() => {
    console.log(`server listening on ${fastify.server.address().port}`)
  })
  .catch(err => {
    console.error(err)
    process.exit(1)
  })


================================================
FILE: cache-service/middlewares/exports-size.middleware.js
================================================
require('dotenv-defaults').config()
const LRU = require('lru-cache')
const firebase = require('firebase')
const debug = require('debug')('bp:cache')
const { encodeFirebaseKey } = require('../cache.utils')

const LRUCache = new LRU({ max: 1500 })

// Configurable Firebase keys for read/write operations
const FIREBASE_READ_KEY_EXPORTS =
  process.env.FIREBASE_READ_KEY_EXPORTS || 'exports-v3'
const FIREBASE_WRITE_KEY_EXPORTS =
  process.env.FIREBASE_WRITE_KEY_EXPORTS || 'exports-v3'

debug(
  'Firebase config (exports): READ from %s (with fallback: %s), WRITE to %s',
  FIREBASE_READ_KEY_EXPORTS,
  FIREBASE_READ_KEY_EXPORTS === 'exports-v3' ? 'yes, to exports' : 'no',
  FIREBASE_WRITE_KEY_EXPORTS
)

async function getPackageResultFromKey(key, { name, version }) {
  const ref = firebase
    .database()
    .ref()
    .child(key)
    .child(encodeFirebaseKey(name))
    .child(encodeFirebaseKey(version))

  const snapshot = await ref.once('value')
  return snapshot.val()
}

async function getPackageResult({ name, version, readKey }) {
  const targetReadKey = readKey || FIREBASE_READ_KEY_EXPORTS
  // Try primary read key first
  const result = await getPackageResultFromKey(targetReadKey, { name, version })

  if (result) {
    debug('cache hit: firebase (%s)', targetReadKey)
    return result
  }

  // If reading from default v3 and not found, fall back to "exports" (v2)
  if (
    targetReadKey === 'exports-v3' &&
    !readKey &&
    !process.env.DISABLE_FIREBASE_V2_FALLBACK
  ) {
    const fallbackResult = await getPackageResultFromKey('exports', {
      name,
      version,
    })
    if (fallbackResult) {
      debug('cache hit: firebase (fallback to exports)')
    }
    return fallbackResult
  }

  return null
}

async function setPackageResult({ name, version, result }) {
  const modules = firebase.database().ref().child(FIREBASE_WRITE_KEY_EXPORTS)
  return modules
    .child(encodeFirebaseKey(name))
    .child(encodeFirebaseKey(version))
    .set(result)
}

async function getExportsSizeMiddlware(req, res) {
  const name = decodeURIComponent(req.query.name)
  const version = decodeURIComponent(req.query.version)
  const readKey = req.query.readKey

  if (!name || !version) {
    return res.code(422).send()
  }
  debug('get exports %s@%s (readKey: %s)', name, version, readKey)

  // Use memory cache only if no explicit readKey is provided
  if (!readKey) {
    const lruCacheEntry = LRUCache.get(`${name}@${version}`)
    if (lruCacheEntry) {
      debug('cache hit: memory')
      return res.code(200).send(lruCacheEntry)
    }
  }

  const result = await getPackageResult({ name, version, readKey })
  if (result) {
    debug('cache hit: firebase')
    if (!readKey) {
      LRUCache.set(`${name}@${version}`, result)
    }
    return res.code(200).send(result)
  }

  return res.code(404).send()
}

async function postExportsSizeMiddleware(req, res) {
  const { name, version, result } = req.body

  if (!name || !version || !result) return res.code(422).send()

  debug('set exports %O to %O', { name, version }, result)
  LRUCache.set(`${name}@${version}`, result)
  try {
    await setPackageResult({ name, version, result })
    return res.code(201).send()
  } catch (err) {
    console.log(err)
    return res.code(500).send({ error: err })
  }
}

module.exports = { getExportsSizeMiddlware, postExportsSizeMiddleware }


================================================
FILE: cache-service/middlewares/package-size.middleware.js
================================================
require('dotenv-defaults').config()
const firebase = require('firebase')
const LRU = require('lru-cache')
const debug = require('debug')('bp:cache')
const { encodeFirebaseKey } = require('../cache.utils')
const LRUCache = new LRU({ max: 3000 })

// Configurable Firebase keys for read/write operations
// This allows safe migration from modules-v2 (old) to modules-v3 (new package-build-stats 8.x)
// When FIREBASE_READ_KEY is 'modules-v3', it will try v3 first, then fall back to v2
// When FIREBASE_READ_KEY is 'modules-v2', it will only read from v2
const FIREBASE_READ_KEY = process.env.FIREBASE_READ_KEY || 'modules-v3'
const FIREBASE_WRITE_KEY = process.env.FIREBASE_WRITE_KEY || 'modules-v3'

debug(
  'Firebase config: READ from %s (with fallback: %s), WRITE to %s',
  FIREBASE_READ_KEY,
  FIREBASE_READ_KEY === 'modules-v3' ? 'yes, to modules-v2' : 'no',
  FIREBASE_WRITE_KEY
)

async function getPackageResultFromKey(key, { name, version }) {
  const ref = firebase
    .database()
    .ref()
    .child(key)
    .child(encodeFirebaseKey(name))
    .child(encodeFirebaseKey(version))

  const snapshot = await ref.once('value')
  return snapshot.val()
}

async function getPackageResult({ name, version, readKey }) {
  const targetReadKey = readKey || FIREBASE_READ_KEY
  // Try primary read key first
  const result = await getPackageResultFromKey(targetReadKey, { name, version })

  if (result) {
    debug('cache hit: firebase (%s)', targetReadKey)
    return result
  }

  // If reading from default v3 and not found, fall back to v2
  if (
    targetReadKey === 'modules-v3' &&
    !readKey &&
    !process.env.DISABLE_FIREBASE_V2_FALLBACK
  ) {
    const fallbackResult = await getPackageResultFromKey('modules-v2', {
      name,
      version,
    })
    if (fallbackResult) {
      debug('cache hit: firebase (fallback to modules-v2)')
    }
    return fallbackResult
  }

  return null
}

async function setPackageResult({ name, version, result }) {
  const modules = firebase.database().ref().child(FIREBASE_WRITE_KEY)
  return modules
    .child(encodeFirebaseKey(name))
    .child(encodeFirebaseKey(version))
    .set(result)
}

async function getPackageSizeMiddlware(req, res) {
  const name = decodeURIComponent(req.query.name)
  const version = decodeURIComponent(req.query.version)
  const readKey = req.query.readKey

  if (!name || !version) {
    return res.code(422).send()
  }
  debug('get package %s@%s (readKey: %s)', name, version, readKey)

  // Use memory cache only if no explicit readKey is provided
  if (!readKey) {
    const lruCacheEntry = LRUCache.get(`${name}@${version}`)
    if (lruCacheEntry) {
      debug('cache hit: memory')
      return res.code(200).send(lruCacheEntry)
    }
  }

  const result = await getPackageResult({ name, version, readKey })
  if (result) {
    debug('cache hit: firebase')
    if (!readKey) {
      LRUCache.set(`${name}@${version}`, result)
    }
    return res.code(200).send(result)
  }

  return res.code(404).send()
}

async function postPackageSizeMiddlware(req, res) {
  const { name, version, result } = req.body

  if (!name || !version || !result) return res.code(422).send()

  debug('set package %O to %O', { name, version }, result)
  LRUCache.set(`${name}@${version}`, result)
  try {
    await setPackageResult({ name, version, result })
    return res.code(201).send()
  } catch (err) {
    console.log(err)
    return res.code(500).send({ error: err })
  }
}

module.exports = { getPackageSizeMiddlware, postPackageSizeMiddlware }


================================================
FILE: cache-service/package.json
================================================
{
  "name": "cache",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "debug": "^4.3.4",
    "dotenv": "^8.6.0",
    "dotenv-defaults": "^2.0.2",
    "fastify": "^4.25.2",
    "firebase": "^8.10.1",
    "lru-cache": "^5.1.1"
  },
  "scripts": {
    "start": "DEBUG=bp* node index",
    "dev": "DEBUG=bp* node index"
  }
}


================================================
FILE: client/analytics.ts
================================================
type HasPackageName = {
  packageName: string
}

type HasTimeTaken = {
  timeTaken: number
}

type HasIsDisabled = {
  isDisabled: boolean
}

type HasSuccessRatio = {
  successRatio: number
}

type HasPackageNameAndTimeTaken = HasPackageName & HasTimeTaken

export default class Analytics {
  static pageView(pageType: string) {
    amplitude.getInstance().logEvent(`Viewed ${pageType}`, {
      path: window.location.pathname,
    })
  }

  static performedSearch(packageName: string) {
    amplitude.getInstance().logEvent('Search Performed', {
      package: packageName,
    })
  }

  static searchSuccess({ packageName, timeTaken }: HasPackageNameAndTimeTaken) {
    amplitude.getInstance().logEvent('Search Successful', {
      package: packageName,
      timeTaken,
    })
  }

  static searchFailure({ packageName, timeTaken }: HasPackageNameAndTimeTaken) {
    amplitude.getInstance().logEvent('Search Failed', {
      package: packageName,
      timeTaken,
    })
  }

  static graphBarClicked({
    packageName,
    isDisabled,
  }: HasPackageName & HasIsDisabled) {
    amplitude.getInstance().logEvent('Bar Graph Clicked', {
      package: packageName,
      isDisabled,
    })
  }

  static scanPackageJsonDropped(itemCount: number) {
    amplitude.getInstance().logEvent('Scan packageJSON dropped', {
      itemCount,
    })
  }

  static performedScan() {
    amplitude.getInstance().logEvent('Scan Performed')
  }

  static scanParseError() {
    amplitude.getInstance().logEvent('Scan Parse Error')
  }

  static scanCompleted({
    timeTaken,
    successRatio,
  }: HasTimeTaken & HasSuccessRatio) {
    amplitude.getInstance().logEvent('Scan Parse Completed', {
      successRatio,
      timeTaken,
    })
  }

  static performedExportsAnalysis(packageName: string) {
    amplitude.getInstance().logEvent('Exports Analysis Performed', {
      package: packageName,
    })
  }

  static exportsAnalysisSuccess({
    packageName,
    timeTaken,
  }: HasPackageNameAndTimeTaken) {
    amplitude.getInstance().logEvent('Exports Analysis Successful', {
      package: packageName,
      timeTaken,
    })
  }

  static exportsAnalysisFailure({
    packageName,
    timeTaken,
  }: HasPackageNameAndTimeTaken) {
    amplitude.getInstance().logEvent('Exports Analysis Failed', {
      package: packageName,
      timeTaken,
    })
  }

  static exportsSizesSuccess({
    packageName,
    timeTaken,
  }: HasPackageNameAndTimeTaken) {
    amplitude.getInstance().logEvent('Exports Size Calculated', {
      package: packageName,
      timeTaken,
    })
  }

  static exportsSizesFailure({
    packageName,
    timeTaken,
  }: HasPackageNameAndTimeTaken) {
    amplitude.getInstance().logEvent('Exports Size Failed', {
      package: packageName,
      timeTaken,
    })
  }
}


================================================
FILE: client/api.ts
================================================
import fetch from 'unfetch'

type PackageSuggestion = {
  searchScore: number
  score: { detail: { popularity: number } }
}

type RecentSearch = {
  [key: string]: {
    name: string
    version: string
    lastSearched: number
    count: number
  }
}

export default class API {
  static get<T = unknown>(url: string, isInternal = true): Promise<T> {
    const headers: Record<string, string> = {
      Accept: 'application/json',
    }

    if (isInternal) {
      headers['X-Bundlephobia-User'] = 'bundlephobia website'
    }
    return fetch(url, { headers }).then(res => {
      if (!res.ok) {
        try {
          return res.json().then(err => Promise.reject(err))
        } catch (e) {
          if (res.status === 503) {
            return Promise.reject({
              error: {
                code: 'TimeoutError',
                message:
                  'This is taking unusually long. Check back in a couple of minutes?',
              },
            })
          }

          return Promise.reject({
            error: {
              code: 'BuildError',
              message:
                "Oops, something went wrong and we don't have an appropriate error for this. Open an issue maybe?",
            },
          })
        }
      }
      return res.json()
    })
  }

  static getInfo(packageString: string) {
    return API.get(`/api/size?package=${packageString}&record=true`)
  }

  static getExports(packageString: string) {
    return API.get(`/api/exports?package=${packageString}`)
  }

  static getExportsSizes(packageString: string) {
    return API.get(`/api/exports-sizes?package=${packageString}`)
  }

  static getHistory(packageString: string, limit: number) {
    return API.get(
      `/api/package-history?package=${packageString}&limit=${limit}`
    )
  }

  static getRecentSearches(limit: number) {
    return API.get<RecentSearch[]>(`/api/recent?limit=${limit}`)
  }

  static getSimilar(packageName: string) {
    return API.get(`/api/similar-packages?package=${packageName}`)
  }

  static getSuggestions(query: string) {
    const suggestionSort = (
      packageA: PackageSuggestion,
      packageB: PackageSuggestion
    ) => {
      // Rank closely matching packages followed
      // by most popular ones
      if (
        Math.abs(
          Math.log(packageB.searchScore) - Math.log(packageA.searchScore)
        ) > 1
      ) {
        return packageB.searchScore - packageA.searchScore
      } else {
        return (
          packageB.score.detail.popularity - packageA.score.detail.popularity
        )
      }
    }

    return API.get<PackageSuggestion[]>(
      `https://api.npms.io/v2/search/suggestions?q=${query}`,
      false
    ).then(result => result.sort(suggestionSort))

    //backup when npms.io is down

    //return API.get(`/-/search?text=${query}`)
    //  .then(result => result.objects
    //    .sort(suggestionSort)
    //    .map(suggestion => {
    //      const name = suggestion.package.name
    //      const hasMatch = name.includes(query)
    //      const startIndex = name.indexOf(query)
    //      const endIndex = startIndex + query.length
    //      let highlight
    //
    //      if (hasMatch) {
    //        highlight =
    //          name.substring(0, startIndex) +
    //          '<em>' + name.substring(startIndex, endIndex) + '</em>' +
    //          name.substring(endIndex)
    //      } else {
    //        highlight = name
    //      }
    //
    //      return {
    //        ...suggestion,
    //        highlight,
    //      }
    //    }),
    //  )
  }
}


================================================
FILE: client/assets/public/browserconfig.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
    <msapplication>
        <tile>
            <square150x150logo src="/mstile-150x150.png"/>
            <TileColor>#2b5797</TileColor>
        </tile>
    </msapplication>
</browserconfig>


================================================
FILE: client/assets/public/manifest.json
================================================
{
  "name": "Bundlephobia",
  "icons": [
    {
      "src": "/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/android-chrome-256x256.png",
      "sizes": "256x256",
      "type": "image/png"
    }
  ],
  "theme_color": "#ffffff",
  "background_color": "#ffffff",
  "display": "standalone",
  "orientation": "portrait"
}


================================================
FILE: client/assets/public/open-search-description.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
    <ShortName>bundlephobia</ShortName>
    <Description>Search npm packages on bundlephobia</Description>
    <InputEncoding>UTF-8</InputEncoding>
    <OutputEncoding>UTF-8</OutputEncoding>
    <Language>en-us</Language>
    <Image width="32" height="32" type="image/png">https://bundlephobia.com/favicon-32x32.png</Image>
    <Tags>bundlephobia</Tags>
    <Url type="text/html"
         template="https://bundlephobia.com/package/{searchTerms}"/>
    <moz:SearchForm>https://bundlephobia.com</moz:SearchForm>
</OpenSearchDescription>


================================================
FILE: client/assets/public/robots.txt
================================================
# *
User-agent: *
Allow: /
Disallow: /scan-results

# Host
Host: https://bundlephobia.com

# Sitemaps
Sitemap: https://bundlephobia.com/sitemap.xml


================================================
FILE: client/assets/public/sitemap.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
        xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
        xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
    <url>
        <loc>https://bundlephobia.com/</loc>
        <changefreq>weekly</changefreq>
        <priority>1.0</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/scan</loc>
        <changefreq>weekly</changefreq>
        <priority>1.0</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/moment</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/lodash</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-dom</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/axios</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@material-ui/core</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/date-fns</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/dayjs</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/vue</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/redux</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-query</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/swiper</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-hook-form</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/styled-components</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/formik</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/framer-motion</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/yup</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/angular</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/antd</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/preact</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-spring</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/rxjs</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/jquery</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/chart.js</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/firebase</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/glider-js</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/chroma-js</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-select</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@google/model-viewer</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/bootstrap</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-redux</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@apollo/client</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/three</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/luxon</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/uuid</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/tailwindcss</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/swr</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/mobx</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-slick</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/d3</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-router-dom</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@angular/core</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/recoil</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/immer</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/express</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/classnames</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-datepicker</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/recharts</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/svelte</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@chakra-ui/react</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-final-form</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/xstate</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/zustand</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/slick-carousel</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/next</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@reduxjs/toolkit</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-transition-group</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/ramda</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/gsap</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/lodash-es</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/query-string</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/emotion</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-beautiful-dnd</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-router</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-dnd</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-bootstrap</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-window</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@react-google-maps/api</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/qs</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-motion</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/joi</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/slate</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/moment-timezone</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/chartist</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-i18next</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-table</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-virtualized</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/lottie-web</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/node-fetch</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@emotion/styled</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-toastify</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/xlsx</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/i18next</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/flickity</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@emotion/react</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@material-ui/styles</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/animejs</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-intl</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@hookstate/core</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/dompurify</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@popperjs/core</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/graphql</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/highcharts</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/js-cookie</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/vuetify</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/clsx</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@sentry/browser</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/draft-js</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/zod</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/material-ui</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/superstruct</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/downshift</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/redux-saga</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@headlessui/react</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/redux-thunk</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/nanoid</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/libphonenumber-js</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/redux-toolkit</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-markdown</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-day-picker</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-use</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/urql</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/quill</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@material-ui/icons</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-modal</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/marked</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-icons</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/momentjs</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/ajv</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/jspdf</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-dropzone</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-popper</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/jotai</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-tooltip</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/sanitize-html</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/crypto-js</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-move</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-helmet</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/apexcharts</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-chartjs-2</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-multi-carousel</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/fuse.js</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/alpinejs</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/aws-sdk</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-intersection-observer</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-responsive-modal</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/core-js</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/tippy.js</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-responsive-carousel</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-dates</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/popper.js</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/graphql-request</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/frappe-charts</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/exceljs</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/chartjs</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-form</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/vuex</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/uplot</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/date-fns-tz</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/phin</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-player</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/keen-slider</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/underscore</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/final-form</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@angular/material</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-number-format</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/immutable</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/xss</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/chakra-ui</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/lodash.debounce</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/typescript</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/echarts</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/bulma</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/jsonschema</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/@xstate/react</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/react-pdf</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/got</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/victory</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/lazysizes</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/validator</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/lit-html</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/effector</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/numeral</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/lit-element</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/mobx-react</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://bundlephobia.com/package/polished</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
</urlset>


================================================
FILE: client/components/AnnouncementBanner/AnnouncementBanner.scss
================================================
@import '../../../stylesheets/colors';
@import '../../../stylesheets/variables';

.announcement-banner {
  background: #fff;
  color: $dark-gulf-blue;
  padding: 0;
  position: fixed;
  bottom: 24px;
  right: 24px;
  left: auto;
  top: auto;
  transform: none;
  width: 300px;
  margin: 0;
  border-radius: 8px;
  z-index: 1000;
  border: 1.5px solid $raven;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.announcement-banner__content {
  display: flex;
  align-items: center;
  justify-content: center;
  max-width: 1200px;
  margin: 0 auto;
  padding: 10px 48px 10px 20px;
  gap: 12px;

  @media (max-width: 600px) {
    padding: 10px 40px 10px 12px;
    gap: 8px;
  }
}

.announcement-banner__icon {
  font-size: 18px;
  flex-shrink: 0;
}

.announcement-banner__text {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  line-height: 1.5;
  text-align: left;
  font-family: $font-family-body;

  @media (max-width: 600px) {
    font-size: 13px;
    text-align: left;
  }

  strong {
    font-weight: 600;
    color: $gulf-blue;
  }

  a {
    color: $cornflower-blue;
    font-weight: 500;
    text-decoration: underline;
    text-decoration-color: rgba($cornflower-blue, 0.4);
    text-underline-offset: 2px;
    transition: all 0.2s ease;

    &:hover {
      text-decoration-color: $cornflower-blue;
      color: darken($cornflower-blue, 10%);
    }
  }
}

.announcement-banner__close {
  position: absolute;
  right: 8px;
  top: 8px;
  transform: none;
  background: transparent;
  border: none;
  color: $raven;
  font-size: 24px;
  font-weight: 300;
  width: 28px;
  height: 28px;
  border-radius: 4px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s ease;
  line-height: 1;
  padding: 0;

  &:hover {
    color: $dark-gulf-blue;
    background: rgba($raven, 0.1);
  }

  &:focus {
    outline: none;
    color: $dark-gulf-blue;
  }
}


================================================
FILE: client/components/AnnouncementBanner/AnnouncementBanner.tsx
================================================
import React, { useState, useEffect } from 'react'

const STORAGE_KEY = 'bundlephobia_rspack_banner_dismissed'
const EXPIRY_DATE = new Date('2026-07-18T00:00:00Z') // 6 months from January 18, 2026

export const AnnouncementBanner: React.FC = () => {
  const [isVisible, setIsVisible] = useState(false)

  useEffect(() => {
    // Check if banner should be shown
    const now = new Date()

    // Don't show after expiry date
    if (now >= EXPIRY_DATE) {
      return
    }

    // Check if already dismissed
    try {
      const dismissed = localStorage.getItem(STORAGE_KEY)
      if (dismissed === 'true') {
        return
      }
    } catch (e) {
      // localStorage not available
    }

    setIsVisible(true)
  }, [])

  const handleDismiss = () => {
    setIsVisible(false)
    try {
      localStorage.setItem(STORAGE_KEY, 'true')
    } catch (e) {
      // localStorage not available
    }
  }

  if (!isVisible) {
    return null
  }

  return (
    <div className="announcement-banner">
      <div className="announcement-banner__content">
        <span className="announcement-banner__icon">🚀</span>
        <p className="announcement-banner__text">
          <strong>New:</strong> Now uses{' '}
          <a
            href="https://rspack.dev"
            target="_blank"
            rel="noopener noreferrer"
          >
            Rspack
          </a>{' '}
          — much faster results, better tree-shaking, accuracy and reliability !
        </p>
        <button
          className="announcement-banner__close"
          onClick={handleDismiss}
          aria-label="Dismiss announcement"
        >
          ×
        </button>
      </div>
    </div>
  )
}

export default AnnouncementBanner


================================================
FILE: client/components/AnnouncementBanner/index.ts
================================================
export { AnnouncementBanner } from './AnnouncementBanner'
export { default } from './AnnouncementBanner'


================================================
FILE: client/components/AutocompleteInput/AutocompleteInput.scss
================================================
@use "sass:math";

@import '../../../stylesheets/colors';
@import '../../../stylesheets/variables';

.autocomplete-input__container {
  position: relative;
  width: 100%;
}

.autocomplete-input {
  width: 40vw;
  border: none;
  border-radius: 50px;
  color: rgba(0, 0, 0, 0);
}

.autocomplete-input,
.autocomplete-input__dummy-input {
  @include font-size-lg;
  padding: $global-spacing * 1.5 (25px + $global-spacing * 2) $global-spacing *
    1.5 $global-spacing * 3;
  font-family: $font-family-code;
  font-weight: $font-weight-thin;
  width: 100%;
  box-sizing: border-box;
  letter-spacing: -0.7px;
  margin: 0;

  @media screen and (max-width: 40em) {
    padding: $global-spacing (20px + $global-spacing) $global-spacing
      $global-spacing;
  }
}

.autocomplete-input__dummy-input {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  pointer-events: none;
  display: flex;
  align-items: center;
  white-space: nowrap;
  overflow: hidden;
}

.dummy-input__package-name {
  color: #1d1d1d;
  font-size: inherit;
  font-weight: inherit;
  margin: 0;
}

.dummy-input__package-version {
  color: #636363;
}

.dummy-input__at-separator {
  color: $pastel-green;
}

.autocomplete-input__suggestions-menu {
  border: 1px solid $autocomplete-border-color;
  border-top: 0;
  background: rgba(255, 255, 255, 0.96);
  font-size: 90%;
  position: absolute;
  overflow: auto;
  z-index: 10;
  max-height: 35vh;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  border-bottom-left-radius: 10px;
  border-bottom-right-radius: 10px;
  animation: unroll 0.2s cubic-bezier(0.305, 0.42, 0.205, 1.2);
  // align borders, negating nesting due to border on parent
  left: -1px;
  // hide rounded borders of the input
  margin-top: -5px;
  width: 100%;
  width: calc(100% + 2px);

  &:empty {
    border: 0;
  }
}

@keyframes unroll {
  from {
    opacity: 0;
    transform: translateY(-5px);
  }

  to {
    opacity: 1;
    transform: translateY(0px);
  }
}

.autocomplete-input__suggestion {
  padding: 10px 32px;
  color: #333;
  font-size: 15px;
  cursor: pointer;
  font-family: 'Source Code Pro', monospace;
  font-weight: $font-weight-light;
  letter-spacing: -0.5px;

  &:not(:last-of-type) {
    border-bottom: 1px solid #f5f5f5;
  }

  @media screen and (max-width: 40em) {
    padding: math.div($global-spacing, 1.5) $global-spacing * 1.5;
  }

  em {
    font-weight: $font-weight-bold;
    font-style: normal;
    color: #444;
  }
}

.autocomplete-input__suggestion--highlight {
  background: rgb(212, 243, 255);
}

.autocomplete-input__suggestion-description {
  @include font-size-xs;
  width: 100%;
  min-width: 260px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-family: $font-family-body;
  font-weight: $font-weight-thin;
  color: #666;
  padding-top: 5px;
  letter-spacing: 0;

  @media screen and (max-width: 40em) {
    font-weight: $font-weight-light;
  }
}

.autocomplete-input__form {
  display: flex;
  align-items: baseline;
  position: relative;
}

.autocomplete-input__search-icon {
  position: absolute;
  right: $global-spacing * 2.5;
  z-index: 1;
  cursor: pointer;
  top: 0;
  bottom: 0;
  margin: auto;
  width: 25px;
  height: 25px;
  border: none;
  background: none;
  padding: 0;

  @media screen and (max-width: 40em) {
    width: 16px;
    height: 16px;
    right: $global-spacing * 1.5;
  }

  svg {
    width: 100%;
    height: 100%;

    path {
      transition: all 0.2s;
      fill: #666;
    }
  }

  &:hover {
    path {
      fill: $pastel-green;
      stroke: $pastel-green;
      stroke-width: 4px;
    }
  }
}


================================================
FILE: client/components/AutocompleteInput/AutocompleteInput.tsx
================================================
import React from 'react'
import cx from 'classnames'
import AutoComplete from 'react-autocomplete'

import SearchIcon from '../Icons/SearchIcon'
import { parsePackageString } from '../../../utils/common.utils'
import { useAutocompleteInput } from './hooks/useAutocompleteInput'
import { SuggestionItem } from './components/SuggestionItem'
import { useFontSize } from './hooks/useFontSize'

type AutocompleteInputProps = {
  initialValue?: string
  renderAsH1?: boolean
  className?: string
  containerClass?: string
  autoFocus?: boolean
  onSearchSubmit: (value: string) => void
}

export const AutocompleteInput = ({
  initialValue = '',
  renderAsH1 = false,
  className,
  containerClass,
  autoFocus,
  onSearchSubmit,
}: AutocompleteInputProps) => {
  const searchInput = React.useRef<AutoComplete | null>(null)
  const {
    value,
    isMenuVisible,
    suggestions,
    handleSubmit,
    handleInputChange,
    setIsMenuVisible,
    setSuggestions,
  } = useAutocompleteInput({ initialValue, onSubmit: onSearchSubmit })
  const { searchFontSize } = useFontSize({ value })

  const { name, version } = React.useMemo(
    () => parsePackageString(value),
    [value]
  )

  return (
    <form
      className={cx(containerClass, 'autocomplete-input__form')}
      onSubmit={handleSubmit}
    >
      <div
        className={cx('autocomplete-input__container', className, {
          'autocomplete-input__container--menu-visible':
            isMenuVisible && !!suggestions.length,
        })}
      >
        <AutoComplete
          getItemValue={item => item.package.name}
          inputProps={{
            placeholder: 'find package',
            className: 'autocomplete-input',
            autoCorrect: 'off',
            autoFocus: autoFocus,
            autoCapitalize: 'off',
            spellCheck: false,
            style: { fontSize: searchFontSize! },
          }}
          onMenuVisibilityChange={isOpen => setIsMenuVisible(isOpen)}
          onChange={handleInputChange}
          ref={searchInput}
          value={value}
          items={suggestions}
          onSelect={(value, item) => {
            setSuggestions([item])
            onSearchSubmit(value)
          }}
          renderMenu={(items, value, inbuiltStyles) => {
            return (
              <div
                style={{ minWidth: inbuiltStyles.minWidth }}
                className="autocomplete-input__suggestions-menu"
              >
                {items as any}
              </div>
            )
          }}
          wrapperStyle={{
            display: 'inline-block',
            width: '100%',
            position: 'relative',
          }}
          renderItem={(item, isHighlighted) => (
            <div key={item.package.name}>
              <SuggestionItem item={item} isHighlighted={isHighlighted} />
            </div>
          )}
        />
        <div
          style={{ fontSize: searchFontSize! }}
          className="autocomplete-input__dummy-input"
        >
          <PackageNameElement
            isHeading={renderAsH1}
            className="dummy-input__package-name"
          >
            {name}
          </PackageNameElement>
          {version !== null && (
            <>
              <span className="dummy-input__at-separator">@</span>
              <span className="dummy-input__package-version">{version}</span>
            </>
          )}
        </div>
      </div>
      <button type="submit" className="autocomplete-input__search-icon">
        <SearchIcon className="" />
      </button>
    </form>
  )
}

type PackageNameElementProps = React.HTMLAttributes<HTMLElement> & {
  isHeading?: boolean
}

export function PackageNameElement({
  isHeading,
  ...props
}: PackageNameElementProps) {
  return isHeading ? <h1 {...props} /> : <span {...props} />
}


================================================
FILE: client/components/AutocompleteInput/components/SuggestionItem.tsx
================================================
import cx from 'classnames'

interface SuggestionItemProps {
  item: {
    highlight: string | null
    package: {
      name: string
      description: string
    }
  }
  isHighlighted: boolean
}

export function SuggestionItem({ item, isHighlighted }: SuggestionItemProps) {
  return (
    <div
      key={item.package.name}
      className={cx('autocomplete-input__suggestion', {
        'autocomplete-input__suggestion--highlight': isHighlighted,
      })}
    >
      {item.highlight != null ? (
        <div dangerouslySetInnerHTML={{ __html: item.highlight }} />
      ) : (
        <div>{item.package.name}</div>
      )}

      <div className="autocomplete-input__suggestion-description">
        {item.package.description}
      </div>
    </div>
  )
}


================================================
FILE: client/components/AutocompleteInput/hooks/useAutocompleteInput.ts
================================================
import React from 'react'
import debounce from 'debounce'

import { parsePackageString } from '../../../../utils/common.utils'
import API from '../../../api'

interface UseAutocompleteInputArgs {
  initialValue: string
  onSubmit: (value: string) => void
}

export function useAutocompleteInput({
  initialValue,
  onSubmit,
}: UseAutocompleteInputArgs) {
  const [value, setValue] = React.useState(initialValue)
  const [suggestions, setSuggestions] = React.useState<any[]>([])
  const [isMenuVisible, setIsMenuVisible] = React.useState(false)

  const getSuggestions = React.useMemo(
    () =>
      debounce((value: string) => {
        API.getSuggestions(value).then(result => {
          setSuggestions(result)
        })
      }, 150),
    []
  )

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    onSubmit(value)
  }

  const handleInputChange = (
    e: React.ChangeEvent<HTMLInputElement>,
    value: string
  ) => {
    setValue(e.target.value)
    const trimmedValue = e.target.value.trim()
    const { name } = parsePackageString(trimmedValue)

    if (trimmedValue.length > 1) {
      getSuggestions(name)
    }

    if (!trimmedValue) {
      setSuggestions([])
    }
  }

  return {
    value,
    suggestions,
    isMenuVisible,
    handleSubmit,
    handleInputChange,
    setIsMenuVisible,
    setSuggestions,
  }
}


================================================
FILE: client/components/AutocompleteInput/hooks/useFontSize.ts
================================================
import React from 'react'

export function useFontSize({ value }: { value: string }) {
  const searchFontSize = React.useMemo(() => {
    const baseFontSize =
      typeof window !== 'undefined' && window.innerWidth < 640 ? 22 : 35
    const maxFullSizeChars =
      typeof window !== 'undefined' && window.innerWidth < 640 ? 15 : 20
    const searchFontSize =
      value.length < maxFullSizeChars
        ? null
        : `${baseFontSize - (value.length - maxFullSizeChars) * 0.8}px`

    return searchFontSize
  }, [value])

  return { searchFontSize }
}


================================================
FILE: client/components/AutocompleteInput/index.ts
================================================
export { AutocompleteInput } from './AutocompleteInput'


================================================
FILE: client/components/AutocompleteInputBox/AutocompleteInputBox.scss
================================================
@import '../../../stylesheets/colors';
@import '../../../stylesheets/variables';

.autocomplete-input-box {
  border: 1px solid $autocomplete-border-color;
  border-radius: 10px;
  background: transparent;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
  max-width: 700px;
  min-width: 550px;

  @media screen and (max-width: 48em) {
    width: 85vw;
    max-width: 550px;
    min-width: auto;
  }

  @media screen and (max-width: 40em) {
    width: 85vw;
    min-width: auto;
  }
}

.autocomplete-input-box__footer {
  position: relative;

  &::after {
    content: '';
    position: absolute;
    width: 80%;
    margin: auto;
    height: 1px;
  }
}


================================================
FILE: client/components/AutocompleteInputBox/AutocompleteInputBox.tsx
================================================
import React, { Component } from 'react'
import cx from 'classnames'

import { WithClassName } from '../../../types'

type AutocompleteInputBoxProps = React.PropsWithChildren &
  WithClassName & {
    footer?: React.ReactNode
  }

class AutocompleteInputBox extends Component<AutocompleteInputBoxProps> {
  render() {
    const { children, footer, className } = this.props
    return (
      <div className={cx('autocomplete-input-box', className)}>
        {children}
        {footer && (
          <div className="autocomplete-input-box__footer">{footer}</div>
        )}
      </div>
    )
  }
}

export default AutocompleteInputBox


================================================
FILE: client/components/AutocompleteInputBox/index.ts
================================================
import AutocompleteInputBox from './AutocompleteInputBox'

export default AutocompleteInputBox


================================================
FILE: client/components/BarGraph/BarGraph.scss
================================================
@use "sass:math";

@import '../../../stylesheets/colors';
@import '../../../stylesheets/variables';

$bar-width: 1.6vw;
$min-bar-width: 20px;
$bar-grow-duration: 0.4s;

.bar-graph-container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 100%;
  height: 48vh;
}

.bar-graph {
  height: 40vh;
  padding-bottom: 6vh;
  display: flex;
  margin: 0;
  justify-content: center;
}

.bar-graph__bar-group {
  position: relative;
  height: 100%;
  margin: 0 3px;
  display: flex;
  width: $bar-width;
  min-width: $min-bar-width;
  justify-content: flex-end;
  flex-direction: column;
  animation: grow $bar-grow-duration cubic-bezier(0.305, 0.42, 0.205, 1.2);
  transform-origin: 100% 100%;
}

.bar-graph__bar-symbols {
  display: flex;
  flex-direction: column;
  margin-top: -500%; // don't know why this works :/
}

.bar-graph__bar-symbol {
  text-align: center;

  svg {
    height: $global-spacing * 1.8;
    width: auto;
  }

  & + & {
    margin-top: math.div($global-spacing, 3);
  }
}

.bar-graph__bar,
.bar-graph__bar2,
.bar-graph__bar[data-balloon],
.bar-graph__bar2[data-balloon] {
  width: 100%;
  left: 0;
  bottom: 0;
  transition: background 0.2s;
  cursor: pointer;
}

.bar-graph__bar,
.bar-graph__bar[data-balloon] {
  background: $maya-blue;
  border-radius: 5px 5px 0 0;

  .bar-graph__bar-group:not(.bar-graph__bar-group--disabled):hover & {
    background: darken($maya-blue, 5%);
  }

  .bar-graph__bar-group--disabled & {
    background: lighten($raven, 45%);
    border-radius: 5px;

    &:hover {
      background: lighten($raven, 35%);
    }
  }
}

.bar-graph__bar2 {
  background: $cornflower-blue;
  z-index: 1;
  pointer-events: none;
  border-radius: 0 0 5px 5px;

  .bar-graph__bar-group:hover & {
    background: darken($cornflower-blue, 5%);
  }
}

.bar-graph__bar-version,
.bar-graph__bar-symbols,
.bar-graph__legend {
  animation: fade-in 0.5s $bar-grow-duration * 0.9 both
    cubic-bezier(0.305, 0.42, 0.205, 1.2);
}

.bar-graph__legend {
  @include font-size-xs;
  padding-top: $global-spacing;
  display: flex;
  text-transform: uppercase;
  justify-content: center;
  color: lighten($raven, 20%);
}

.bar-graph__legend__colorbox {
  width: $global-spacing * 1.5;
  height: $global-spacing * 1.5;
  margin-right: $global-spacing;
  border-radius: 3px;

  .bar-graph__legend__bar1 & {
    background: $maya-blue;
  }

  .bar-graph__legend__bar2 & {
    background: $cornflower-blue;
  }
}

.bar-graph__legend__bar1,
.bar-graph__legend__bar2 {
  display: flex;
  align-items: center;
}

.bar-graph__legend__bar1 {
  margin-right: $global-spacing * 4;
}

@keyframes grow {
  from {
    transform: scaleY(0);
  }

  to {
    transform: scaleY(1);
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
}


================================================
FILE: client/components/BarGraph/BarGraph.tsx
================================================
import React, { PureComponent } from 'react'

import { formatSize } from '../../../utils'
import TreeShakeIcon from '../Icons/TreeShakeIcon'
import SideEffectIcon from '../Icons/SideEffectIcon'
import { BarVersion } from '../BarVersion/BarVersion'

export type Reading = {
  version: string
  size: number
  gzip: number
  disabled: boolean
  hasSideEffects: boolean
  hasJSModule: boolean
  hasJSNext: boolean
  isModuleType: boolean
}

type BarGraphProps = {
  readings: Reading[]
  onBarClick: (reading: Reading) => void
}

export default class BarGraph extends PureComponent<BarGraphProps> {
  getScale = () => {
    const { readings } = this.props

    const gzipValues = readings
      .filter(reading => !reading.disabled)
      .map(reading => reading.gzip)

    const sizeValues = readings
      .filter(reading => !reading.disabled)
      .map(reading => reading.size)

    const maxValue = Math.max(...[...gzipValues, ...sizeValues])
    return 100 / maxValue
  }

  getFirstSideEffectFreeIndex = () => {
    const { readings } = this.props
    const sideEffectFreeIntroducedRecently = !readings.every(
      reading => !reading.hasSideEffects
    )
    const firstSideEffectFreeIndex = readings.findIndex(
      reading => !(reading.disabled || reading.hasSideEffects)
    )

    return sideEffectFreeIntroducedRecently ? firstSideEffectFreeIndex : -1
  }

  getFirstTreeshakeableIndex = () => {
    const { readings } = this.props
    const treeshakingIntroducedRecently = !readings.every(
      reading => reading.hasJSModule
    )
    const firstTreeshakingIndex = readings.findIndex(
      reading =>
        !reading.disabled &&
        (reading.hasJSModule || reading.hasJSNext || reading.isModuleType)
    )

    return treeshakingIntroducedRecently ? firstTreeshakingIndex : -1
  }

  renderDisabledBar = (reading: Reading) => (
    <div
      key={reading.version}
      className="bar-graph__bar-group bar-graph__bar-group--disabled"
      onClick={() => this.props.onBarClick(reading)}
    >
      <div
        className="bar-graph__bar"
        style={{ height: `${50}%` }}
        data-balloon="Unknown | Click 👆 to build"
      />
      <BarVersion version={reading.version} />
    </div>
  )

  renderActiveBar = (
    reading: Reading,
    scale: number,
    options: { isFirstTreeshakeable: boolean; isFirstSideEffectFree: boolean }
  ) => {
    const getTooltipMessage = (reading: Reading) => {
      const formattedSize = formatSize(reading.size)
      const formattedGzip = formatSize(reading.gzip)
      return `Minified: ${parseFloat(formattedSize.size).toFixed(1)}${
        formattedSize.unit
      } | Gzipped: ${parseFloat(formattedGzip.size).toFixed(1)}${
        formattedGzip.unit
      }`
    }

    return (
      <div
        onClick={() => this.props.onBarClick(reading)}
        key={reading.version}
        className="bar-graph__bar-group"
      >
        <div className="bar-graph__bar-symbols">
          {options.isFirstTreeshakeable && (
            <div
              data-balloon={`ES2015 exports introduced. ${
                reading.hasSideEffects
                  ? 'Not side-effect free yet, hence limited tree-shake ability.'
                  : ''
              }`}
              className="bar-graph__bar-symbol"
            >
              <TreeShakeIcon />
            </div>
          )}
          {options.isFirstSideEffectFree && (
            <div
              data-balloon={`Was marked side-effect free. ${
                reading.hasJSNext || reading.hasJSModule || reading.isModuleType
                  ? 'Supports ES2015 exports also, hence fully tree-shakeable'
                  : "Doesn't export ESM yet, limited tree-shake ability"
              }`}
              className="bar-graph__bar-symbol"
            >
              <SideEffectIcon />
            </div>
          )}
        </div>

        <div
          className="bar-graph__bar"
          style={{ height: `${(reading.size - reading.gzip) * scale}%` }}
          data-balloon={getTooltipMessage(reading)}
        />
        <div
          className="bar-graph__bar2"
          style={{ height: `${reading.gzip * scale}%` }}
          data-balloon={getTooltipMessage(reading)}
        />
        <BarVersion version={reading.version} />
      </div>
    )
  }

  render() {
    const { readings } = this.props
    const graphScale = this.getScale()
    const firstTreeshakeableIndex = this.getFirstTreeshakeableIndex()
    const firstSideEffectFreeIndex = this.getFirstSideEffectFreeIndex()

    return (
      <div className="bar-graph-container">
        <figure className="bar-graph">
          {readings.map((reading, index) =>
            reading.disabled
              ? this.renderDisabledBar(reading)
              : this.renderActiveBar(reading, graphScale, {
                  isFirstTreeshakeable: index === firstTreeshakeableIndex,
                  isFirstSideEffectFree: index === firstSideEffectFreeIndex,
                })
          )}
        </figure>
        <div className="bar-graph__legend">
          <div className="bar-graph__legend__bar1">
            <div className="bar-graph__legend__colorbox" />
            Min
          </div>
          <div className="bar-graph__legend__bar2">
            <div className="bar-graph__legend__colorbox" />
            GZIP
          </div>
        </div>
      </div>
    )
  }
}


================================================
FILE: client/components/BarGraph/index.ts
================================================
import BarGraph from './BarGraph'

export default BarGraph


================================================
FILE: client/components/BarVersion/BarVersion.scss
================================================
@import '../../../stylesheets/colors';
@import '../../../stylesheets/variables';

.bar-graph__bar-version {
  @include font-size-xs;
  z-index: 33;
  font-weight: $font-weight-light;
  transform: rotate(-90deg) translateX(#{-$global-spacing * 1.5});
  font-variant-numeric: tabular-nums;
  color: lighten($raven, 15%);
  transition: opacity 0.2s, color 0.2s;
  font-family: $font-family-code;
  letter-spacing: -1px;
  line-height: 1;
  cursor: pointer;
  height: $bar-width;
  text-align: end;
  display: flex;
  justify-content: flex-end;
  align-items: center;

  .bar-graph-container:hover & {
    color: $raven;
  }

  .bar-graph__bar-group:hover & {
    color: darken($raven, 20%);
  }
}


================================================
FILE: client/components/BarVersion/BarVersion.tsx
================================================
import truncate from 'truncate'

interface Props {
  version: string
}

export function BarVersion({ version }: Props) {
  return (
    <div className="bar-graph__bar-version">
      <div>
        <span title={version}>{truncate(version, 7)}</span>
      </div>
    </div>
  )
}


================================================
FILE: client/components/BlogLayout/BlogLayout.scss
================================================
@import '../../../stylesheets/colors';
@import '../../../stylesheets/variables';

.page-container.blog {
  .page-content {
    justify-content: normal;
  }
}

.blog-layout__container {
  max-width: 80ch;
  padding: 0 2rem;
  width: 100%;

  h1 {
    margin-bottom: 0;
  }

  img,
  iframe {
    max-width: 100%;
    object-fit: contain;
  }
}

.blog-post__preview-read-more {
  @include font-size-xs();
  letter-spacing: 1px;
  color: $gulf-blue;
  font-weight: $font-weight-bold;
  text-transform: uppercase;

  &:hover {
    color: lighten($gulf-blue, 20%);
  }
}

.blog-post__preview {
  h2 {
    font-weight: $dark-gulf-blue;
    color: #5c5c66;
    margin: 0;

    &:hover {
      color: lighten($gulf-blue, 10%);
    }
  }

  & + & {
    margin-top: 4rem;
  }
}

.blog-post__preview-content {
  color: darken($dark-raven, 10%);
  line-height: 1.6;
  @include font-size-reg();

  a {
    color: darken($maya-blue, 20%);
    border-bottom: 1px solid $cornflower-blue;
    transition: all 0.2s;

    &:hover {
      color: darken($maya-blue, 40%);
      border-bottom: 1px dashed $cornflower-blue;
    }
  }
}

.blog-post__preview-date {
  @include font-size-sm;
  margin: 0.7rem 0 0;
  color: $raven;
  font-weight: $font-weight-light;
}


================================================
FILE: client/components/BlogLayout/BlogLayout.tsx
================================================
import React from 'react'

import { WithClassName } from '../../../types'
import ResultLayout from '../ResultLayout'

type BlogLayoutProps = React.PropsWithChildren & WithClassName

const BlogLayout = ({ className, children }: BlogLayoutProps) => (
  <ResultLayout className={className}>
    <div className="blog-layout__container">{children}</div>
  </ResultLayout>
)

export default BlogLayout


================================================
FILE: client/components/BlogLayout/index.ts
================================================
export { default } from './BlogLayout'


================================================
FILE: client/components/BuildProgressIndicator/BuildProgressIndicator.scss
================================================
@import '../../../stylesheets/colors';
@import '../../../stylesheets/variables';

.build-progress-indicator {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  flex-grow: 1;
  padding: 0 $global-spacing * 2;
  text-align: center;
}

.build-progress-indicator__text {
  font-size: 0.7rem;
  margin-top: $global-spacing * 3;
  color: lighten($raven, 15%);
  text-transform: uppercase;
  font-weight: $font-weight-bold;
  letter-spacing: 2px;
  line-height: 1.5;
}


================================================
FILE: client/components/BuildProgressIndicator/BuildProgressIndicator.tsx
================================================
import React, { Component } from 'react'

import ProgressHex from '../ProgressHex'

const OptimisticLoadTimeout = 700

type BuildProgressIndicatorProps = {
  isDone: boolean
  onDone: () => void
}

type BuildProgressIndicatorState = {
  started: boolean
  progressText?: string
}

const order = ['resolving', 'building', 'minifying', 'calculating'] as const

export default class BuildProgressIndicator extends Component<
  BuildProgressIndicatorProps,
  BuildProgressIndicatorState
> {
  stage: number
  timeoutId?: ReturnType<typeof setTimeout>

  constructor(props: BuildProgressIndicatorProps) {
    super(props)
    this.stage = 0
    this.state = {
      started: false,
    }
  }

  componentDidMount() {
    setTimeout(() => {
      if (!this.props.isDone) {
        this.setState({ started: true })
        this.setMessage()
      }
    }, OptimisticLoadTimeout)
  }

  componentWillReceiveProps(nextProps: BuildProgressIndicatorProps) {
    if (nextProps.isDone) {
      this.stage = 3
      this.props.onDone()
    }
  }

  shouldComponentUpdate(
    props: BuildProgressIndicatorProps,
    nextState: BuildProgressIndicatorState
  ) {
    return this.state.progressText !== nextState.progressText
  }

  componentWillUnmount() {
    clearTimeout(this.timeoutId)
  }

  getProgressText = (stage: typeof order[number]) => {
    const progressText = {
      resolving: 'Resolving version and dependencies',
      building: 'Bundling package',
      minifying: 'Minifying, GZipping',
      calculating: 'Calculating file sizes',
    }
    return progressText[stage]
  }

  setMessage = (stage = 0) => {
    const timings = {
      resolving: 3 + Math.random() * 2,
      building: 5 + Math.random() * 3,
      minifying: 3 + Math.random() * 2,
      calculating: 20,
    }

    if (this.stage === order.length) {
      //this.props.onDone()
      return
    }

    this.setState({
      progressText: this.getProgressText(order[this.stage]),
    })

    this.timeoutId = setTimeout(() => {
      if (this.stage < order.length) {
        this.stage += 1
      }

      this.setMessage(this.stage)
    }, timings[order[stage]] * 1000)
  }

  render() {
    const { progressText, started } = this.state
    if (!started) {
      return null
    }

    return (
      <div className="build-progress-indicator">
        <ProgressHex compact />
        <p className="build-progress-indicator__text">{progressText}</p>
      </div>
    )
  }
}


================================================
FILE: client/components/BuildProgressIndicator/index.ts
================================================
import BuildProgressIndicator from './BuildProgressIndicator'

export default BuildProgressIndicator


================================================
FILE: client/components/Header/Header.tsx
================================================
import React, { Component } from 'react'
import Sidebar from 'react-sidebar'
import Link from 'next/link'

import { WithClassName } from '../../../types'
import GithubLogo from '../../assets/github-logo.svg'

type HeaderProps = WithClassName

type HeaderState = {
  sidebarDocked: boolean
  sidebarOpen: boolean
}

export default class Header extends Component<HeaderProps, HeaderState> {
  mql!: MediaQueryList

  constructor(props: HeaderProps) {
    super(props)
    this.state = {
      sidebarDocked: false,
      sidebarOpen: false,
    }
  }

  componentDidMount() {
    this.mql = window.matchMedia(`(min-width: 800px)`)
    this.setState({ sidebarDocked: this.mql.matches })
    this.mql.addListener(this.mediaQueryChanged)
  }

  componentWillUnmount() {
    this.mql.removeListener(this.mediaQueryChanged)
  }

  onSetSidebarOpen(open: boolean) {
    this.setState({ sidebarOpen: open })
  }

  mediaQueryChanged() {
    this.setState({ sidebarDocked: this.mql.matches, sidebarOpen: false })
  }

  render() {
    return (
      <Sidebar
        sidebar={<b>Sidebar content</b>}
        open={this.state.sidebarOpen}
        docked={this.state.sidebarDocked}
        onSetOpen={this.onSetSidebarOpen}
      >
        <header className="page-header">
          <section className="result-header--left-section">
            <Link href="/">
              <div className="logo-small">
                <span>Bundle</span>
                <span className="logo-small__alt">Phobia</span>
              </div>
            </Link>
          </section>
          <section className="page-header--right-section">
            <ul className="page-header__quicklinks">
              <li>
                <a
                  target="_blank"
                  rel="noreferrer noopener"
                  href="https://badgen.net/#bundlephobia"
                >
                  Badges
                </a>
              </li>
              <li>
                <a
                  target="_blank"
                  rel="noreferrer noopener"
                  href="https://github.com/sponsors/pastelsky"
                >
                  Sponsor
                </a>
              </li>
              <li>
                <Link href="/scan">
                  Scan package.json <sup>β</sup>
                </Link>
              </li>
            </ul>
            <a target="_blank" href="https://github.com/pastelsky/bundlephobia">
              <GithubLogo />
            </a>
          </section>
        </header>
        <b>Main content</b>
      </Sidebar>
    )
  }
}


================================================
FILE: client/components/Header/index.ts
================================================
import Header from './Header'

export default Header


================================================
FILE: client/components/Icons/SearchIcon.tsx
================================================
import React from 'react'

import { WithClassName } from '../../../types'

export default function SearchIcon({ className }: WithClassName) {
  return (
    <svg
      width="90"
      height="90"
      viewBox="0 0 90 90"
      xmlns="http://www.w3.org/2000/svg"
      className={className}
    >
      <path d="M89.32 86.5L64.25 61.4C77.2 47 76.75 24.72 62.87 10.87 55.93 3.92 46.7.1 36.87.1s-19.06 3.82-26 10.77C3.92 17.8.1 27.05.1 36.87s3.82 19.06 10.77 26c6.94 6.95 16.18 10.77 26 10.77 9.15 0 17.8-3.32 24.55-9.4l25.08 25.1c.38.4.9.57 1.4.57.52 0 1.03-.2 1.42-.56.78-.78.78-2.05 0-2.83zM36.87 69.63c-8.75 0-16.98-3.4-23.17-9.6-6.2-6.2-9.6-14.42-9.6-23.17 0-8.75 3.4-16.98 9.6-23.17 6.2-6.2 14.42-9.6 23.17-9.6 8.75 0 16.98 3.4 23.18 9.6 12.77 12.75 12.77 33.55 0 46.33-6.2 6.2-14.43 9.6-23.18 9.6z" />
    </svg>
  )
}


================================================
FILE: client/components/Icons/SideEffectIcon.scss
================================================
svg.sideeffect-icon-animated {
  overflow: hidden;

  .side-effect-icon-svg__circle,
  .side-effect-icon-svg__arrows {
    transform-origin: 50% 50%;
    transition: all 0.2s;
  }

  &:hover {
    .side-effect-icon-svg__arrows {
      transform: scale(1.3);
      stroke-width: 0.3px;
    }

    .side-effect-icon-svg__circle {
      transform: scale(1.2);
      stroke-width: 0.6px;
    }
  }
}

@keyframes shrink-arrows {
  from {
    transform: scale(2);
  }

  to {
    transform: scale(1);
  }
}

@keyframes grow-circle {
  from {
    transform: scale(1.5);
  }

  to {
    transform: scale(1);
  }
}


================================================
FILE: client/components/Icons/SideEffectIcon.tsx
================================================
import React from 'react'
import cx from 'classnames'

import { WithClassName } from '../../../types'
import SideEffectIconSVG from '../../assets/side-effect.svg'

export default function TreeShakeIcon({ className }: WithClassName) {
  return (
    <SideEffectIconSVG className={cx(className, 'sideeffect-icon-animated')} />
  )
}


================================================
FILE: client/components/Icons/TreeShakeIcon.scss
================================================
svg.treeshake-icon-animated {
  .tree-shake-icon-svg__bush {
    transition: transform 0.3s;
    transform-origin: 50% 100%;
  }

  &:hover {
    .tree-shake-icon-svg__shake {
      transform-origin: 50% 50%;
      animation: move-to-sides 0.3s, shake 0.3s 0.15s;
    }

    .tree-shake-icon-svg__bush {
      transform: scaleY(1.2);
    }
  }
}

@keyframes move-to-sides {
  from {
    transform: scale(0);
  }

  to {
    transform: scale(1);
  }
}

@keyframes shake {
  10%,
  100% {
    transform: translate3d(-0.5px, 0, 0);
  }

  80% {
    transform: translate3d(1px, 0, 0);
  }

  30%,
  70% {
    transform: translate3d(-1px, 0, 0);
  }

  60% {
    transform: translate3d(1px, 0, 0);
  }
}


================================================
FILE: client/components/Icons/TreeShakeIcon.tsx
================================================
import React from 'react'
import cx from 'classnames'

import { WithClassName } from '../../../types'
import TreeShakeIconSVG from '../../assets/tree-shake.svg'

export default function TreeShakeIcon({ className }: WithClassName) {
  return (
    <TreeShakeIconSVG className={cx(className, 'treeshake-icon-animated')} />
  )
}


================================================
FILE: client/components/JumpingDots/JumpingDots.scss
================================================
@import '../../../stylesheets/variables';

.jumping-dots {
  position: relative;
  text-align: center;
  padding: 0 $global-spacing * 0.5;
}

.jumping-dots__dot {
  display: inline-block;
  width: 2px;
  height: 2px;
  border-radius: 50%;
  margin-right: 3px;
  background: #303131;
  animation: dots-wave 1s linear infinite;

  &:nth-child(2) {
    animation-delay: -0.9s;
  }

  &:nth-child(3) {
    animation-delay: -0.8s;
  }
}

@keyframes dots-wave {
  0%,
  60%,
  100% {
    transform: initial;
  }

  30% {
    transform: translateY(-8px);
  }
}


================================================
FILE: client/components/JumpingDots/JumpingDots.tsx
================================================
import React from 'react'

export default function JumpingDots() {
  return (
    <span className="jumping-dots">
      <span className="jumping-dots__dot" />
      <span className="jumping-dots__dot" />
      <span className="jumping-dots__dot" />
    </span>
  )
}


================================================
FILE: client/components/JumpingDots/index.ts
================================================
export { default } from './JumpingDots'


================================================
FILE: client/components/Layout/Layout.scss
================================================
@use "sass:math";

@import '../../../stylesheets/variables';
@import '../../../stylesheets/base';
@import '../../../stylesheets/colors';

$footer-content-max-width: 800px;

.layout {
  max-width: 100%;
}

footer {
  background: #222;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 0 $global-spacing * 5;
  color: lighten($raven, 27%);
  flex-direction: column;

  a {
    color: lighten($raven, 27%);
    transition: color 0.2s;

    &:hover {
      color: lighten($raven, 40%);
    }
  }
}

.footer__recent-search-bar {
  width: 100%;
  background: darken($raven, 22%);
  padding: 0 $global-spacing * 2;
}

.footer__recent-search-bar__wrap {
  max-width: $footer-content-max-width;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: auto;

  h4 {
    @include font-size-xs;
    color: lighten($raven, 27%);
    margin: 0;
    line-height: 1.2;
  }
}

.footer__recent-search-list {
  @include font-size-xs;

  display: flex;
  padding: 0;
  margin: 0 0 0 $global-spacing * 1.5;
  flex-grow: 1;
  max-width: 960px;

  @media screen and (max-width: 40em) {
    margin: 0;
  }

  li {
    list-style: none;
    position: relative;
    flex-grow: 1;
    text-align: center;
    font-family: $font-family-code;
    letter-spacing: 0.5px;

    a {
      padding: $global-spacing $global-spacing * 2;

      @media screen and (max-width: 48em) {
        padding: $global-spacing $global-spacing * 0.5;
      }
    }

    &:not(:first-of-type) {
      &::after {
        content: '';
        width: 1px;
        height: 60%;
        background: rgba(255, 255, 255, 0.1);
        position: absolute;
        left: 0;
        top: 0;
        bottom: 0;
        margin: auto;
      }
    }

    @media screen and (max-width: 48em) {
      &:nth-child(n + 5) {
        display: none;
      }
    }

    @media screen and (max-width: 40em) {
      &:nth-child(n + 4) {
        display: none;
      }
    }
  }
}

.footer__split {
  display: flex;
  max-width: $footer-content-max-width;
  padding: $global-spacing * 3 $global-spacing;
  margin: auto;

  @media screen and (max-width: 40em) {
    padding: $global-spacing * 3 $global-spacing * 2.5;
    flex-direction: column;
  }
}

.footer__hosting-credits {
  @include font-size-xs;
  border-top: 1px solid darken($raven, 25%);
  color: lighten($raven, 70%);
  text-transform: uppercase;
  letter-spacing: 2px;
  padding-top: $global-spacing;
  padding-right: $global-spacing;
  font-size: 12px;
  width: $global-spacing * 20;

  @media screen and (max-width: 40em) {
    text-align: center;
    padding-top: $global-spacing * 1.5;
    margin: $global-spacing * 3 auto auto;
  }
}

.footer__zeit-logo {
  width: $global-spacing;
  height: $global-spacing;
  margin: 0 $global-spacing * 0.5;
}

.footer__credits {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-basis: 33%;

  p {
    margin: 0;
  }

  @media screen and (max-width: 40em) {
    margin-top: $global-spacing * 2;
  }
}

.footer__description {
  @include font-size-xs;
  color: lighten($raven, 20%);
  flex-basis: 66%;

  p {
    text-align: left;
    line-height: 1.4;

    a {
      display: inline;
    }

    code {
      font-family: $font-family-code;
      padding: 0 math.div($global-spacing, 3) 0 $global-spacing;
      opacity: 0.9;
    }
  }
}

.footer__credits__heart {
  width: 8vw;
  height: 8vw;
  max-width: 100px;

  path {
    fill: mix($raven, $maya-blue);
  }

  &:hover {
    path {
      animation: pulse 10s infinite both;
    }
  }
}

@keyframes pulse {
  0% {
    fill: mix($raven, $maya-blue);
  }

  25% {
    fill: mix($raven, $cornflower-blue);
  }

  50% {
    fill: $raven;
  }

  75% {
    fill: mix($raven, $maya-blue);
  }
}

.footer__credits-fork-button {
  @include font-size-xs;
  cursor: pointer;
  margin-top: $global-spacing;
  border: 2px solid lighten($raven, 30%);
  background: transparent;
  border-radius: 10px;
  padding: math.div($global-spacing, 1.5) $global-spacing;
  display: block;
  transition: background 0.2s;
  color: lighten($raven, 30%);
  text-transform: uppercase;
  letter-spacing: 1.5px;
  font-size: 10px;
  font-weight: $font-weight-bold;

  &:hover {
    background: lighten($raven, 30%);
    color: #212121;
  }

  @media screen and (max-width: 48em) {
    padding: $global-spacing $global-spacing * 2;
    margin-top: $global-spacing * 2;
  }
}

.footer__credits-profile {
  margin-top: -$global-spacing * 1.5;
  margin-bottom: $global-spacing * 0.5;
}

.footer__sponsor-logo {
  margin-top: $global-spacing;
}

footer {
  p {
    text-align: center;
  }

  a {
    display: block;
    text-decoration: none;
  }
}


================================================
FILE: client/components/Layout/Layout.tsx
================================================
import React, { Component } from 'react'
import Link from 'next/link'

import API from '../../api'
import Heart from '../../assets/heart.svg'
import DigitalOceanLogo from '../../assets/digital-ocean-logo.svg'
import { AnnouncementBanner } from '../AnnouncementBanner'
import { WithClassName } from '../../../types'

type LayoutProps = React.PropsWithChildren & WithClassName

type LayoutState = {
  recentSearches: string[]
}

export default class Layout extends Component<LayoutProps, LayoutState> {
  state = {
    recentSearches: [],
  }

  componentDidMount() {
    API.getRecentSearches(5).then(searches => {
      this.setState({
        recentSearches: Object.keys(searches),
      })
    })
  }

  render() {
    const { children, className } = this.props
    const { recentSearches } = this.state

    return (
      <section className="layout">
        <AnnouncementBanner />
        <section className={className}>{children}</section>

        <footer>
          <div className="footer__recent-search-bar">
            <div className="footer__recent-search-bar__wrap">
              <h4>Recent searches</h4>
              <ul className="footer__recent-search-list">
                {recentSearches.map(search => (
                  <li key={search}>
                    <Link href={`/package/${search}`}>{search}</Link>
                  </li>
                ))}
              </ul>
            </div>
          </div>
          <section className="footer__split">
            <div className="footer__description">
              <h3> What does Bundlephobia do? </h3>
              <p>
                JavaScript bloat is more real today than it ever was. Sites
                continuously get bigger as more (often redundant) libraries are
                thrown to solve new problems. Until of-course, the{' '}
                <i> big rewrite </i>
                happens.
              </p>
              <p>
                Bundlephobia lets you understand the performance cost of
                <code>npm&nbsp;install</code> ing a new npm package before it
                becomes a part of your bundle. Analyze size, compositions and
                exports
              </p>
              <p>
                Credits to{' '}
                <a href="https://twitter.com/thekitze" target="_blank">
                  {' '}
                  @thekitze{' '}
                </a>
                for the name.
              </p>
              <div className="footer__hosting-credits">
                Hosted on
                <a href="https://digitalocean.com" target="_blank">
                  <DigitalOceanLogo className="footer__sponsor-logo" />
                </a>
              </div>
            </div>
            <div className="footer__credits">
              <Heart className="footer__credits__heart" />️
              <a
                className="footer__credits-profile"
                target="_blank"
                href="https://github.com/pastelsky"
              >
                @pastelsky
              </a>
              <a
                target="_blank"
                href="https://github.com/pastelsky/bundlephobia"
              >
                <button className="footer__credits-fork-button">
                  Star on GitHub
                </button>
              </a>
            </div>
          </section>
        </footer>
      </section>
    )
  }
}


================================================
FILE: client/components/Layout/index.ts
================================================
import Layout from './Layout'

export default Layout


================================================
FILE: client/components/MetaTags.tsx
================================================
import React from 'react'
import Head from 'next/head'

export const DEFAULT_DESCRIPTION_START =
  'Bundlephobia helps you find the performance impact of npm packages.'

type MetaTagsProps = {
  title: string
  canonicalPath: string
  description?: string
  twitterDescription?: string
  image?: string
  isLargeImage?: boolean
}

export default function MetaTags({
  description,
  twitterDescription,
  title,
  canonicalPath,
  image,
  isLargeImage,
}: MetaTagsProps) {
  const defaultDescription = `${DEFAULT_DESCRIPTION_START} Find the size of any javascript package and its effect on your frontend bundle.`
  const defaultImage = 'https://bundlephobia.com/android-chrome-256x256.png'
  const origin =
    typeof window === 'undefined'
      ? 'https://bundlephobia.com'
      : window.location.origin

  return (
    <Head>
      <title>{title}</title>
      <meta name="description" content={description || defaultDescription} />
      <meta property="og:title" key="og:title" content={title} />
      <meta
        property="og:description"
        key="og:description"
        content={description || defaultDescription}
      />
      <meta property="og:type" key="og:type" content="website" />
      <meta property="og:url" key="og:url" content={origin + canonicalPath} />
      <meta
        property="og:image"
        key="og:image"
        content={image || defaultImage}
      />
      <meta
        property="twitter:creator"
        key="twitter:creator"
        content="@_pastelsky"
      />
      {twitterDescription && (
        <meta
          property="twitter:description"
          key="twitter:description"
          content={twitterDescription}
        />
      )}
      {isLargeImage ? (
        <meta
          name="twitter:card"
          key="twitter:card"
          content="summary_large_image"
        />
      ) : (
        <meta name="twitter:card" content="summary" key="summary" />
      )}
      <link rel="canonical" href={origin + canonicalPath} />
    </Head>
  )
}


================================================
FILE: client/components/PageNav/PageNav.tsx
================================================
import Link from 'next/link'
import React from 'react'
import GithubLogo from '../../assets/github-logo.svg'

type PageNavProps = {
  minimal?: boolean
}

const PageNav = ({ minimal }: PageNavProps) => (
  <header className="page-header">
    {!minimal && (
      <section className="result-header--left-section">
        <Link href="/">
          <div className="logo-small">
            <span>Bundle</span>
            <span className="logo-small__alt">Phobia</span>
          </div>
        </Link>
      </section>
    )}
    <section className="page-header--right-section">
      <ul className="page-header__quicklinks">
        <li>
          <a
            target="_blank"
            rel="noreferrer noopener"
            href="https://badgen.net/#bundlephobia"
          >
            Badges
          </a>
        </li>
        <li>
          <a
            target="_blank"
            rel="noreferrer noopener"
            href="https://github.com/sponsors/pastelsky"
          >
            Sponsor
          </a>
        </li>
        <li>
          <Link href="/blog">Blog</Link>
        </li>
        {!minimal && (
          <li>
            <Link href="/scan">Scan package.json</Link>
          </li>
        )}
      </ul>
      <a target="_blank" href="https://github.com/pastelsky/bundlephobia">
        <GithubLogo />
      </a>
    </section>
  </header>
)

export default PageNav


================================================
FILE: client/components/PageNav/index.ts
================================================
export { default } from './PageNav'


================================================
FILE: client/components/ProgressHex/ProgressHex.scss
================================================
.progress-hex {
  width: 8rem;
  height: 8rem;
  contain: strict;
  will-change: transform;

  circle {
    fill: #212121;
    transform-box: view-box;
    transform-origin: 50% 50%;
  }
}

.progress-hex__trail {
  stroke-width: 1px;
}


================================================
FILE: client/components/ProgressHex/ProgressHex.tsx
================================================
import React, { Component } from 'react'
import ProgressHexAnimator from './progress-hex-timeline'

type ProgressHexProps = {
  compact?: boolean
}

class ProgressHex extends Component<ProgressHexProps> {
  svgRef: React.RefObject<SVGSVGElement>
  animator?: ProgressHexAnimator
  timeline?: ReturnType<ProgressHexAnimator['createTimeline']>

  constructor(props: ProgressHexProps) {
    super(props)
    this.svgRef = React.createRef()
  }

  componentDidMount() {
    this.animator = new ProgressHexAnimator({ svg: this.svgRef.current! })
    this.timeline = this.animator.createTimeline()
    this.timeline.play()
  }

  componentWillUnmount() {
    this.timeline?.pause()
  }

  render() {
    const { compact } = this.props
    return (
      <svg
        className="progress-hex"
        xmlns="http://www.w3.org/2000/svg"
        width="112"
        height="120"
        viewBox="0 0 112 120"
        ref={this.svgRef}
      >
        {!compact && (
          <g id="ring-5">
            <circle cx="28.99" cy="108.24" r="1" />
            <circle cx="42.98" cy="116" r="1" />
            <circle cx="14.99" cy="100.49" r="1" />
            <circle cx="1" cy="92.73" r="1" />
            <circle cx="58.39" cy="124.54" r="1" />
            <circle cx="1" cy="77.73" r="1" />
            <circle cx="72.72" cy="117.48" r="1" />
            <circle cx="1" cy="62.73" r="1" />
            <circle cx="86.71" cy="110.24" r="1" />
            <circle cx="1.01" cy="45.97" r="1" />
            <circle cx="100.71" cy="101.24" r="1" />
            <circle cx="1.01" cy="30.97" r="1" />
            <circle cx="114.71" cy="94" r="1" />
            <circle cx="15" cy="23.73" r="1" />
            <circle cx="114.71" cy="79" r="1" />
            <circle cx="28.99" cy="15.49" r="1" />
            <circle cx="114.71" cy="63" r="1" />
            <circle cx="42.99" cy="8.24" r="1" />
            <circle cx="114.71" cy="48" r="1" />
            <circle cx="56.98" cy="1" r="1" />
            <circle cx="114.71" cy="33" r="1" />
            <circle cx="100.71" cy="25.24" r="1" />
            <circle cx="86.72" cy="17.48" r="1" />
            <circle cx="72.39" cy="9.54" r="1" />
          </g>
        )}
        <g id="ring-4">
          <circle cx="28.99" cy="93.24" r="1" />
          <circle cx="42.99" cy="101.00" r="1" />
          <circle cx="15.00" cy="85.49" r="1" />
          <circle cx="58.40" cy="109.54" r="1" />
          <circle cx="15.00" cy="70.49" r="1" />
          <circle cx="72.72" cy="102.48" r="1" />
          <circle cx="15.01" cy="53.73" r="1" />
          <circle cx="86.72" cy="93.48" r="1" />
          <circle cx="15.01" cy="38.73" r="1" />
          <circle cx="100.72" cy="86.24" r="1" />
          <circle cx="29.00" cy="31.49" r="1" />
          <circle cx="100.72" cy="71.24" r="1" />
          <circle cx="42.99" cy="23.24" r="1" />
          <circle cx="100.72" cy="55.24" r="1" />
          <circle cx="56.99" cy="16.00" r="1" />
          <circle cx="100.72" cy="40.24" r="1" />
          <circle cx="86.72" cy="32.48" r="1" />
          <circle cx="72.40" cy="24.54" r="1" />
        </g>
        <g id="ring-3">
          <circle cx="29.00" cy="78.24" r="1" />
          <circle cx="42.99" cy="86.00" r="1" />
          <circle cx="58.41" cy="94.54" r="1" />
          <circle cx="29.01" cy="61.49" r="1" />
          <circle cx="72.41" cy="85.54" r="1" />
          <circle cx="29.01" cy="46.49" r="1" />
          <circle cx="86.73" cy="78.48" r="1" />
          <circle cx="43.00" cy="39.24" r="1" />
          <circle cx="86.73" cy="63.48" r="1" />
          <circle cx="56.99" cy="31.00" r="1" />
          <circle cx="86.73" cy="47.48" r="1" />
          <circle cx="72.41" cy="39.54" r="1" />
        </g>
        <g id="ring-2">
          <circle cx="43.00" cy="68.24" r="1" />
          <circle cx="56.99" cy="76.00" r="1" />
          <circle cx="43.00" cy="53.24" r="1" />
          <circle cx="72.41" cy="69.54" r="1" />
          <circle cx="56.99" cy="46.00" r="1" />
          <circle cx="72.41" cy="54.54" r="1" />
        </g>
        <g id="ring-1">
          <circle cx="56" cy="60" r="1" />
        </g>
      </svg>
    )
  }
}

export default ProgressHex


================================================
FILE: client/components/ProgressHex/index.ts
================================================
export { default } from './ProgressHex'


================================================
FILE: client/components/ProgressHex/progress-hex-timeline.ts
================================================
import anime, { AnimeAnimParams } from 'animejs'

import colors from '../../config/colors'
import { randomFromArray, zeroToN } from '../../../utils'

const DURATION = 1000

type Circle = { cx: number; cy: number; ringNumber: number }

type CirclesMap = Map<SVGCircleElement, Circle>

type ProgressHexAnimatorProps = {
  svg: SVGSVGElement
}

export default class ProgressHexAnimator {
  circlesMap: CirclesMap
  circles: NodeListOf<SVGCircleElement>
  rings: NodeListOf<SVGGElement>
  trailBlaze: Trailblaze
  width: number
  height: number

  constructor({ svg }: ProgressHexAnimatorProps) {
    this.circlesMap = new Map()
    this.circles = svg.querySelectorAll('circle')
    this.rings = svg.querySelectorAll('g')
    this.trailBlaze = new Trailblaze({
      linesCount: 55,
      svg,
      circlesMap: this.circlesMap,
      ringsCount: this.rings.length,
    })

    Array.from(this.circles).forEach(circle => {
      const cx = parseFloat(circle.getAttribute('cx')!)
      const cy = parseFloat(circle.getAttribute('cy')!)
      circle.style.transformOrigin = `${cx}px ${cy}px`
      this.circlesMap.set(circle, {
        cx,
        cy,
        ringNumber: parseInt(circle.parentElement!.id.match(/.+(\d+)/)![1]) - 1,
      })
    })

    this.width = parseFloat(svg.getAttribute('width')!)
    this.height = parseFloat(svg.getAttribute('height')!)
  }

  getTranslation(circle: SVGCircleElement, distance: number) {
    const { cx, cy } = this.circlesMap.get(circle)!
    const { x, y } = this.pointAtDistance(
      cx,
      cy,
      this.width / 2,
      this.height / 2,
      distance
    )

    return { x: x - cx, y: y - cy }
  }

  pointAtDistance(x1: number, y1: number, x2: number, y2: number, d: number) {
    const curDistanceBetweenPoints = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
    if (curDistanceBetweenPoints === 0) return { x: x1, y: y1 }

    const t = d / curDistanceBetweenPoints
    const x = (x1 - t * x2) / (1 - t)
    const y = (y1 - t * y2) / (1 - t)
    return { x, y }
  }

  createTimeline() {
    const fadeInTimeline = anime.timeline({
      duration: DURATION,
      autoplay: false,
      loop: false,
    })

    const quakeTimeline = anime.timeline({
      duration: DURATION,
      autoplay: false,
      loop: true,
    })

    const fadeInRings = {
      targets: this.rings,
      opacity: [0, 1],
      delay: anime.stagger(DURATION / 5, { from: 'last' }),
      duration: DURATION / 2,
      easing: 'linear',
    }

    const quakeCircles = {
      targets: this.circles,
      scale: (el: SVGCircleElement) =>
        this.circlesMap.get(el)!.ringNumber === 0 ? 3 : 1.5,
      translateY: (circle: SVGCircleElement) =>
        this.getTranslation(circle, 4).y,
      translateX: (circle: SVGCircleElement) =>
        this.getTranslation(circle, 4).x,
      delay: ((el: SVGCircleElement) =>
        (Math.pow(this.circlesMap.get(el)!.ringNumber, 0.6) * DURATION) / 4 +
        (this.circlesMap.get(el)!.ringNumber > 0
          ? DURATION / 2.5
          : 0)) as unknown as AnimeAnimParams['delay'],
      duration: DURATION,
      easing: () => (t: number) => Math.sin(t * Math.PI),
      changeBegin: () => this.trailBlaze.start(),
    }

    fadeInTimeline.add(fadeInRings)
    quakeTimeline.add(quakeCircles)

    return {
      ...quakeTimeline,
      play: () => {
        fadeInTimeline.play()
        setTimeout(() => {
          quakeTimeline.play()
        }, DURATION)
      },
    }
  }
}

type TrailblazeProps = {
  svg: SVGSVGElement
  circlesMap: CirclesMap
  linesCount: number
  ringsCount: number
}

class Trailblaze {
  circlesMap: CirclesMap
  ringsCount: number
  lines: SVGLineElement[]

  constructor({ linesCount, svg, circlesMap, ringsCount }: TrailblazeProps) {
    this.lines = []
    this.circlesMap = circlesMap
    this.ringsCount = ringsCount
    for (let i = 0; i < linesCount; i++) {
      const line = this.createTrail()
      this.lines.push(line)
      svg.insertBefore(line, svg.children[0])
    }
  }

  createTrail() {
    const line = document.createElementNS('http://www.w3.org/2000/svg', 'line')
    line.setAttribute('stroke-width', '0.5')
    line.setAttribute('class', 'progress-hex__trail')
    return line
  }

  setLineCoords(line: SVGLineElement, x1 = 0, x2 = 0, y1 = 0, y2 = 0) {
    line.setAttribute('x1', `${x1}`)
    line.setAttribute('x2', `${x2}`)
    line.setAttribute('y1', `${y1}`)
    line.setAttribute('y2', `${y2}`)
  }

  getCirclesInRing(ringNumber: number) {
    const circles: Circle[] = []
    this.circlesMap.forEach(value => {
      if (value.ringNumber === ringNumber) {
        circles.push(value)
      }
    })
    return circles
  }

  distanceBetweenCircles(c1: Circle, c2: Circle) {
    return Math.sqrt((c2.cx - c1.cx) ** 2 + (c2.cy - c1.cy) ** 2)
  }

  getRandomConnection() {
    const rings = zeroToN(this.ringsCount)
    const sourceRingNumber = randomFromArray(rings.slice(0, -1))
    const destinationRingNumber = sourceRingNumber + 1

    const eligibleSourceCircles = this.getCirclesInRing(sourceRingNumber)
    const sourceCircle = randomFromArray(eligibleSourceCircles)

    const eligibleDestinationCircles = this.getCirclesInRing(
      destinationRingNumber
    )

    const destinationCircleDistances = eligibleDestinationCircles.map(
      (circle, index) => ({
        index,
        distance: this.distanceBetweenCircles(sourceCircle, circle),
      })
    )

    const eligibleDistancesMin = Math.min(
      ...destinationCircleDistances.map(a => a.distance)
    )
    const eligibleDestinationIndexes = destinationCircleDistances
      .filter(c => Math.abs(eligibleDistancesMin - c.distance) < 2)
      .map(d => d.index)

    const destinationCircle =
      eligibleDestinationCircles[randomFromArray(eligibleDestinationIndexes)]

    return {
      source: sourceCircle,
      destination: destinationCircle,
    }
  }

  getDashOffset = (element: SVGElement | HTMLElement | null) => {
    if (!element) return 0
    try {
      return anime.setDashoffset(element)
    } catch (err) {
      // Called before the element was rendered
      console.error(err)
      return 0
    }
  }

  start() {
    const lineMap = new WeakMap<
      SVGLineElement,
      { source: Circle; destination: Circle }
    >()

    this.lines.forEach(line => {
      const { source, destination } = this.getRandomConnection()
      lineMap.set(line, { source, destination })
      line.setAttribute('stroke', randomFromArray(colors))
      this.setLineCoords(
        line,
        source.cx,
        destination.cx,
        source.cy,
        destination.cy
      )
    })

    anime({
      targets: this.lines,
      opacity: [1, 0.9, 0],
      strokeDashoffset: [(el: SVGLineElement) => this.getDashOffset(el), 0],
      x1: (el: SVGLineElement) => lineMap.get(el)!.source.cx,
      x2: (el: SVGLineElement) => lineMap.get(el)!.destination.cx,
      y1: (el: SVGLineElement) => lineMap.get(el)!.source.cy,
      y2: (el: SVGLineElement) => lineMap.get(el)!.destination.cy,
      duration: 500,
      delay: () => anime.random(0, DURATION / 5),
      easing: 'easeOutCubic',
    })
  }
}


================================================
FILE: client/components/QuickStatsBar/QuickStatsBar.scss
================================================
@use "sass:math";

@import '../../../stylesheets/colors';
@import '../../../stylesheets/variables';

.quick-stats-bar {
  display: flex;
  align-content: center;
  @include font-size-xs;
  color: lighten($raven, 15%);
  background: lighten($raven, 55%);
  border-radius: 0 0 10px 10px;
  overflow: hidden;
}

.quick-stats-bar__stat {
  padding: math.div($global-spacing, 1.5) $global-spacing * 1.5;
  display: flex;
  align-content: center;
  position: relative;
  justify-content: center;
  flex: 1 1 auto;
  white-space: nowrap;
  margin: auto 0;

  & > * {
    margin: auto 0;
  }

  &:not(:first-of-type)::before {
    content: '';
    width: 1px;
    height: 60%;
    position: absolute;
    background: transparentize(black, 0.95);
    top: 0;
    bottom: 0;
    margin: auto;
    left: 0;
  }

  &:first-of-type,
  &:last-of-type {
    //padding-left: $global-spacing;
  }
}

.quick-stats-bar__stat--optional {
  @media screen and (max-width: 48em) {
    display: none;
  }

  @media screen and (max-width: 40em) {
    display: none;
  }
}

.quick-stats-bar__stat--description {
  overflow: hidden;
  text-overflow: ellipsis;
  display: block;
  flex-grow: 1;

  @media screen and (max-width: 40em) {
    display: none;
  }
}

.quick-stats-bar__stat--description-content {
  margin-left: $global-spacing;
}

.quick-stats-bar__stat-icon {
  margin-right: $global-spacing;
}

.quick-stats-bar__logo-icon {
  vertical-align: middle;

  &.quick-stats-bar__logo-icon--npm {
    width: 36px;
  }

  &.quick-stats-bar__logo-icon--github {
    height: 18px;
    width: 18px;
  }

  path {
    transition: fill 0.2s;
    fill: lighten($raven, 15%);
  }
}

.quick-stats-bar__link {
  margin: auto $global-spacing * 0.5;

  &:hover {
    .quick-stats-bar__logo-icon--github {
      path {
        fill: #333;
      }
    }
    .quick-stats-bar__logo-icon--npm {
      path {
        fill: #cb3837;
      }
    }
  }
}


================================================
FILE: client/components/QuickStatsBar/QuickStatsBar.tsx
================================================
import React, { Component } from 'react'

import { sanitizeHTML } from '../../../utils/common.utils'
import TreeShakeIcon from '../../assets/tree-shake.svg'
import SideEffectIcon from '../../assets/side-effect.svg'
import DependencyIcon from '../../assets/dependency.svg'
import GithubIcon from '../../assets/github-logo.svg'
import NPMIcon from '../../assets/npm-logo.svg'
import InfoIcon from '../../assets/info.svg'
import { PackageInfo } from '../../../types'

type QuickStatsBarProps = Pick<
  PackageInfo,
  | 'name'
  | 'description'
  | 'repository'
  | 'dependencyCount'
  | 'isTreeShakeable'
  | 'hasSideEffects'
>

class QuickStatsBar extends Component<QuickStatsBarProps> {
  static defaultProps = {
    description: '',
  }

  getStatItemCount = () => {
    const { isTreeShakeable, hasSideEffects } = this.props
    let statItemCount = 0

    if (isTreeShakeable) statItemCount += 1
    if (hasSideEffects !== true) statItemCount += 1
    return statItemCount
  }

  getTrimmedDescription = () => {
    const { description } = this.props
    const trimmed = description.trim()

    if (trimmed.endsWith('.')) {
      return trimmed.substring(0, trimmed.length - 1)
    } else {
      return trimmed
    }
  }

  render() {
    const {
      isTreeShakeable,
      hasSideEffects,
      dependencyCount,
      name,
      repository,
    } = this.props
    const statItemCount = this.getStatItemCount()
    const description = this.getTrimmedDescription()

    return (
      <div className="quick-stats-bar">
        <div
          className="quick-stats-bar__stat quick-stats-bar__stat--description "
          title={description}
        >
          <InfoIcon />
          {statItemCount < 2 && (
            <span
              className="quick-stats-bar__stat--description-content"
              dangerouslySetInnerHTML={{ __html: sanitizeHTML(description) }}
              style={{
                maxWidth: `${500 - statItemCount * 280}px`,
              }}
            />
          )}
        </div>

        {isTreeShakeable && (
          <div className="quick-stats-bar__stat">
            <TreeShakeIcon className="quick-stats-bar__stat-icon" />{' '}
            <span>tree-shakeable</span>
          </div>
        )}

        {!(hasSideEffects === true) && (
          <div className="quick-stats-bar__stat">
            <SideEffectIcon className="quick-stats-bar__stat-icon" />{' '}
            <span>
              {!(hasSideEffects === false) && hasSideEffects.length
                ? 'some side-effects'
                : 'side-effect free'}
            </span>
          </div>
        )}
        <div className="quick-stats-bar__stat quick-stats-bar__stat--optional">
          <DependencyIcon className="quick-stats-bar__stat-icon" />
          <span>
            {dependencyCount === 0 ? (
              'no dependencies'
            ) : (
              <span>
                {dependencyCount}{' '}
                {dependencyCount > 1 ? 'dependencies' : 'dependency'}
              </span>
            )}
          </span>
        </div>
        <div className="quick-stats-bar__stat">
          <a
            className="quick-stats-bar__link"
            href={'https://npmjs.com/package/' + name}
            target="_blank"
            rel="noopener noreferrer"
          >
            <NPMIcon className="quick-stats-bar__logo-icon quick-stats-bar__logo-icon--npm" />
          </a>
          {repository && (
            <a
              className="quick-stats-bar__link"
              href={repository}
              target="_blank"
              rel="noopener noreferrer"
            >
              <GithubIcon className="quick-stats-bar__logo-icon quick-stats-bar__logo-icon quick-stats-bar__logo-icon--github" />
            </a>
          )}
        </div>
      </div>
    )
  }
}

export default QuickStatsBar


================================================
FILE: client/components/QuickStatsBar/index.ts
================================================
import QuickStatsBar from './QuickStatsBar'

export default QuickStatsBar


================================================
FILE: client/components/ResultLayout/ResultLayout.scss
================================================
@import '../../../stylesheets/variables';
@import '../../../stylesheets/colors';

.page-header {
  padding: $global-spacing * 3;
  padding-bottom: $global-spacing * 2;
  display: flex;
  align-items: center;

  @media screen and (max-width: 40em) {
    padding: $global-spacing * 2;
  }
}

.page-header--right-section {
  margin-left: auto;
  display: flex;
  align-items: center;
}

.github-logo {
  width: 30px;
  height: 30px;

  @media screen and (max-width: 40em) {
    width: 20px;
    height: 20px;
  }

  path {
    fill: #666;
    transition: fill 0.2s;
  }

  &:hover {
    path {
      fill: black;
    }
  }
}

.logo-small {
  @include font-size-reg;
  text-transform: uppercase;
  font-weight: $font-weight-very-bold;
  letter-spacing: 3px;
  user-select: none;
  cursor: pointer;
  color: #212121;
}

.logo-small__alt {
  color: #888;
}

.page-container {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: calc(100vh - 6px);
  flex-gorw: 1;
}

.page-content {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  flex-grow: 1;

  @media screen and (max-width: 40em) {
    padding: 0 $global-spacing * 2;
  }
}

.page-header__quicklinks {
  list-style: none;
  margin: 0 2rem 0 0;
  font-weight: $font-weight-light;
  display: flex;

  a {
    @include font-size-xs;
    text-transform: uppercase;
    letter-spacing: 0.3px;
    font-weight: $font-weight-bold;
    opacity: 0.55;
    color: $raven;
    transition: opacity 0.2s;

    &:hover {
      opacity: 1;
    }
  }

  li + li {
    margin-left: $global-spacing * 2;
  }

  @media screen and (max-width: 40em) {
    max-width: 40vw;
    overflow: scroll;
    align-items: center;
    justify-content: flex-end;
    overflow: scroll;
  }
}


================================================
FILE: client/components/ResultLayout/ResultLayout.tsx
================================================
import React, { Component } from 'react'
import cx from 'classnames'

import Layout from '../../components/Layout'
import PageNav from '../PageNav'
import { WithClassName } from '../../../types'

export default class ResultLayout extends Component<
  React.PropsWithChildren & WithClassName
> {
  render() {
    const { children, className } = this.props
    return (
      <Layout>
        <div className={cx('page-container', className)}>
          <PageNav />
          <div className="page-content">{children}</div>
        </div>
      </Layout>
    )
  }
}


================================================
FILE: client/components/ResultLayout/index.ts
================================================
import ResultLayout from './ResultLayout'

export default ResultLayout


================================================
FILE: client/components/Separator.tsx
================================================
import React from 'react'

type SeparatorProps = {
  text?: string
  align?: React.CSSProperties['justifyContent']
  showLeft?: boolean
  containerStyles?: React.CSSProperties
}

export default function Separator({
  text = 'or',
  align = 'center',
  showLeft = true,
  containerStyles,
}: SeparatorProps) {
  const commonStyles = {
    display: 'flex',
    justifyContent: align,
    alignItems: 'center',
  }

  return (
    <div
      style={{
        width: '100%',
        position: 'relative',
        margin: '6px 0',
        opacity: '0.7',
        ...containerStyles,
      }}
    >
      <div
        style={{
          width: '100%',
          ...commonStyles,
        }}
      >
        {showLeft && (
          <div style={commonStyles}>
            <svg width="49" height="39" xmlns="http://www.w3.org/2000/svg">
              <g fill="#211915" fillRule="evenodd">
                <path d="M28.4 21c5.2-2.9 12.2-2.3 18.1-1-1.9.8-3.7 1.8-5.8 2.1a24.8 24.8 0 0 1-12.3-1zm-7.2 14.7c3 4.3 11.2 3.8 11.7-2.1.4-4.7-5.8-8-8.5-3.6-.7 1.3-.9 3 .3 4 1.5 1.3 3.5.5 4.1-1.2.3-.7-.8-.7-1.1-.4-1.1 1-2-.1-2-1 0-1.6 2-2 3.1-1.4 3 1.5 2.8 5.8-.4 6.7-2.6.7-5-1-6-3.3-1.2-2.3-.6-4.9.6-7 1-1.7 2.1-3 3.4-4l2.3.7c3.6 1 7.5 1.3 11.2.8 2.8-.4 6.4-1.3 8.4-3.5l.2-.3c.2-.2.3-.5 0-.6-6.8-3-15.3-3.2-21.7 1a15 15 0 0 1-6.3-4.8C17.5 12 17 4.4 22.7 2.6c2.4-.8 5.3.3 5.7 3 .3 2-1.1 4-3.3 4.2-.8 0-1.9-.3-2.2-1a.9.9 0 0 1 1.4-1.1c.5.4 1.3-.3 1-.8-1.4-1.8-3.4-1-3.9 1-.5 2.6 2 3.7 4 3.3 5.1-1 6.6-8.4 1.4-10.2-5.4-1.8-10 3.7-10.3 8.6-.4 5.7 3.7 9.9 8.5 12.2-4 3.4-7.1 9.1-3.8 14z" />
                <path d="M5.7 21.7zm12.5 1.8c-1.7 3-3.7 5.4-7 6.6 1-3.3 3.6-5.7 7-6.6zm-.9-3.6a17.7 17.7 0 0 1-7.3-5c2.7 1 5.3 3 7.3 5zM.6 22.3c2.4 1 5 1.2 7.6 1.3h6a12.2 12.2 0 0 0-5.1 7.8c-.1.6.3.8.9.7 4.5-1 9.9-5 10.3-10v-.2c0-.1.1-.3 0-.4a20.5 20.5 0 0 0-10.9-8.6c-.3-.1-.8.1-1 .4-1.3 2 1 3.7 2.4 4.9 1.1 1 2.3 1.8 3.6 2.4-4.4-.2-8.8-1.1-13.1.1-.5.1-1.5 1.2-.7 1.6z" />
              </g>
            </svg>
          </div>
        )}
        <div style={commonStyles}>
          <svg width="249" height="3" xmlns="http://www.w3.org/2000/svg">
            <path
              d="M1.5 2.3c31.4.7 62.9.2 94.3.3h144.8c1.3 0 .5-1.7-.5-1.7h-.3c-1.3 0-.4 1.7.5 1.7h8c1.3-.1.3-2-.8-1.6h.1c-1.2 0-.4 1.5.5 1.5.1 0 .2 0 0 0 1.2.4 1-1.5-.2-1.6h-8c-1.4 0-.5 1.7.4 1.7h.3c1.3 0 .5-1.7-.5-1.7H55.4c-18 0-36.2-.1-54.2.3-.9 0-.3 1.1.3 1.1"
              fill="#211915"
              fillRule="evenodd"
            />
          </svg>
        </div>
        {!showLeft && (
          <div style={{ ...commonStyles, marginLeft: '-20px' }}>
            <svg width="249" height="3" xmlns="http://www.w3.org/2000/svg">
              <path
                d="M1.5 2.3c31.4.7 62.9.2 94.3.3h144.8c1.3 0 .5-1.7-.5-1.7h-.3c-1.3 0-.4 1.7.5 1.7h8c1.3-.1.3-2-.8-1.6h.1c-1.2 0-.4 1.5.5 1.5.1 0 .2 0 0 0 1.2.4 1-1.5-.2-1.6h-8c-1.4 0-.5 1.7.4 1.7h.3c1.3 0 .5-1.7-.5-1.7H55.4c-18 0-36.2-.1-54.2.3-.9 0-.3 1.1.3 1.1"
                fill="#211915"
                fillRule="evenodd"
              />
            </svg>
          </div>
        )}
        <div style={commonStyles}>
          <svg width="49" height="39" xmlns="http://www.w3.org/2000/svg">
            <g fill="#211915" fillRule="evenodd">
              <path d="M16 22.2c-2.5.3-5 .3-7.4 0-2.1-.4-4-1.4-5.8-2.2 6-1.3 12.9-1.9 18.1 1-1.6.6-3.3 1-5 1.2zm8.2-.4c4.9-2.3 9-6.5 8.6-12.2C32.5 4.7 28-.8 22.5 1c-5.2 1.8-3.7 9.3 1.3 10.2 2.1.4 4.6-.7 4-3.2-.4-2.1-2.4-2.9-3.8-1-.3.4.5 1 1 .7a.9.9 0 0 1 1.4 1c-.3.8-1.4 1.2-2.2 1.1-2.2-.1-3.6-2.2-3.3-4.2.4-2.7 3.3-3.8 5.7-3 5.7 1.8 5.1 9.3 2.2 13.1a15 15 0 0 1-6.3 4.8c-6.4-4.2-15-4-21.7-1-.3 0-.2.4 0 .6l.2.3c2 2.3 5.6 3.1 8.4 3.5A27.8 27.8 0 0 0 23 22.4c1.3 1 2.4 2.3 3.4 4 1.2 2.1 1.7 4.7.6 7-1 2.2-3.4 4-6 3.3-3.2-.9-3.4-5.2-.5-6.7 1.3-.6 3.1-.2 3.1 1.4 0 .9-.8 2-1.9 1-.4-.3-1.4-.3-1.1.4.6 1.7 2.6 2.5 4 1.2 1.3-1 1.1-2.7.4-4-2.7-4.4-9-1-8.5 3.6.5 6 8.7 6.4 11.7 2.1 3.3-4.8.1-10.5-3.9-14z" />
              <path d="M39.6 21.9zM38.1 30a12.7 12.7 0 0 1-7-6.6c3.4.9 6 3.3 7 6.6zm1.2-15.2c-1 1.1-3.4 3-3.7 3.1-1.1.8-2.4 1.4-3.7 2 2.1-2.2 4.7-4.1 7.4-5.1zm8.7 5.8c-4.4-1.2-8.7-.3-13.1 0a23 23 0 0 0 3.6-2.5c1.4-1.2 3.7-3 2.4-4.9-.2-.3-.7-.5-1-.4-4.3 1-8.7 5-11 8.6l.1.4v.2c.4 5 5.8 9 10.3 10 .6.1 1-.1 1-.7-.7-3.3-2.6-6-5.3-7.8h6.1c2.6-.1 5.2-.2 7.5-1.3.8-.4-.1-1.5-.6-1.6z" />
            </g>
          </svg>
        </div>
      </div>
      {text && (
        <span
          style={{
            position: 'absolute',
            left: 0,
            right: 0,
            top: 0,
            bottom: 0,
            margin: 'auto',
            width: '42px',
            height: '20px',
            background: 'white',
            padding: '0 15px',
            borderRadius: '50%',
          }}
        >
          {text}
        </span>
      )}
    </div>
  )
}


================================================
FILE: client/components/SimilarPackageCard/SimilarPackageCard.scss
================================================
@use "sass:math";

@import '../../../stylesheets/colors';
@import '../../../stylesheets/variables';

.similar-package-card {
  border: 1px solid $autocomplete-border-color;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
  border-radius: 10px;
  width: calc(25% - #{$global-spacing * 2});
  display: flex;
  flex-direction: column;
  margin: $global-spacing;
  color: inherit;
  transition: all 0.2s;

  &:hover {
    transform: scale(1.01);
    box-shadow: 0 5px 10px rgba(0, 0, 0, 0.06);
    border: 1px solid fade-in($autocomplete-border-color, 0.02);
  }

  &.similar-package-card--empty {
    border-style: dashed;
    background: transparentize($raven, 0.97);
  }

  @media screen and (max-width: 64em) {
    margin: math.div($global-spacing, 1.5);
    width: calc(33.3% - #{$global-spacing * 2});
  }

  @media screen and (max-width: 48em) {
    margin: math.div($global-spacing, 1.5);
    width: calc(50% - #{$global-spacing * 1.5});
  }

  @media screen and (max-width: 40em) {
    width: 100%;
  }
}

.similar-package-card--empty {
  color: transparentize($raven, 0.5);
  align-items: center;
}

.similar-package-card__wrap {
  padding: $global-spacing * 2;
  flex-grow: 1;

  .similar-package-card--empty & {
    padding: $global-spacing * 5 $global-spacing * 2;
    align-items: center;
    text-align: center;
  }
}

.similar-package-card__header {
  display: flex;
}

.similar-package-card__name {
  margin: 0;
  font-family: $font-family-code;
  font-weight: $font-weight-light;
  flex-grow: 1;
  word-break: break-word;
}

.similar-package-card__description {
  @include font-size-sm;
  color: $dark-raven;
  line-height: 1.5;
  margin: $global-spacing 0 0 0;
  word-break: break-word;
  img {
    height: auto;
    max-width: 100%;
  }

  .similar-package-card--empty & {
    text-transform: uppercase;
    letter-spacing: 1px;
    font-weight: $font-weight-bold;
    color: transparentize($raven, 0.4);
  }
}

.similar-package-card__footer {
  background: lighten($raven, 54%);
  display: flex;
  padding: $global-spacing $global-spacing * 2;
  align-items: center;
  border-radius: 0 0 10px 10px;
}

.similar-package-card__stat {
  & + & {
    margin-left: $global-spacing * 2;
  }
}

.similar-package-card__number {
  @include font-size-md;
  font-weight: $font-weight-very-bold;
}

.similar-package-card__comparison--positive {
  color: $pastel-green;
}

.similar-package-card__comparison--negative {
  color: $carrot-orange;
}

.similar-package-card__comparison--similar {
  color: $raven;
}

.similar-package-card__label {
  @include font-size-xs;
  text-transform: uppercase;
  letter-spacing: 1px;
  line-height: 1.5;

  .similar-package-card__size & {
    color: $raven;
  }
}

.similar-package-card__treeshake {
  height: $global-spacing * 2.5;
  width: auto;
  margin-left: auto;
}

.similar-package-card__shrink {
  font-size: 75%;
  .similar-package-card__size & {
    color: $raven;
  }
}

.similar-package-card__github-icon {
  height: $global-spacing * 2;
  width: auto;
  opacity: 0.5;
  transition: all 0.2s;
  vertical-align: middle;

  &:hover {
    opacity: 1;
  }
}

.similar-package-card__plus {
  width: 35%;
  height: auto;
  margin-bottom: $global-spacing * 1.5;
  path {
    fill: transparentize($raven, 0.7);
  }
}


================================================
FILE: client/components/SimilarPackageCard/SimilarPackageCard.tsx
================================================
import React, { Component } from 'react'
import cx from 'classnames'
import Link from 'next/link'
import queryString from 'query-string'

import { formatSize } from '../../../utils'
import { sanitizeHTML } from '../../../utils/common.utils'
import TreeShakeIcon from '../../assets/tree-shake.svg'
import PlusIcon from '../../assets/plus.svg'
import GithubIcon from '../../assets/github-logo.svg'
import GitIcon from '../../assets/git-logo.svg'

type SimilarPackageCardProps = { category?: string } & (
  | { pack: any; comparisonSizePercent: number }
  | { isEmpty: true }
)

export default class SimilarPackageCard extends Component<SimilarPackageCardProps> {
  getSuggestionIssueUrl = () => {
    const params = queryString.stringify({
      labels: 'similar suggestion',
      template: '2-similar-package-suggestion.md',
      title: `Package suggestion: <package-name> for \`${this.props.category}\``,
    })

    return `https://github.com/pastelsky/bundlephobia/issues/new?${params}`
  }

  render() {
    if ('isEmpty' in this.props) {
      return (
        <a
          className="similar-package-card similar-package-card--empty"
          href={this.getSuggestionIssueUrl()}
          target="_blank"
          rel="noreferrer noopener"
        >
          <div className="similar-package-card__wrap">
            <PlusIcon className="similar-package-card__plus" />
            <p className="similar-package-card__description">Suggest another</p>
          </div>
        </a>
      )
    }

    const { pack, comparisonSizePercent } = this.props
    const { size, unit } = formatSize(pack.gzip)
    const sizeDiff = Math.abs(
      (comparisonSizePercent / 100) * pack.gzip - pack.gzip
    )

    const getComparisonNumber = (comparisonSizePercent: number) => {
      if (sizeDiff < 1500) {
        return (
          <div>
            <div className="similar-package-card__label">
              Similar <br /> size
            </div>
          </div>
        )
      } else if (Math.abs(comparisonSizePercent) > 100) {
        return (
          <div>
            <div className="similar-package-card__number">
              {(1 + Math.abs(comparisonSizePercent) / 100).toFixed(1)}{' '}
              <span className="similar-package-card__shrink">×</span>
            </div>
            <div className="similar-package-card__label">
              {comparisonSizePercent > 0 ? 'Larger' : 'Smaller'}
            </div>
          </div>
        )
      } else {
        return (
          <div>
            <div className="similar-package-card__number">
              {Math.abs(comparisonSizePercent).toFixed(0)}{' '}
              <span className="similar-package-card__shrink">%</span>
            </div>
            <div className="similar-package-card__label">
              {comparisonSizePercent > 0 ? 'Larger' : 'Smaller'}
            </div>
          </div>
        )
      }
    }

    const footer = (
      <div className="similar-package-card__footer">
        <div
          className={cx('similar-package-card__stat', {
            'similar-package-card__comparison--similar': sizeDiff < 1500,
            'similar-package-card__comparison--positive':
              comparisonSizePercent < 0,
            'similar-package-card__comparison--negative':
              comparisonSizePercent > 0,
          })}
        >
          {getComparisonNumber(comparisonSizePercent)}
        </div>
        <div className="similar-package-card__stat similar-package-card__size">
          <div className="similar-package-card__number">
            {size.toFixed(2)}
            <span className="similar-package-card__shrink"> {unit} </span>
          </div>
          <div className="similar-package-card__label">Min + Gzip</div>
        </div>

        {(pack.hasJSModule || pack.hasJSNext || pack.isModuleType) && (
          <TreeShakeIcon className="similar-package-card__treeshake" />
        )}
      </div>
    )

    return (
      <Link href={`/package/${pack.name}`} className="similar-package-card">
        <div className="similar-package-card__wrap">
          <div className="similar-package-card__header">
            <h3 className="similar-package-card__name">{pack.name}</h3>
            {pack.repository && (
              <a
                href={pack.repository}
                onClick={e => {
                  e.stopPropagation()
                  window.location = pack.repository
                }}
              >
                {pack.repository.includes('github.com') ? (
                  <GithubIcon className="similar-package-card__github-icon" />
                ) : (
                  <GitIcon className="similar-package-card__github-icon" />
                )}
              </a>
            )}
          </div>
          <p
            className="similar-package-card__description"
            dangerouslySetInnerHTML={{
              __html: sanitizeHTML(pack.description),
            }}
          />
        </div>
        {footer}
      </Link>
    )
  }
}


================================================
FILE: client/components/SimilarPackageCard/index.ts
================================================
import SimilarPackageCard from './SimilarPackageCard'

export default SimilarPackageCard


================================================
FILE: client/components/Stat/Stat.scss
================================================
@import '../../../stylesheets/variables';
@import '../../../stylesheets/colors';

.stat-container {
  margin: 0 24px;

  @media screen and (max-width: 40em) {
    margin: 0 $global-spacing;
  }
}

.stat-container--compact {
  margin: 0;
}

.stat-container__value-container {
  display: flex;
  align-items: baseline;
  justify-content: center;
  padding: 5px 15px;

  .stat-container--compact & {
    padding-top: 0;
  }
}

.stat-container__value {
  @include font-size-xxl;
  font-weight: bold;
  color: #212121;
  background: inherit;
  position: relative;

  .stat-container--compact & {
    @include font-size-lg;
    font-weight: $font-weight-light;
  }
}

.stat-container__value.time::before {
  content: attr(data-value);
  position: absolute;
  z-index: 2;
  overflow: hidden;
  color: $pastel-green;
  white-space: nowrap;
  width: 0%;
  transition: width 0.3s;
}

.stat-container__value.time:hover::before {
  width: 100%;
  transition-duration: inherit;
}

.stat-container__unit {
  @include font-size-xl;
  color: $raven;
  font-weight: bold;
  margin-left: 4px;

  .stat-container--compact & {
    @include font-size-sm;
    font-weight: $font-weight-thin;
  }
}

.stat-container__footer {
  display: flex;
  justify-content: center;
  align-items: center;
  margin-top: 10px;

  .stat-container--compact & {
    margin-top: 0;
  }
}

.stat-container__label {
  @include font-size-reg;
  color: $raven;
  text-transform: uppercase;
  letter-spacing: 2px;
  text-align: center;

  .stat-container--compact & {
    @include font-size-sm;
    letter-spacing: 1px;
  }
}

.stat-container__info-text {
  margin-left: $global-spacing * 0.5;
  border: 1px solid rgba(40, 40, 40, 0.5);
  color: rgba(40, 40, 40, 0.5);
  width: 12px;
  height: 13px;
  font-family: $font-family-code;
  font-size: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 2px;
  transition: background 0.2s;
  cursor: help;

  &:hover {
    background: rgba(40, 40, 40, 0.6);
    color: white;
  }

  &::after,
  &::before {
    font-family: $font-family-body;
  }

  @media screen and (max-width: 40em) {
    display: none;
  }
}


================================================
FILE: client/components/Stat/Stat.tsx
================================================
import React from 'react'
import cx from 'classnames'

import { formatSize, formatTime } from '../../../utils'
import { WithClassName } from '../../../types'

const Type = {
  SIZE: 'size',
  TIME: 'time',
} as const

type StatProps = WithClassName & {
  value: number
  type: 'size' | 'time'
  label: string
  infoText?: string
  compact?: boolean
}

export default function Stat({
  value,
  label,
  type,
  infoText,
  compact,
  className,
}: StatProps) {
  const roundedValue =
    type === Type.SIZE
      ? parseFloat(formatSize(value).size.toFixed(1))
      : parseFloat(formatTime(value).size.toFixed(2))

  return (
    <div
      className={cx('stat-container', className, {
        'stat-container--compact': compact,
      })}
    >
      <div className="stat-container__value-container">
        <div className="stat-container__value-wrap">
          <div
            className={cx('stat-container__value', type)}
            style={{ transitionDuration: `${value}s` }}
            data-value={roundedValue}
          >
            {roundedValue}
          </div>
        </div>
        <div className="stat-container__unit">
          {type === Type.SIZE ? formatSize(value).unit : formatTime(value).unit}{' '}
        </div>
      </div>
      <div className="stat-container__divider" />
      <div className="stat-container__footer">
        <div className="stat-container__label">{label}</div>
        {infoText && (
          <div
            className="stat-container__info-text"
            data-balloon-pos="right"
            data-balloon={infoText}
          >
            i
          </div>
        )}
      </div>
    </div>
  )
}

Stat.type = Type


================================================
FILE: client/components/Stat/index.ts
================================================
export { default } from './Stat'


================================================
FILE: client/components/Treemap/Treemap.tsx
================================================
import React, { Component } from 'react'

import squarify from './squarify'

type TreeMapProps = {
  width: number
  height: number
} & React.PropsWithChildren &
  React.HTMLAttributes<HTMLDivElement>

class TreeMap extends Component<TreeMapProps> {
  render() {
    const { width, height, children, ...others } = this.props

    const values = React.Children.map(children, square =>
      React.isValidElement(square) ? square.props.value : square
    )

    const squared = squarify(values, width, height, 0, 0)
    const getBorderRadius = (index: number) => {
      const topLeftRadius =
        squared[index][0] || squared[index][1] ? '0px' : '10px'
      const topRightRadius =
        squared[index][1] === 0 && squared[index][2] === width ? '10px' : '0px'
      const bottomLeftRadius =
        squared[index][3] === height && squared[index][0] === 0 ? '10px' : '0px'
      const bottomRightRadius =
        Math.round(squared[index][3]) === height &&
        Math.round(squared[index][2]) === width
          ? '10px'
          : '0px'

      return `${topLeftRadius} ${topRightRadius} ${bottomRightRadius} ${bottomLeftRadius}`
    }

    return (
      <div style={{ width: '100%', height, position: 'relative' }} {...others}>
        {React.Children.map(children, (child, index) => {
          if (!React.isValidElement(child)) {
            return child
          }

          const childProps = {
            left: `${(squared[index][0] / width) * 100}%`,
            top: `${(squared[index][1] / height) * 100}%`,
            width: `${
              ((squared[index][2] - squared[index][0]) / width) * 100
            }%`,
            height: `${
              ((squared[index][3] - squared[index][1]) / height) * 100
            }%`,
            borderRadius: getBorderRadius(index),
            data: squared[index],
          }

          return React.cloneElement(child, childProps)
        })}
      </div>
    )
  }
}

export default TreeMap


================================================
FILE: client/components/Treemap/TreemapSquare.tsx
================================================
import React from 'react'

type TreemapSquareProps = {
  style: React.CSSProperties
  data?: any
} & React.PropsWithChildren &
  Pick<
    React.CSSProperties,
    'left' | 'top' | 'width' | 'height' | 'borderRadius'
  >

function TreemapSquare({
  children,
  left,
  top,
  width,
  height,
  borderRadius,
  data,
  style,
  ...other
}: TreemapSquareProps) {
  return (
    <div
      data-vals={data.toString() + '...' + width + '...' + height}
      style={{
        position: 'absolute',
        left,
        top,
        width,
        height,
        borderRadius,
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        padding: '10px',
        wordBreak: 'break-word',
        flexDirection: 'column',
        ...style,
      }}
      {...other}
    >
      {children}
    </div>
  )
}

export default TreemapSquare


================================================
FILE: client/components/Treemap/index.ts
================================================
import Treemap from './Treemap'
import TreemapSquare from './TreemapSquare'

export { Treemap, TreemapSquare }


================================================
FILE: client/components/Treemap/squarify.js
================================================
/*
 * treemap-squarify.js - open source implementation of squarified treemaps
 *
 * Treemap Squared 0.5 - Treemap Charting library
 *
 * https://github.com/imranghory/treemap-squared/
 *
 * Copyright (c) 2012 Imran Ghory (imranghory@gmail.com)
 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
 *
 *
 * Implementation of the squarify treemap algorithm described in:
 *
 * Bruls, Mark; Huizing, Kees; van Wijk, Jarke J. (2000), "Squarified treemaps"
 * in de Leeuw, W.; van Liere, R., Data Visualization 2000:
 * Proc. Joint Eurographics and IEEE TCVG Symp. on Visualization, Springer-Verlag, pp. 33–42.
 *
 * Paper is available online at: http://www.win.tue.nl/~vanwijk/stm.pdf
 *
 * The code in this file is completeley decoupled from the drawing code so it should be trivial
 * to port it to any other vector drawing library. Given an array of datapoints this library returns
 * an array of cartesian coordinates that represent the rectangles that make up the treemap.
 *
 * The library also supports multidimensional data (nested treemaps) and performs normalization on the data.
 *
 * See the README file for more details.
 */

function Container(xoffset, yoffset, width, height) {
  this.xoffset = xoffset // offset from the the top left hand corner
  this.yoffset = yoffset // ditto
  this.height = height
  this.width = width

  this.shortestEdge = function () {
    return Math.min(this.height, this.width)
  }

  // getCoordinates - for a row of boxes which we've placed
  //                  return an array of their cartesian coordinates
  this.getCoordinates = function (row) {
    const coordinates = []
    var subxoffset = this.xoffset,
      subyoffset = this.yoffset //our offset within the container
    var areawidth = sumArray(row) / this.height
    var areaheight = sumArray(row) / this.width
    var i

    if (this.width >= this.height) {
      for (i = 0; i < row.length; i++) {
        coordinates.push([
          subxoffset,
          subyoffset,
          subxoffset + areawidth,
          subyoffset + row[i] / areawidth,
        ])
        subyoffset = subyoffset + row[i] / areawidth
      }
    } else {
      for (i = 0; i < row.length; i++) {
        coordinates.push([
          subxoffset,
          subyoffset,
          subxoffset + row[i] / areaheight,
          subyoffset + areaheight,
        ])
        subxoffset = subxoffset + row[i] / areaheight
      }
    }
    return coordinates
  }

  // cutArea - once we've placed some boxes into an row we then need to identify the remaining area,
  //           this function takes the area of the boxes we've placed and calculates the location and
  //           dimensions of the remaining space and returns a container box defined by the remaining area
  this.cutArea = function (area) {
    var newcontainer

    if (this.width >= this.height) {
      var areawidth = area / this.height
      var newwidth = this.width - areawidth
      newcontainer = new Container(
        this.xoffset + areawidth,
        this.yoffset,
        newwidth,
        this.height
      )
    } else {
      var areaheight = area / this.width
      var newheight = this.height - areaheight
      newcontainer = new Container(
        this.xoffset,
        this.yoffset + areaheight,
        this.width,
        newheight
      )
    }
    return newcontainer
  }
}

// normalize - the Bruls algorithm assumes we're passing in areas that nicely fit into our
//             container box, this method takes our raw data and normalizes the data values into
//             area values so that this assumption is valid.
function normalize(data, area) {
  var normalizeddata = []
  var sum = sumArray(data)
  var multiplier = area / sum
  var i

  for (i = 0; i < data.length; i++) {
    normalizeddata[i] = data[i] * multiplier
  }
  return normalizeddata
}

// treemapSingledimensional - simple wrapper around squarify
export default function treemapSingledimensional(
  data,
  width,
  height,
  xoffset,
  yoffset
) {
  xoffset = typeof xoffset === 'undefined' ? 0 : xoffset
  yoffset = typeof yoffset === 'undefined' ? 0 : yoffset

  var rawtreemap = squarify(
    normalize(data, width * height),
    [],
    new Container(xoffset, yoffset, width, height),
    []
  )
  return flattenTreemap(rawtreemap)
}

// flattenTreemap - squarify implementation returns an array of arrays of coordinates
//                  because we have a new array everytime we switch to building a new row
//                  this converts it into an array of coordinates.
function flattenTreemap(rawtreemap) {
  var flattreemap = []
  var i, j

  for (i = 0; i < rawtreemap.length; i++) {
    for (j = 0; j < rawtreemap[i].length; j++) {
      flattreemap.push(rawtreemap[i][j])
    }
  }
  return flattreemap
}

// squarify  - as per the Bruls paper
//             plus coordinates stack and containers so we get
//             usable data out of it
function squarify(data, currentrow, container, stack) {
  var length
  var nextdatapoint
  var newcontainer

  if (data.length === 0) {
    stack.push(container.getCoordinates(currentrow))
    return
  }

  length = container.shortestEdge()
  nextdatapoint = data[0]

  if (improvesRatio(currentrow, nextdatapoint, length)) {
    currentrow.push(nextdatapoint)
    squarify(data.slice(1), currentrow, container, stack)
  } else {
    newcontainer = container.cutArea(sumArray(currentrow), stack)
    stack.push(container.getCoordinates(currentrow))
    squarify(data, [], newcontainer, stack)
  }
  return stack
}

// improveRatio - implements the worse calculation and comparision as given in Bruls
//                (note the error in the original paper; fixed here)
function improvesRatio(currentrow, nextnode, length) {
  var newrow

  if (currentrow.length === 0) {
    return true
  }

  newrow = currentrow.slice()
  newrow.push(nextnode)

  var currentratio = calculateRatio(currentrow, length)
  var newratio = calculateRatio(newrow, length)

  // the pseudocode in the Bruls paper has the direction of the comparison
  // wrong, this is the correct one.
  return currentratio >= newratio
}

// calculateRatio - calculates the maximum width to height ratio of the
//                  boxes in this row
function calculateRatio(row, length) {
  var min = Math.min.apply(Math, row)
  var max = Math.max.apply(Math, row)
  var sum = sumArray(row)
  return Math.max(
    (Math.pow(length, 2) * max) / Math.pow(sum, 2),
    Math.pow(sum, 2) / (Math.pow(length, 2) * min)
  )
}

// isArray - checks if arr is an array
function isArray(arr) {
  return arr && arr.constructor === Array
}

// sumArray - sums a single dimensional array
function sumArray(arr) {
  var sum = 0
  var i

  for (i = 0; i < arr.length; i++) {
    sum += arr[i]
  }
  return sum
}


================================================
FILE: client/components/Warning/Warning.scss
================================================
@import '../../../stylesheets/colors';
@import '../../../stylesheets/variables';

.warning-bar {
  @include font-size-xs;
  background: lighten($dandelion, 5%);
  padding: $global-spacing * 0.5 $global-spacing;
  border-radius: 4px;
  margin-top: 2vh;
  color: darken(desaturate($dandelion, 35%), 35%);

  a {
    @include font-size-xxs;
    color: inherit;
    font-weight: $font-weight-bold;
    opacity: 0.8;
    padding-left: $global-spacing;
    text-transform: uppercase;
  }
}


================================================
FILE: client/components/Warning/Warning.tsx
================================================
import React, { Component } from 'react'

class Warning extends Component<React.PropsWithChildren> {
  render() {
    return <div className="warning-bar">{this.props.children}</div>
  }
}

export default Warning


================================================
FILE: client/components/Warning/index.ts
================================================
export { default } from './Warning'


================================================
FILE: client/config/colors.ts
================================================
export default [
  '#718af0',
  '#6e98e6',
  '#79c0f2',
  '#7dd6fa',
  '#6ed0db',
  '#59b3aa',
  '#7ebf80',
  '#9bc26b',
  '#dee675',
  '#fff080',
  '#ffd966',
  '#ffbf66',
  '#ff8a66',
  '#ed7872',
  '#db6b8f',
  '#bd66cc',
  '#cae0eb',
] as const


================================================
FILE: client/config/scanBlacklist.ts
================================================
/**
 * Packages that are unlikely to be useful in
 * determining size of frontend bundles.
 */
export default [
  /*** Config ****/
  /dotenv/,

  /*** CLI Tools ***/
  /gulp/,
  /cli/,

  /*** Build Tools ****/
  /webpack/,
  /react-native/,
  /babel/,
  /rollup/,
  /autoprefixer/,
  /css-nano/,
  /node-sass/,
  /next/,
  /create-react-app/,
  /react-scripts/,
  /-loader/,
  /extract-plugin/,

  /**** Testing ****/
  /jest/,
  /enzyme/,
  /mocha/,
  /ava/,
  /nightwatch/,

  /**** Server libraries ****/
  /koa/,
  /express/,
  /pm2/,
  /nodemon/,
  /supervisor/,

  /**** Common dev dependencies ****/
  /prop-types/,
  /devtools/,
] as const


================================================
FILE: index.js
================================================
const { register } = require('esbuild-register/dist/node')
const tsconfig = require('./tsconfig.server.json')
register({
  tsconfigRaw: tsconfig,
  target: tsconfig.compilerOptions.target,
})
require('./index.ts')


================================================
FILE: index.ts
================================================
require('dotenv-defaults').config()

import next from 'next'
import exec from 'execa'
import { parse } from 'url'

import Koa, { Context } from 'koa'
import proxy from 'koa-proxy'
import serve from 'koa-static'
import Router from '@koa/router'
import compress from 'koa-compress'
import cacheControl from 'koa-cache-control'
import requestId from 'koa-requestid'
import auth from 'koa-basic-auth'
import bodyParser from 'koa-bodyparser'
import invariant from 'ts-invariant'

import Cache from './utils/cache.utils'
import { parsePackageString } from './utils/common.utils'
import firebaseUtils from './utils/firebase.utils'
import logger from './server/Logger'

import limit from './server/middlewares/rateLimit.middleware'
import exportsMiddlware from './server/middlewares/exports.middleware'
import exportsSizesMiddlware from './server/middlewares/exportsSizes.middleware'
import resolvePackageMiddleware from './server/middlewares/results/resolvePackage.middleware'
import cachedResponseMiddleware from './server/middlewares/results/cachedResponse.middleware'
import buildMiddleware from './server/middlewares/results/build.middleware'
import errorMiddleware from './server/middlewares/results/error.middleware'
import blockBlacklistMiddleware from './server/middlewares/results/blockBlacklist.middleware'
import requestLoggerMiddleware from './server/middlewares/requestLogger.middleware'
import similarPackagesMiddleware from './server/middlewares/similar-packages/similarPackages.middleware'
import generateImgMiddleware from './server/middlewares/generateImg.middleware'

import jsonCacheMiddleware from './server/middlewares/jsonCache.middleware'

import config from './server/config'

function getEnv(env: Record<string, string | undefined | null>) {
  invariant(
    env.BASIC_AUTH_PASSWORD,
    'Environment variable BASIC_AUTH_PASSWORD is required'
  )
  invariant(env.NODE_ENV, 'Environment variable NODE_ENV is required')

  return {
    basicAuthPassword: env.BASIC_AUTH_PASSWORD,
    port: env.PORT ? parseInt(env.PORT) : config.DEFAULT_DEV_PORT,
    nodeEnv: env.NODE_ENV,
  }
}

const env = getEnv(process.env)

const cache = new Cache()
const port = env.port
const dev = env.nodeEnv !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  const server = new Koa()
  const router = new Router()

  server.use(requestId())
  server.use(bodyParser())
  server.use(requestLoggerMiddleware)
  server.use(cacheControl())

  if (!dev) {
    server.use(
      limit({
        duration: 1000 * 60 * 5, //  5 mins
        max: 60,
        whiteList: ['127.0.0.1', '::1'],
      })
    )
  }

  server.use(async (ctx, next) => {
    try {
      await next()
    } catch (err: any) {
      if (401 == err.status) {
        ctx.status = 401
        ctx.set('WWW-Authenticate', 'Basic')
        ctx.body = 'Permission denied'
      } else {
        throw err
      }
    }
  })

  server.use(
    compress({
      filter: function (contentType) {
        return /(text|json|javascript|svg)/.test(contentType)
      },
      threshold: 2048,
      gzip: {
        flush: require('zlib').Z_SYNC_FLUSH,
      },
    })
  )

  server.use(
    serve('./client/assets/public', {
      maxage: config.CACHE.PUBLIC_ASSETS * 1000,
    })
  )

  server.use(
    proxy({
      match: /^\/-\/search/,
      host: 'https://www.npmjs.com',
    })
  )

  type Key = {
    name: string
    version: string
  }

  router.get(
    '/api/size',
    jsonCacheMiddleware({
      get: (key: Key) => cache.getPackageSize(key),
      set: (key: Key, value: string) => cache.setPackageSize(key, value),
      hash: (ctx: Context) => ({
        name: ctx.state.resolved.name,
        version: ctx.state.resolved.version,
      }),
    }),
    errorMiddleware,
    resolvePackageMiddleware,
    blockBlacklistMiddleware,
    cachedResponseMiddleware,
    buildMiddleware
  )

  router.get(
    '/api/exports',
    errorMiddleware,
    resolvePackageMiddleware,
    blockBlacklistMiddleware,
    exportsMiddlware
  )

  router.get(
    '/api/exports-sizes',
    jsonCacheMiddleware({
      get: (key: Key) => cache.getExportsSize(key),
      set: (key: Key, value: string) => cache.setExportsSize(key, value),
      hash: (ctx: Context) => ({
        name: ctx.state.resolved.name,
        version: ctx.state.resolved.version,
      }),
    }),
    errorMiddleware,
    resolvePackageMiddleware,
    blockBlacklistMiddleware,
    cachedResponseMiddleware,
    exportsSizesMiddlware
  )

  router.get('/api/recent', async ctx => {
    try {
      ctx.cacheControl = {
        maxAge: config.CACHE.RECENTS_API,
      }
      ctx.body = await firebaseUtils.getRecentSearches(Number(ctx.query.limit))
    } catch (err: any) {
      console.error('in /api/recent', err)
      logger.error('RECENT', err, 'RECENT FAILED: failed')
      ctx.status = 422
      ctx.body = { type: err.name, message: err.message }
    }
  })

  router.get('/api/package-history', async ctx => {
    const { name } = parsePackageString(ctx.query.package)
    try {
      ctx.cacheControl = {
        maxAge: config.CACHE.PACKAGE_HISTORY_API,
      }
      ctx.body = await firebaseUtils.getPackageHistory(
        name,
        Number(ctx.query.limit)
      )
    } catch (err: any) {
      console.error(err)
      logger.error(
        'HISTORY',
        err,
        'HISTORY FAILED: for package' + ctx.query.package
      )
      ctx.status = 422
      ctx.body = { type: err.name, message: err.message }
    }
  })

  router.get('/api/similar-packages', similarPackagesMiddleware)

  router.get('/api/stats-image', generateImgMiddleware)

  router.get(
    '/admin/restart',
    auth({ name: 'bundlephobia', pass: env.basicAuthPassword }),
    async (ctx, next) => {
      try {
        const { stdout, stderr } = await exec.command('pm2 reload all')
        ctx.body = 'Server restarted' + stdout
      } catch (err) {
        console.error('Failed to restart', err)
        ctx.status = 500
        ctx.body = err
      }
    }
  )

  router.post('/admin/restart', async ctx => {
    const { name, pass } = <{ name?: string; pass?: string }>ctx.request.body
    if (name !== 'bundlephobia' || pass !== env.basicAuthPassword) {
      console.error('Failed to restart')
      ctx.status = 500
      ctx.body = 'Failed to restart'
    } else {
      const { stdout, stderr } = await exec.command('pm2 reload all')
      ctx.body = 'Server restarted' + stdout
      console.error(stderr)
    }
  })

  router.get(
    '/admin/clear-cache',
    auth({ name: 'bundlephobia', pass: env.basicAuthPassword }),
    async (ctx, next) => {
      try {
        const { stdout } = await exec.command(
          'rm -rf /tmp/tmp-build/cache/_cacache /tmp/tmp-build/packages/'
        )
        ctx.body = 'Cache cleared' + stdout
      } catch (err) {
        console.error('Failed to clear cache', err)
        ctx.status = 500
        ctx.body = err
      }
    }
  )

  router.get('/result', async ctx => {
    invariant(ctx.query.p, 'p parameter is required')
    const packageString =
      typeof ctx.query.p === 'string' ? ctx.query.p : ctx.query.p.join('/')

    ctx.redirect(`/package/${packageString.trim()}`)
    ctx.status = 301
  })

  router.get('(.*)', async ctx => {
    invariant(ctx.req.url, 'url is missing')
    const parsedUrl = parse(ctx.req.url, true)
    await handle(ctx.req, ctx.res, parsedUrl)
    ctx.respond = false
  })

  server.use(async (ctx, next) => {
    ctx.res.statusCode = 200
    await next()
  })

  server.use(router.routes())
  server.listen(port, () => {
    console.log(`> Ready on http://localhost:${port}`)
  })
})


================================================
FILE: next.config.js
================================================
const path = require('path')

module.exports = {
  pageExtensions: ['page.js', 'page.tsx'],
  sassOptions: {
    includePaths: [path.join(__dirname, 'stylesheets')],
  },
  env: {
    RELEASE_DATE: new Date().toDateString(),
  },
  webpack(config) {
    config.module.rules.push({
      test: /\.svg$/i,
      issuer: /\.[jt]sx?$/,
      use: [
        {
          loader: '@svgr/webpack',
          options: {
            svgoConfig: {
              plugins: [
                {
                  name: 'removeViewBox',
                  active: false,
                },
              ],
            },
          },
        },
      ],
    })

    return config
  },
}


================================================
FILE: nodemon.json
================================================
{
  "exec": "ts-node --project tsconfig.server.json ./index.ts",
  "ext": "js ts"
}


================================================
FILE: package.json
================================================
{
  "name": "bundlephobia",
  "version": "1.0.1",
  "description": "Find the cost of adding new frontend dependencies",
  "main": "index.ts",
  "author": "Shubham Kanodia <shubham.kanodia10@gmail.com>",
  "license": "MIT",
  "private": true,
  "workspaces": [
    "build-service",
    "cache-service",
    "scripts"
  ],
  "engines": {
    "node": ">= 24.0.0",
    "npm": ">= 5.4.x"
  },
  "dependencies": {
    "@contentful/rich-text-react-renderer": "^15.17.0",
    "@contentful/rich-text-types": "^15.15.1",
    "@koa/router": "^12.0.1",
    "@next/font": "^13.5.6",
    "@types/koa__router": "^12.0.4",
    "animejs": "^3.2.2",
    "array-to-sentence": "^2.0.0",
    "axios": "^0.21.4",
    "balloon-css": "^0.4.0",
    "bfj": "^9.0.2",
    "big-json": "^3.2.0",
    "canvas": "^3.2.1",
    "classnames": "^2.5.1",
    "date-fns": "^2.30.0",
    "debounce": "^1.2.1",
    "debug": "^4.3.4",
    "dompurify": "^2.4.7",
    "dotenv": "^8.6.0",
    "dotenv-defaults": "^2.0.2",
    "esbuild": "^0.18.20",
    "esbuild-register": "^3.5.0",
    "execa": "^5.1.1",
    "fabric": "^6.0.0",
    "firebase": "^8.10.1",
    "firebase-admin": "^12.6.0",
    "flatten": "^1.0.3",
    "git-url-parse": "^13.1.1",
    "github": "^10.1.0",
    "got": "^9.6.0",
    "hot-shots": "^6.8.7",
    "ipchecker": "^0.0.2",
    "is-empty-object": "^1.1.1",
    "jsdom": "^24.0.0",
    "jsonstream": "^1.0.3",
    "koa": "^2.15.0",
    "koa-basic-auth": "^4.0.0",
    "koa-better-ratelimit": "^2.1.2",
    "koa-bodyparser": "^4.4.1",
    "koa-cache-control": "^2.0.0",
    "koa-cash": "3.0.4",
    "koa-compress": "^3.1.0",
    "koa-proxy": "^0.9.0",
    "koa-requestid": "^2.2.1",
    "koa-send": "^5.0.1",
    "koa-static": "^5.0.0",
    "lodash": "^4.17.21",
    "lodash.isequal": "^4.5.0",
    "lodash.sortby": "^4.7.0",
    "lru-cache": "^6.0.0",
    "memory-fs": "^0.5.0",
    "mkdir-promise": "^1.0.0",
    "natural": "^5.2.4",
    "next": "^13.5.6",
    "node-fetch": "^2.7.0",
    "normalize.css": "^8.0.1",
    "p-queue": "^3.2.0",
    "package-build-stats": "8.2.5",
    "pacote": "^11.3.5",
    "performance-now": "^2.1.0",
    "progress-stream": "^2.0.0",
    "promise-queue-plus": "^1.2.2",
    "promise.series": "^0.2.0",
    "query-string": "^7.1.3",
    "react": "18.2.0",
    "react-autocomplete": "^1.8.1",
    "react-contentful": "^2.0.31",
    "react-dom": "18.2.0",
    "react-dropzone": "^7.0.1",
    "react-flip-move": "^3.0.5",
    "react-sidebar": "^3.0.2",
    "remark": "^9.0.0",
    "sass": "^1.70.0",
    "semver": "^7.6.3",
    "stream-chain": "^3.3.2",
    "stream-json": "^1.8.0",
    "strip-markdown": "^4.2.0",
    "trending-github": "^1.2.0",
    "truncate": "^3.0.0",
    "ts-invariant": "^0.10.3",
    "uglifyjs-webpack-plugin": "^2.2.0",
    "unfetch": "^4.2.0",
    "webpack": "^4.47.0",
    "winston": "^3.11.0",
    "workerpool": "^6.5.1"
  },
  "scripts": {
    "dev": "DEBUG='bp:*' NODE_ENV=development nodemon --watch ./server --watch ./utils ./index.ts",
    "build": "NODE_ENV=production next build",
    "prebuild": "node bin/generate-sitemap.js",
    "prestart": "yarn build",
    "start": "DEBUG='bp:*' NODE_ENV=production node ./index.js",
    "prod": "yarn run build && yarn start",
    "test": "jest",
    "test:watch": "jest --watch",
    "prettier": "prettier --write '**/*.{html,js,json,css,scss,jsx,flow,md,yml,yaml}'",
    "lint": "next lint",
    "deploy": "git branch -D gh-pages && git checkout -b gh-pages && yarn run build && yarn run export && cp -R out/* . "
  },
  "husky": {
    "hooks": {
      "pre-commit": "next lint --fix && pretty-quick --staged"
    }
  },
  "prettier": {
    "semi": false,
    "arrowParens": "avoid",
    "singleQuote": true
  },
  "resolutions": {
    "canvas": "^3.2.1",
    "@types/react": "18.2.48"
  },
  "devDependencies": {
    "@svgr/webpack": "^6.5.1",
    "@swc/core": "^1.3.107",
    "@swc/helpers": "^0.5.3",
    "@types/animejs": "^3.1.12",
    "@types/debounce": "^1.2.4",
    "@types/jsonstream": "^0.8.33",
    "@types/koa": "^2.14.0",
    "@types/koa-basic-auth": "^2.0.6",
    "@types/koa-bodyparser": "^4.3.12",
    "@types/koa-cache-control": "^2.0.5",
    "@types/koa-cash": "^4.1.3",
    "@types/koa-compress": "^4.0.6",
    "@types/koa-proxy": "^1.0.7",
    "@types/koa-static": "^4.0.4",
    "@types/node": "^24.0.0",
    "@types/progress-stream": "^2",
    "@types/react": "18.2.48",
    "@types/react-autocomplete": "1.8.10",
    "@types/react-dom": "18.2.18",
    "@types/react-sidebar": "^3.0.4",
    "@types/semver": "^7.7.1",
    "@types/stream-json": "^1",
    "autoprefixer": "^9.8.8",
    "eslint": "^8.56.0",
    "eslint-config-next": "^13.5.6",
    "eslint-config-prettier": "^8.10.0",
    "glob": "^7.2.3",
    "husky": "^4.3.8",
    "jest": "^24.9.0",
    "nodemon": "^2.0.22",
    "postcss": "^8.4.33",
    "postcss-easy-import": "^3.0.0",
    "postcss-loader": "^7.3.4",
    "prettier": "2.8.8",
    "pretty-quick": "^3.3.1",
    "sass-loader": "^10.5.2",
    "sitemap": "^7.1.1",
    "ts-node": "^10.9.2",
    "typescript": "^4.9.5"
  },
  "packageManager": "yarn@4.0.2"
}


================================================
FILE: pages/_app.page.tsx
================================================
import React from 'react'
import Head from 'next/head'
import { AppProps } from 'next/app'
import '../stylesheets/index.scss'

function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Head>
        <meta
          name="viewport"
          content="width=device-width, initial-scale=
Download .txt
gitextract_j7l_cmv1/

├── .eslintrc.json
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── 1-package-build-failure---inaccurate-sizes.md
│   │   ├── 2-similar-package-suggestion.md
│   │   ├── 3-feature-request---improvement.md
│   │   └── 4-bug_report.md
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .travis.yml
├── .yarnrc.yml
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── __tests__/
│   ├── errors-cache.test.js
│   ├── stress-test.sh
│   └── utils.test.js
├── bin/
│   ├── generate-sitemap.js
│   ├── getResults.js
│   └── updateHistoricalData.js
├── build-service/
│   ├── .yarnrc.yml
│   ├── index.js
│   └── package.json
├── cache-service/
│   ├── .gitignore
│   ├── cache.utils.js
│   ├── index.js
│   ├── middlewares/
│   │   ├── exports-size.middleware.js
│   │   └── package-size.middleware.js
│   └── package.json
├── client/
│   ├── analytics.ts
│   ├── api.ts
│   ├── assets/
│   │   └── public/
│   │       ├── browserconfig.xml
│   │       ├── manifest.json
│   │       ├── open-search-description.xml
│   │       ├── robots.txt
│   │       └── sitemap.xml
│   ├── components/
│   │   ├── AnnouncementBanner/
│   │   │   ├── AnnouncementBanner.scss
│   │   │   ├── AnnouncementBanner.tsx
│   │   │   └── index.ts
│   │   ├── AutocompleteInput/
│   │   │   ├── AutocompleteInput.scss
│   │   │   ├── AutocompleteInput.tsx
│   │   │   ├── components/
│   │   │   │   └── SuggestionItem.tsx
│   │   │   ├── hooks/
│   │   │   │   ├── useAutocompleteInput.ts
│   │   │   │   └── useFontSize.ts
│   │   │   └── index.ts
│   │   ├── AutocompleteInputBox/
│   │   │   ├── AutocompleteInputBox.scss
│   │   │   ├── AutocompleteInputBox.tsx
│   │   │   └── index.ts
│   │   ├── BarGraph/
│   │   │   ├── BarGraph.scss
│   │   │   ├── BarGraph.tsx
│   │   │   └── index.ts
│   │   ├── BarVersion/
│   │   │   ├── BarVersion.scss
│   │   │   └── BarVersion.tsx
│   │   ├── BlogLayout/
│   │   │   ├── BlogLayout.scss
│   │   │   ├── BlogLayout.tsx
│   │   │   └── index.ts
│   │   ├── BuildProgressIndicator/
│   │   │   ├── BuildProgressIndicator.scss
│   │   │   ├── BuildProgressIndicator.tsx
│   │   │   └── index.ts
│   │   ├── Header/
│   │   │   ├── Header.tsx
│   │   │   └── index.ts
│   │   ├── Icons/
│   │   │   ├── SearchIcon.tsx
│   │   │   ├── SideEffectIcon.scss
│   │   │   ├── SideEffectIcon.tsx
│   │   │   ├── TreeShakeIcon.scss
│   │   │   └── TreeShakeIcon.tsx
│   │   ├── JumpingDots/
│   │   │   ├── JumpingDots.scss
│   │   │   ├── JumpingDots.tsx
│   │   │   └── index.ts
│   │   ├── Layout/
│   │   │   ├── Layout.scss
│   │   │   ├── Layout.tsx
│   │   │   └── index.ts
│   │   ├── MetaTags.tsx
│   │   ├── PageNav/
│   │   │   ├── PageNav.tsx
│   │   │   └── index.ts
│   │   ├── ProgressHex/
│   │   │   ├── ProgressHex.scss
│   │   │   ├── ProgressHex.tsx
│   │   │   ├── index.ts
│   │   │   └── progress-hex-timeline.ts
│   │   ├── QuickStatsBar/
│   │   │   ├── QuickStatsBar.scss
│   │   │   ├── QuickStatsBar.tsx
│   │   │   └── index.ts
│   │   ├── ResultLayout/
│   │   │   ├── ResultLayout.scss
│   │   │   ├── ResultLayout.tsx
│   │   │   └── index.ts
│   │   ├── Separator.tsx
│   │   ├── SimilarPackageCard/
│   │   │   ├── SimilarPackageCard.scss
│   │   │   ├── SimilarPackageCard.tsx
│   │   │   └── index.ts
│   │   ├── Stat/
│   │   │   ├── Stat.scss
│   │   │   ├── Stat.tsx
│   │   │   └── index.ts
│   │   ├── Treemap/
│   │   │   ├── Treemap.tsx
│   │   │   ├── TreemapSquare.tsx
│   │   │   ├── index.ts
│   │   │   └── squarify.js
│   │   └── Warning/
│   │       ├── Warning.scss
│   │       ├── Warning.tsx
│   │       └── index.ts
│   └── config/
│       ├── colors.ts
│       └── scanBlacklist.ts
├── index.js
├── index.ts
├── next.config.js
├── nodemon.json
├── package.json
├── pages/
│   ├── _app.page.tsx
│   ├── _document.page.tsx
│   ├── blog/
│   │   ├── components/
│   │   │   ├── Article.tsx
│   │   │   ├── ContentfulProvider.tsx
│   │   │   └── Post.tsx
│   │   ├── digital-ocean-partnership.page.tsx
│   │   └── index.page.tsx
│   ├── compare/
│   │   ├── ComparePage.js
│   │   ├── ComparePage.scss
│   │   └── index.js
│   ├── index.page.tsx
│   ├── index.scss
│   ├── package/
│   │   └── [...packageString]/
│   │       ├── ResultPage.js
│   │       ├── ResultPage.scss
│   │       ├── components/
│   │       │   ├── ExportAnalysisSection/
│   │       │   │   ├── ExportAnalysisSection.js
│   │       │   │   ├── ExportAnalysisSection.scss
│   │       │   │   └── index.js
│   │       │   ├── InterLinksSection/
│   │       │   │   ├── InterLinksSection.js
│   │       │   │   ├── InterLinksSection.scss
│   │       │   │   ├── InterLinksSectionCard/
│   │       │   │   │   ├── InterLinksSectionCard.js
│   │       │   │   │   ├── InterLinksSectionCard.scss
│   │       │   │   │   └── index.js
│   │       │   │   └── index.js
│   │       │   ├── SimilarPackagesSection/
│   │       │   │   ├── SimilarPackagesSection.js
│   │       │   │   ├── SimilarPackagesSection.scss
│   │       │   │   └── index.js
│   │       │   └── TreemapSection.js
│   │       └── index.page.js
│   ├── scan/
│   │   ├── Scan.js
│   │   ├── Scan.scss
│   │   └── index.page.js
│   └── scan-results/
│       ├── ScanResults.js
│       ├── ScanResults.scss
│       └── index.page.js
├── process.yml
├── scripts/
│   ├── README.md
│   ├── cleanup-old-keys.ts
│   ├── generate-top-packages.js
│   ├── package.json
│   └── populate-v3.js
├── server/
│   ├── CustomError.js
│   ├── Logger.js
│   ├── Queue.js
│   ├── api/
│   │   └── BuildService.js
│   ├── config.js
│   ├── data/
│   │   └── similar-packages/
│   │       ├── date-time.js
│   │       ├── index.js
│   │       ├── markdown.js
│   │       └── storage.js
│   ├── init.js
│   ├── middlewares/
│   │   ├── exports.middleware.js
│   │   ├── exportsSizes.middleware.js
│   │   ├── generateImg.middleware.js
│   │   ├── jsonCache.middleware.js
│   │   ├── rateLimit.middleware.js
│   │   ├── requestLogger.middleware.js
│   │   ├── results/
│   │   │   ├── blockBlacklist.middleware.js
│   │   │   ├── build.middleware.js
│   │   │   ├── cachedResponse.middleware.js
│   │   │   ├── error.middleware.js
│   │   │   ├── index.js
│   │   │   └── resolvePackage.middleware.js
│   │   └── similar-packages/
│   │       ├── fixtures.js
│   │       └── similarPackages.middleware.js
│   └── worker.js
├── stylesheets/
│   ├── base.scss
│   ├── colors.scss
│   ├── index.scss
│   ├── mixins.scss
│   └── variables.scss
├── test-packages/
│   ├── blacklist-error/
│   │   ├── index.js
│   │   └── package.json
│   ├── build-error/
│   │   ├── index.js
│   │   └── package.json
│   ├── entry-point-error/
│   │   ├── package.json
│   │   └── random-js-file.js
│   └── missing-dependency-error/
│       ├── index.js
│       └── package.json
├── tsconfig.json
├── tsconfig.server.json
├── types/
│   ├── amplitude.d.ts
│   ├── index.ts
│   └── react-contentful.d.ts
└── utils/
    ├── cache.utils.js
    ├── common.utils.js
    ├── draw.utils.js
    ├── firebase.utils.js
    ├── index.js
    ├── rebuild.utils.js
    └── server.utils.js
Download .txt
SYMBOL INDEX (349 symbols across 77 files)

FILE: bin/getResults.js
  function getFirebaseStoreFromDisk (line 14) | function getFirebaseStoreFromDisk() {
  function getFirebaseStoreFromNetwork (line 23) | async function getFirebaseStoreFromNetwork() {
  function getResults (line 79) | async function getResults() {
  function getPackages (line 89) | async function getPackages() {

FILE: bin/updateHistoricalData.js
  function getPackageFromRepo (line 35) | async function getPackageFromRepo(author, name) {
  function getGithubTrendingPackages (line 50) | async function getGithubTrendingPackages() {
  function getTrendingSearches (line 58) | async function getTrendingSearches() {
  function updateHistoricalData (line 75) | async function updateHistoricalData() {
  function getVersionsToBuild (line 91) | async function getVersionsToBuild(name) {
  function getVersionsToBuild (line 107) | async function getVersionsToBuild(name) {
  function buildPackage (line 123) | async function buildPackage(name, version) {
  function buildPackageFromGithub (line 132) | async function buildPackageFromGithub(name, author) {
  function mostPopuplarGithubRepos (line 147) | async function mostPopuplarGithubRepos() {

FILE: cache-service/cache.utils.js
  function encodeFirebaseKey (line 1) | function encodeFirebaseKey(key) {

FILE: cache-service/middlewares/exports-size.middleware.js
  constant LRU (line 2) | const LRU = require('lru-cache')
  constant FIREBASE_READ_KEY_EXPORTS (line 10) | const FIREBASE_READ_KEY_EXPORTS =
  constant FIREBASE_WRITE_KEY_EXPORTS (line 12) | const FIREBASE_WRITE_KEY_EXPORTS =
  function getPackageResultFromKey (line 22) | async function getPackageResultFromKey(key, { name, version }) {
  function getPackageResult (line 34) | async function getPackageResult({ name, version, readKey }) {
  function setPackageResult (line 63) | async function setPackageResult({ name, version, result }) {
  function getExportsSizeMiddlware (line 71) | async function getExportsSizeMiddlware(req, res) {
  function postExportsSizeMiddleware (line 102) | async function postExportsSizeMiddleware(req, res) {

FILE: cache-service/middlewares/package-size.middleware.js
  constant LRU (line 3) | const LRU = require('lru-cache')
  constant FIREBASE_READ_KEY (line 12) | const FIREBASE_READ_KEY = process.env.FIREBASE_READ_KEY || 'modules-v3'
  constant FIREBASE_WRITE_KEY (line 13) | const FIREBASE_WRITE_KEY = process.env.FIREBASE_WRITE_KEY || 'modules-v3'
  function getPackageResultFromKey (line 22) | async function getPackageResultFromKey(key, { name, version }) {
  function getPackageResult (line 34) | async function getPackageResult({ name, version, readKey }) {
  function setPackageResult (line 63) | async function setPackageResult({ name, version, result }) {
  function getPackageSizeMiddlware (line 71) | async function getPackageSizeMiddlware(req, res) {
  function postPackageSizeMiddlware (line 102) | async function postPackageSizeMiddlware(req, res) {

FILE: client/analytics.ts
  type HasPackageName (line 1) | type HasPackageName = {
  type HasTimeTaken (line 5) | type HasTimeTaken = {
  type HasIsDisabled (line 9) | type HasIsDisabled = {
  type HasSuccessRatio (line 13) | type HasSuccessRatio = {
  type HasPackageNameAndTimeTaken (line 17) | type HasPackageNameAndTimeTaken = HasPackageName & HasTimeTaken
  class Analytics (line 19) | class Analytics {
    method pageView (line 20) | static pageView(pageType: string) {
    method performedSearch (line 26) | static performedSearch(packageName: string) {
    method searchSuccess (line 32) | static searchSuccess({ packageName, timeTaken }: HasPackageNameAndTime...
    method searchFailure (line 39) | static searchFailure({ packageName, timeTaken }: HasPackageNameAndTime...
    method graphBarClicked (line 46) | static graphBarClicked({
    method scanPackageJsonDropped (line 56) | static scanPackageJsonDropped(itemCount: number) {
    method performedScan (line 62) | static performedScan() {
    method scanParseError (line 66) | static scanParseError() {
    method scanCompleted (line 70) | static scanCompleted({
    method performedExportsAnalysis (line 80) | static performedExportsAnalysis(packageName: string) {
    method exportsAnalysisSuccess (line 86) | static exportsAnalysisSuccess({
    method exportsAnalysisFailure (line 96) | static exportsAnalysisFailure({
    method exportsSizesSuccess (line 106) | static exportsSizesSuccess({
    method exportsSizesFailure (line 116) | static exportsSizesFailure({

FILE: client/api.ts
  type PackageSuggestion (line 3) | type PackageSuggestion = {
  type RecentSearch (line 8) | type RecentSearch = {
  class API (line 17) | class API {
    method get (line 18) | static get<T = unknown>(url: string, isInternal = true): Promise<T> {
    method getInfo (line 54) | static getInfo(packageString: string) {
    method getExports (line 58) | static getExports(packageString: string) {
    method getExportsSizes (line 62) | static getExportsSizes(packageString: string) {
    method getHistory (line 66) | static getHistory(packageString: string, limit: number) {
    method getRecentSearches (line 72) | static getRecentSearches(limit: number) {
    method getSimilar (line 76) | static getSimilar(packageName: string) {
    method getSuggestions (line 80) | static getSuggestions(query: string) {

FILE: client/components/AnnouncementBanner/AnnouncementBanner.tsx
  constant STORAGE_KEY (line 3) | const STORAGE_KEY = 'bundlephobia_rspack_banner_dismissed'
  constant EXPIRY_DATE (line 4) | const EXPIRY_DATE = new Date('2026-07-18T00:00:00Z') // 6 months from Ja...

FILE: client/components/AutocompleteInput/AutocompleteInput.tsx
  type AutocompleteInputProps (line 11) | type AutocompleteInputProps = {
  type PackageNameElementProps (line 122) | type PackageNameElementProps = React.HTMLAttributes<HTMLElement> & {
  function PackageNameElement (line 126) | function PackageNameElement({

FILE: client/components/AutocompleteInput/components/SuggestionItem.tsx
  type SuggestionItemProps (line 3) | interface SuggestionItemProps {
  function SuggestionItem (line 14) | function SuggestionItem({ item, isHighlighted }: SuggestionItemProps) {

FILE: client/components/AutocompleteInput/hooks/useAutocompleteInput.ts
  type UseAutocompleteInputArgs (line 7) | interface UseAutocompleteInputArgs {
  function useAutocompleteInput (line 12) | function useAutocompleteInput({

FILE: client/components/AutocompleteInput/hooks/useFontSize.ts
  function useFontSize (line 3) | function useFontSize({ value }: { value: string }) {

FILE: client/components/AutocompleteInputBox/AutocompleteInputBox.tsx
  type AutocompleteInputBoxProps (line 6) | type AutocompleteInputBoxProps = React.PropsWithChildren &
  class AutocompleteInputBox (line 11) | class AutocompleteInputBox extends Component<AutocompleteInputBoxProps> {
    method render (line 12) | render() {

FILE: client/components/BarGraph/BarGraph.tsx
  type Reading (line 8) | type Reading = {
  type BarGraphProps (line 19) | type BarGraphProps = {
  class BarGraph (line 24) | class BarGraph extends PureComponent<BarGraphProps> {
    method render (line 144) | render() {

FILE: client/components/BarVersion/BarVersion.tsx
  type Props (line 3) | interface Props {
  function BarVersion (line 7) | function BarVersion({ version }: Props) {

FILE: client/components/BlogLayout/BlogLayout.tsx
  type BlogLayoutProps (line 6) | type BlogLayoutProps = React.PropsWithChildren & WithClassName

FILE: client/components/BuildProgressIndicator/BuildProgressIndicator.tsx
  type BuildProgressIndicatorProps (line 7) | type BuildProgressIndicatorProps = {
  type BuildProgressIndicatorState (line 12) | type BuildProgressIndicatorState = {
  class BuildProgressIndicator (line 19) | class BuildProgressIndicator extends Component<
    method constructor (line 26) | constructor(props: BuildProgressIndicatorProps) {
    method componentDidMount (line 34) | componentDidMount() {
    method componentWillReceiveProps (line 43) | componentWillReceiveProps(nextProps: BuildProgressIndicatorProps) {
    method shouldComponentUpdate (line 50) | shouldComponentUpdate(
    method componentWillUnmount (line 57) | componentWillUnmount() {
    method render (line 97) | render() {

FILE: client/components/Header/Header.tsx
  type HeaderProps (line 8) | type HeaderProps = WithClassName
  type HeaderState (line 10) | type HeaderState = {
  class Header (line 15) | class Header extends Component<HeaderProps, HeaderState> {
    method constructor (line 18) | constructor(props: HeaderProps) {
    method componentDidMount (line 26) | componentDidMount() {
    method componentWillUnmount (line 32) | componentWillUnmount() {
    method onSetSidebarOpen (line 36) | onSetSidebarOpen(open: boolean) {
    method mediaQueryChanged (line 40) | mediaQueryChanged() {
    method render (line 44) | render() {

FILE: client/components/Icons/SearchIcon.tsx
  function SearchIcon (line 5) | function SearchIcon({ className }: WithClassName) {

FILE: client/components/Icons/SideEffectIcon.tsx
  function TreeShakeIcon (line 7) | function TreeShakeIcon({ className }: WithClassName) {

FILE: client/components/Icons/TreeShakeIcon.tsx
  function TreeShakeIcon (line 7) | function TreeShakeIcon({ className }: WithClassName) {

FILE: client/components/JumpingDots/JumpingDots.tsx
  function JumpingDots (line 3) | function JumpingDots() {

FILE: client/components/Layout/Layout.tsx
  type LayoutProps (line 10) | type LayoutProps = React.PropsWithChildren & WithClassName
  type LayoutState (line 12) | type LayoutState = {
  class Layout (line 16) | class Layout extends Component<LayoutProps, LayoutState> {
    method componentDidMount (line 21) | componentDidMount() {
    method render (line 29) | render() {

FILE: client/components/MetaTags.tsx
  constant DEFAULT_DESCRIPTION_START (line 4) | const DEFAULT_DESCRIPTION_START =
  type MetaTagsProps (line 7) | type MetaTagsProps = {
  function MetaTags (line 16) | function MetaTags({

FILE: client/components/PageNav/PageNav.tsx
  type PageNavProps (line 5) | type PageNavProps = {

FILE: client/components/ProgressHex/ProgressHex.tsx
  type ProgressHexProps (line 4) | type ProgressHexProps = {
  class ProgressHex (line 8) | class ProgressHex extends Component<ProgressHexProps> {
    method constructor (line 13) | constructor(props: ProgressHexProps) {
    method componentDidMount (line 18) | componentDidMount() {
    method componentWillUnmount (line 24) | componentWillUnmount() {
    method render (line 28) | render() {

FILE: client/components/ProgressHex/progress-hex-timeline.ts
  constant DURATION (line 6) | const DURATION = 1000
  type Circle (line 8) | type Circle = { cx: number; cy: number; ringNumber: number }
  type CirclesMap (line 10) | type CirclesMap = Map<SVGCircleElement, Circle>
  type ProgressHexAnimatorProps (line 12) | type ProgressHexAnimatorProps = {
  class ProgressHexAnimator (line 16) | class ProgressHexAnimator {
    method constructor (line 24) | constructor({ svg }: ProgressHexAnimatorProps) {
    method getTranslation (line 50) | getTranslation(circle: SVGCircleElement, distance: number) {
    method pointAtDistance (line 63) | pointAtDistance(x1: number, y1: number, x2: number, y2: number, d: num...
    method createTimeline (line 73) | createTimeline() {
  type TrailblazeProps (line 127) | type TrailblazeProps = {
  class Trailblaze (line 134) | class Trailblaze {
    method constructor (line 139) | constructor({ linesCount, svg, circlesMap, ringsCount }: TrailblazePro...
    method createTrail (line 150) | createTrail() {
    method setLineCoords (line 157) | setLineCoords(line: SVGLineElement, x1 = 0, x2 = 0, y1 = 0, y2 = 0) {
    method getCirclesInRing (line 164) | getCirclesInRing(ringNumber: number) {
    method distanceBetweenCircles (line 174) | distanceBetweenCircles(c1: Circle, c2: Circle) {
    method getRandomConnection (line 178) | getRandomConnection() {
    method start (line 224) | start() {

FILE: client/components/QuickStatsBar/QuickStatsBar.tsx
  type QuickStatsBarProps (line 12) | type QuickStatsBarProps = Pick<
  class QuickStatsBar (line 22) | class QuickStatsBar extends Component<QuickStatsBarProps> {
    method render (line 47) | render() {

FILE: client/components/ResultLayout/ResultLayout.tsx
  class ResultLayout (line 8) | class ResultLayout extends Component<
    method render (line 11) | render() {

FILE: client/components/Separator.tsx
  type SeparatorProps (line 3) | type SeparatorProps = {
  function Separator (line 10) | function Separator({

FILE: client/components/SimilarPackageCard/SimilarPackageCard.tsx
  type SimilarPackageCardProps (line 13) | type SimilarPackageCardProps = { category?: string } & (
  class SimilarPackageCard (line 18) | class SimilarPackageCard extends Component<SimilarPackageCardProps> {
    method render (line 29) | render() {

FILE: client/components/Stat/Stat.tsx
  type StatProps (line 12) | type StatProps = WithClassName & {
  function Stat (line 20) | function Stat({

FILE: client/components/Treemap/Treemap.tsx
  type TreeMapProps (line 5) | type TreeMapProps = {
  class TreeMap (line 11) | class TreeMap extends Component<TreeMapProps> {
    method render (line 12) | render() {

FILE: client/components/Treemap/TreemapSquare.tsx
  type TreemapSquareProps (line 3) | type TreemapSquareProps = {
  function TreemapSquare (line 12) | function TreemapSquare({

FILE: client/components/Treemap/squarify.js
  function Container (line 29) | function Container(xoffset, yoffset, width, height) {
  function normalize (line 105) | function normalize(data, area) {
  function treemapSingledimensional (line 118) | function treemapSingledimensional(
  function flattenTreemap (line 140) | function flattenTreemap(rawtreemap) {
  function squarify (line 155) | function squarify(data, currentrow, container, stack) {
  function improvesRatio (line 181) | function improvesRatio(currentrow, nextnode, length) {
  function calculateRatio (line 201) | function calculateRatio(row, length) {
  function isArray (line 212) | function isArray(arr) {
  function sumArray (line 217) | function sumArray(arr) {

FILE: client/components/Warning/Warning.tsx
  class Warning (line 3) | class Warning extends Component<React.PropsWithChildren> {
    method render (line 4) | render() {

FILE: index.ts
  function getEnv (line 39) | function getEnv(env: Record<string, string | undefined | null>) {
  type Key (line 119) | type Key = {

FILE: next.config.js
  method webpack (line 11) | webpack(config) {

FILE: pages/_app.page.tsx
  function App (line 6) | function App({ Component, pageProps }: AppProps) {

FILE: pages/_document.page.tsx
  class MyDocument (line 43) | class MyDocument extends Document {
    method getInitialProps (line 44) | static async getInitialProps(ctx: DocumentContext) {
    method render (line 49) | render() {

FILE: pages/blog/components/Post.tsx
  type PostProps (line 42) | type PostProps = {

FILE: pages/compare/ComparePage.js
  class ResultPage (line 18) | class ResultPage extends PureComponent {
    method render (line 114) | render() {

FILE: pages/package/[...packageString]/ResultPage.js
  class ResultPage (line 36) | class ResultPage extends PureComponent {
    method getPackageString (line 48) | getPackageString(router) {
    method componentDidMount (line 52) | componentDidMount() {
    method componentDidUpdate (line 61) | componentDidUpdate(prevProps) {
    method render (line 286) | render() {

FILE: pages/package/[...packageString]/components/ExportAnalysisSection/ExportAnalysisSection.js
  function getBGClass (line 17) | function getBGClass(ratio) {
  class ExportPill (line 35) | class ExportPill extends React.Component {
    method render (line 36) | render() {
  function ExportList (line 66) | function ExportList({ exports, totalSize, isLoading }) {
  function InputExportFilter (line 120) | function InputExportFilter({ onChange }) {
  class ExportAnalysisSection (line 134) | class ExportAnalysisSection extends Component {
    method componentDidMount (line 143) | componentDidMount() {
    method renderProgress (line 222) | renderProgress() {
    method getIncompatibleMessage (line 232) | getIncompatibleMessage() {
    method renderIncompatible (line 245) | renderIncompatible() {
    method renderSuccess (line 255) | renderSuccess() {
    method renderFailure (line 291) | renderFailure() {
    method render (line 304) | render() {

FILE: pages/package/[...packageString]/components/InterLinksSection/InterLinksSection.js
  function usePackagesFromSameScope (line 9) | function usePackagesFromSameScope(packageName) {

FILE: pages/package/[...packageString]/components/InterLinksSection/InterLinksSectionCard/InterLinksSectionCard.js
  function InterLinksSectionCard (line 6) | function InterLinksSectionCard(props) {

FILE: pages/package/[...packageString]/components/SimilarPackagesSection/SimilarPackagesSection.js
  class SimilarPackagesSection (line 4) | class SimilarPackagesSection extends Component {
    method render (line 5) | render() {

FILE: pages/package/[...packageString]/components/TreemapSection.js
  class TreemapSection (line 6) | class TreemapSection extends Component {
    method componentDidMount (line 12) | componentDidMount() {
    method render (line 38) | render() {

FILE: pages/scan-results/ScanResults.js
  class ResultCard (line 17) | class ResultCard extends Component {
    method render (line 18) | render() {
  class ScanResults (line 103) | class ScanResults extends Component {
    method constructor (line 104) | constructor(props) {
    method getInitialProps (line 125) | static async getInitialProps() {
    method componentDidMount (line 129) | componentDidMount() {
    method updatePackageState (line 182) | updatePackageState(pack, state) {
    method render (line 233) | render() {

FILE: pages/scan/Scan.js
  class Scan (line 11) | class Scan extends Component {
    method componentDidMount (line 17) | componentDidMount() {
    method showInvalidFileError (line 96) | showInvalidFileError() {
    method render (line 102) | render() {

FILE: scripts/cleanup-old-keys.ts
  type SearchesV2 (line 25) | interface SearchesV2 {
  function formatETA (line 35) | function formatETA(seconds: number): string {
  function processBackupFile (line 43) | async function processBackupFile(
  function uploadPrunedDataToFirebase (line 242) | async function uploadPrunedDataToFirebase(filePath: string) {

FILE: scripts/generate-top-packages.js
  constant SIX_MONTHS_MS (line 48) | const SIX_MONTHS_MS = 6 * 30 * 24 * 60 * 60 * 1000
  constant MIN_SEARCH_COUNT (line 49) | const MIN_SEARCH_COUNT = 2 // At least searched twice
  constant MAX_VERSIONS_PER_PACKAGE (line 50) | const MAX_VERSIONS_PER_PACKAGE = 20
  constant TOP_PACKAGES_LIMIT (line 51) | const TOP_PACKAGES_LIMIT = 1000 // Top N packages by search count
  constant CONCURRENCY (line 52) | const CONCURRENCY = 20 // Number of concurrent Firebase requests
  constant OUTPUT_PATH (line 53) | const OUTPUT_PATH = path.join(__dirname, '../top-packages.json')
  constant PROGRESS_PATH (line 54) | const PROGRESS_PATH = path.join(__dirname, '../top-packages-progress.json')
  function isValidStableVersion (line 59) | function isValidStableVersion(version) {
  function loadProgress (line 76) | function loadProgress() {
  function saveProgress (line 92) | function saveProgress(processedNames, results) {
  function processBatch (line 102) | async function processBatch(packages, processedNames, results, topPackag...
  function main (line 158) | async function main() {

FILE: scripts/populate-v3.js
  constant API_BASE (line 22) | const API_BASE = process.env.API_BASE || 'http://localhost:5000'
  constant CONCURRENCY (line 23) | const CONCURRENCY = parseInt(process.env.CONCURRENCY || '3', 10)
  constant TIMEOUT_MS (line 24) | const TIMEOUT_MS = 120000 // 120 seconds per request
  constant TOP_PACKAGES_PATH (line 25) | const TOP_PACKAGES_PATH = path.join(__dirname, '../top-packages.json')
  constant PROGRESS_PATH (line 26) | const PROGRESS_PATH = path.join(__dirname, '../populate-v3-progress.json')
  constant STATS_PATH (line 27) | const STATS_PATH = path.join(__dirname, '../populate-v3-stats.json')
  constant COMPARISON_PATH (line 28) | const COMPARISON_PATH = path.join(__dirname, '../populate-v3-comparison....
  constant EXPORTS_STATS_PATH (line 30) | const EXPORTS_STATS_PATH = path.join(
  constant EXPORTS_COMPARISON_PATH (line 34) | const EXPORTS_COMPARISON_PATH = path.join(
  constant CACHE_SERVICE_BASE (line 39) | const CACHE_SERVICE_BASE =
  function loadProgress (line 84) | function loadProgress() {
  function saveProgress (line 143) | function saveProgress(progress) {
  function saveStats (line 155) | function saveStats(stats) {
  function saveComparisons (line 160) | function saveComparisons(comparisons) {
  function saveExportsStats (line 165) | function saveExportsStats(stats) {
  function saveExportsComparisons (line 170) | function saveExportsComparisons(comparisons) {
  function withAbortableTimeout (line 178) | function withAbortableTimeout(promiseFactory, timeoutMs, timeoutValue) {
  function buildPackage (line 207) | async function buildPackage(packageName, version, signal = null) {
  function buildExports (line 256) | async function buildExports(packageName, version, signal = null) {
  function processBatch (line 305) | async function processBatch(
  function formatTime (line 454) | function formatTime(seconds) {
  function main (line 461) | async function main() {

FILE: server/Logger.js
  class Logger (line 31) | class Logger {
    method constructor (line 32) | constructor() {
    method info (line 44) | info(tag, json, message) {
    method error (line 54) | error(tag, json, message) {
    method increment (line 65) | increment(label) {
    method decrement (line 69) | decrement(label) {
    method histogram (line 73) | histogram(label, value) {
    method set (line 77) | set(label, value) {
    method timing (line 81) | timing(label, value) {

FILE: server/Queue.js
  class Queue (line 21) | class Queue {
    method constructor (line 22) | constructor(options) {
    method addExecutor (line 40) | addExecutor(jobType, handler) {
    method hasJob (line 44) | hasJob(id, type) {
    method getRunningJobs (line 48) | getRunningJobs() {
    method getReadyJobs (line 52) | getReadyJobs() {
    method pruneQueue (line 61) | pruneQueue() {
    method getNextJobToRun (line 85) | getNextJobToRun() {
    method ageJobs (line 103) | ageJobs() {
    method removeJob (line 115) | removeJob(id, type) {
    method clear (line 124) | clear() {
    method setJobToProcessing (line 139) | setJobToProcessing(id, type) {
    method executeNextJobIfPossible (line 152) | executeNextJobIfPossible() {
    method executeNextJob (line 169) | executeNextJob() {
    method addListenersToJob (line 201) | addListenersToJob(id, type, { resolve, reject }) {
    method process (line 218) | process(id, type, jobParams, options = {}) {

FILE: server/api/BuildService.js
  constant CONFIG (line 3) | const CONFIG = require('../config')
  class BuildService (line 14) | class BuildService {
    method constructor (line 15) | constructor() {
    method _handleError (line 56) | _handleError(error, operationType) {
    method getPackageBuildStats (line 84) | async getPackageBuildStats(packageString, priority) {
    method getPackageExports (line 93) | async getPackageExports(packageString, priority) {
    method getPackageExportSizes (line 102) | async getPackageExportSizes(packageString, priority) {

FILE: server/init.js
  constant LRU (line 2) | const LRU = require('lru-cache')

FILE: server/middlewares/exports.middleware.js
  constant CONFIG (line 2) | const CONFIG = require('../config')
  function exportsMiddleware (line 11) | async function exportsMiddleware(ctx) {

FILE: server/middlewares/exportsSizes.middleware.js
  constant CONFIG (line 2) | const CONFIG = require('../config')
  function exportSizesMiddleware (line 13) | async function exportSizesMiddleware(ctx) {

FILE: server/middlewares/generateImg.middleware.js
  function generateImgMiddleware (line 11) | async function generateImgMiddleware(ctx, next) {

FILE: server/middlewares/jsonCache.middleware.js
  function jsonCacheMiddleware (line 3) | function jsonCacheMiddleware({ get, set, hash: hashFn }) {

FILE: server/middlewares/requestLogger.middleware.js
  function requestLoggerMiddleware (line 4) | async function requestLoggerMiddleware(ctx, next) {

FILE: server/middlewares/results/blockBlacklist.middleware.js
  constant CONFIG (line 3) | const CONFIG = require('../../config')
  function blockBlacklistMiddleware (line 5) | async function blockBlacklistMiddleware(ctx, next) {

FILE: server/middlewares/results/build.middleware.js
  constant CONFIG (line 2) | const CONFIG = require('../../config')
  function buildMiddleware (line 14) | async function buildMiddleware(ctx, next) {

FILE: server/middlewares/results/cachedResponse.middleware.js
  constant CONFIG (line 2) | const CONFIG = require('../../config')
  function cachedResponse (line 6) | async function cachedResponse(ctx, next) {

FILE: server/middlewares/results/error.middleware.js
  constant CONFIG (line 4) | const CONFIG = require('../../config')
  function errorHandler (line 8) | async function errorHandler(ctx, next) {

FILE: server/middlewares/results/resolvePackage.middleware.js
  function resolvePackageMiddleware (line 7) | async function resolvePackageMiddleware(ctx, next) {

FILE: server/middlewares/similar-packages/similarPackages.middleware.js
  constant CONFIG (line 11) | const CONFIG = require('../../config')
  constant MIN_CUTOFF_SCORE (line 13) | const MIN_CUTOFF_SCORE = 12
  function getPackageDetails (line 27) | async function getPackageDetails(packageName) {
  function getReadme (line 50) | async function getReadme(repository) {
  function stripMarkdown (line 111) | async function stripMarkdown(readme) {
  function getScore (line 127) | function getScore(categoryTokens, packageTokens) {
  function getInCategoryMap (line 138) | function getInCategoryMap(packageName) {
  function getCategory (line 146) | async function getCategory(packageName) {
  function test (line 193) | async function test() {
  function similarPackagesMiddleware (line 214) | async function similarPackagesMiddleware(ctx) {

FILE: types/index.ts
  type PackageInfo (line 3) | type PackageInfo = {
  type WithClassName (line 12) | type WithClassName = Pick<React.HTMLAttributes<HTMLElement>, 'className'>

FILE: types/react-contentful.d.ts
  type Item (line 5) | interface Item {
  type Data (line 17) | interface Data {
  type TypedHookResponse (line 21) | interface TypedHookResponse {

FILE: utils/cache.utils.js
  constant API (line 6) | const API = axios.create({
  class Cache (line 11) | class Cache {
    method getPackageSize (line 12) | async getPackageSize({ name, version }) {
    method setPackageSize (line 23) | async setPackageSize({ name, version }, result) {
    method getExportsSize (line 41) | async getExportsSize({ name, version }) {
    method setExportsSize (line 52) | async setExportsSize({ name, version }, result) {

FILE: utils/common.utils.js
  function parsePackageString (line 6) | function parsePackageString(packageString) {
  function daysFromToday (line 38) | function daysFromToday(date) {
  function sanitizeHTML (line 46) | function sanitizeHTML(html) {

FILE: utils/draw.utils.js
  function drawStatsImg (line 4) | function drawStatsImg({

FILE: utils/firebase.utils.js
  constant FIREBASE_READ_KEY (line 9) | const FIREBASE_READ_KEY = process.env.FIREBASE_READ_KEY || 'modules-v2'
  class FirebaseUtils (line 21) | class FirebaseUtils {
    method constructor (line 22) | constructor(firebaseInstance, enable = true) {
    method setRecentSearch (line 28) | setRecentSearch(name, packageInfo) {
    method getPackageHistory (line 62) | async getPackageHistory(name, limit = 15) {
    method getRecentSearches (line 170) | getRecentSearches(limit = 10) {
    method getDailySearches (line 195) | async getDailySearches() {

FILE: utils/index.js
  function encodeFirebaseKey (line 3) | function encodeFirebaseKey(key) {
  function decodeFirebaseKey (line 7) | function decodeFirebaseKey(key) {
  function randomFromArray (line 57) | function randomFromArray(arr) {
  function zeroToN (line 61) | function zeroToN(n) {
  function resolveBuildError (line 65) | function resolveBuildError(resultsError) {

FILE: utils/rebuild.utils.js
  function commit (line 22) | function commit() {
  function encodeFirebaseKey (line 55) | function encodeFirebaseKey(key) {
  function decodeFirebaseKey (line 59) | function decodeFirebaseKey(key) {
  function getFirebaseStore (line 63) | async function getFirebaseStore() {
  function getPackageResult (line 73) | async function getPackageResult({ name, version }) {
  function filterBlacklistedPackages (line 85) | function filterBlacklistedPackages() {
  function trim (line 89) | async function trim(packages) {
  function run (line 112) | async function run() {
  function installPackage (line 254) | async function installPackage(packageName, installPath) {
  function exec (line 291) | function exec(command, options) {
  function getExports (line 303) | async function getExports(name, version) {
  function rebuildTopLevelExports (line 327) | async function rebuildTopLevelExports() {

FILE: utils/server.utils.js
  function resolvePackage (line 13) | async function resolvePackage(packageString) {
  function getRequestPriority (line 27) | function getRequestPriority(ctx) {
Condensed preview — 199 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (448K chars).
[
  {
    "path": ".eslintrc.json",
    "chars": 86,
    "preview": "{\n  \"extends\": [\"next\", \"prettier\"],\n  \"plugins\": [],\n  \"root\": true,\n  \"rules\": {}\n}\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 141,
    "preview": "# These are supported funding model platforms\n\ngithub: pastelsky\nopen_collective: bundlephobia\nko_fi: # Replace with a s"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-package-build-failure---inaccurate-sizes.md",
    "chars": 354,
    "preview": "---\nname: Package Build failure / inaccurate sizes\nabout: Unexpected failures that are not explained by the FAQ's page -"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2-similar-package-suggestion.md",
    "chars": 766,
    "preview": "---\nname: Similar package suggestion\nabout: Suggest a better alternative to a popular package\ntitle: 'Package suggestion"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/3-feature-request---improvement.md",
    "chars": 259,
    "preview": "---\nname: Feature request / Improvement\nabout: Suggest an idea for this project\ntitle: ''\nlabels: Improvement\nassignees:"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/4-bug_report.md",
    "chars": 166,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n---\n\n**Describe the b"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 883,
    "preview": "# This workflow will do a clean install of node dependencies, build the source code and run tests across different versi"
  },
  {
    "path": ".gitignore",
    "chars": 353,
    "preview": ".next\n.env\nbuild\nout\nnow.json\n\n# package directories\nnode_modules\n\n# Serverless directories\n.serverless\n\n# Logs\nyarn-err"
  },
  {
    "path": ".npmrc",
    "chars": 36,
    "preview": "registry=https://registry.npmjs.org\n"
  },
  {
    "path": ".nvmrc",
    "chars": 9,
    "preview": "v24.13.0\n"
  },
  {
    "path": ".prettierignore",
    "chars": 10,
    "preview": ".next\nbin\n"
  },
  {
    "path": ".travis.yml",
    "chars": 36,
    "preview": "language: node_js\nnode_js:\n  - '24'\n"
  },
  {
    "path": ".yarnrc.yml",
    "chars": 100,
    "preview": "enableInlineBuilds: true\n\nnodeLinker: node-modules\n\nnpmRegistryServer: 'https://registry.npmjs.org'\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1815,
    "preview": "Thanks for looking to help 👋. Have a nice time contributing to bundlephobia.\nIf you've any queries regarding setup or co"
  },
  {
    "path": "ISSUE_TEMPLATE.md",
    "chars": 117,
    "preview": "## Type\n\n<!-- Bug / Feature Request -->\n\n## Package name\n\n### Entire error (stringified) I see in my browser console\n"
  },
  {
    "path": "LICENSE",
    "chars": 1072,
    "preview": "MIT License\n\nCopyright (c) 2018 Shubham Kanodia\n\nPermission is hereby granted, free of charge, to any person obtaining a"
  },
  {
    "path": "README.md",
    "chars": 3288,
    "preview": "<p align=\"center\">\n    <img src=\"https://cdn.rawgit.com/pastelsky/bundlephobia/bundlephobia/client/assets/site-logo.svg\""
  },
  {
    "path": "__tests__/errors-cache.test.js",
    "chars": 4113,
    "preview": "const fetch = require('node-fetch')\n\nconst baseURL = 'http://127.0.0.1:5000/api/size?package='\n\ndescribe('build api', ()"
  },
  {
    "path": "__tests__/stress-test.sh",
    "chars": 2429,
    "preview": "(curl localhost:5000/api/size?package=react && echo '\\n') &\nsleep 3\n(curl localhost:5000/api/size?package=preact && echo"
  },
  {
    "path": "__tests__/utils.test.js",
    "chars": 1297,
    "preview": "import { parsePackageString } from '../utils/common.utils'\n\ndescribe('parsePackageString', () => {\n  it('handles scoped "
  },
  {
    "path": "bin/generate-sitemap.js",
    "chars": 3848,
    "preview": "const { SitemapStream, streamToPromise } = require( 'sitemap' )\nconst { Readable } = require( 'stream' )\nconst { writeFi"
  },
  {
    "path": "bin/getResults.js",
    "chars": 2532,
    "preview": "const firebase = require('firebase')\nconst { encodeFirebaseKey, decodeFirebaseKey } = require('../utils/index')\nconst fs"
  },
  {
    "path": "bin/updateHistoricalData.js",
    "chars": 4268,
    "preview": "#!/usr/bin/env node\n\nconst firebase = require('firebase')\nconst FirebaseUtils = require('../utils/firebase.utils')\nconst"
  },
  {
    "path": "build-service/.yarnrc.yml",
    "chars": 50,
    "preview": "compressionLevel: mixed\n\nenableGlobalCache: false\n"
  },
  {
    "path": "build-service/index.js",
    "chars": 1987,
    "preview": "import 'dotenv-defaults/config.js'\nimport Fastify from 'fastify'\nimport {\n  getPackageStats,\n  getAllPackageExports,\n  g"
  },
  {
    "path": "build-service/package.json",
    "chars": 452,
    "preview": "{\n  \"name\": \"build-service\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"depe"
  },
  {
    "path": "cache-service/.gitignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "cache-service/cache.utils.js",
    "chars": 131,
    "preview": "function encodeFirebaseKey(key) {\n  return key.replace(/[.]/g, ',').replace(/\\//g, '__')\n}\n\nmodule.exports = { encodeFir"
  },
  {
    "path": "cache-service/index.js",
    "chars": 970,
    "preview": "require('dotenv-defaults').config()\nconst firebase = require('firebase')\nconst fastify = require('fastify')()\nconst {\n  "
  },
  {
    "path": "cache-service/middlewares/exports-size.middleware.js",
    "chars": 3367,
    "preview": "require('dotenv-defaults').config()\nconst LRU = require('lru-cache')\nconst firebase = require('firebase')\nconst debug = "
  },
  {
    "path": "cache-service/middlewares/package-size.middleware.js",
    "chars": 3526,
    "preview": "require('dotenv-defaults').config()\nconst firebase = require('firebase')\nconst LRU = require('lru-cache')\nconst debug = "
  },
  {
    "path": "cache-service/package.json",
    "chars": 361,
    "preview": "{\n  \"name\": \"cache\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"debug\": \"^"
  },
  {
    "path": "client/analytics.ts",
    "chars": 2788,
    "preview": "type HasPackageName = {\n  packageName: string\n}\n\ntype HasTimeTaken = {\n  timeTaken: number\n}\n\ntype HasIsDisabled = {\n  i"
  },
  {
    "path": "client/api.ts",
    "chars": 3580,
    "preview": "import fetch from 'unfetch'\n\ntype PackageSuggestion = {\n  searchScore: number\n  score: { detail: { popularity: number } "
  },
  {
    "path": "client/assets/public/browserconfig.xml",
    "chars": 246,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo"
  },
  {
    "path": "client/assets/public/manifest.json",
    "chars": 381,
    "preview": "{\n  \"name\": \"Bundlephobia\",\n  \"icons\": [\n    {\n      \"src\": \"/android-chrome-192x192.png\",\n      \"sizes\": \"192x192\",\n   "
  },
  {
    "path": "client/assets/public/open-search-description.xml",
    "chars": 700,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\" xmlns:moz=\"ht"
  },
  {
    "path": "client/assets/public/robots.txt",
    "chars": 148,
    "preview": "# *\nUser-agent: *\nAllow: /\nDisallow: /scan-results\n\n# Host\nHost: https://bundlephobia.com\n\n# Sitemaps\nSitemap: https://b"
  },
  {
    "path": "client/assets/public/sitemap.xml",
    "chars": 28254,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:news=\"http://ww"
  },
  {
    "path": "client/components/AnnouncementBanner/AnnouncementBanner.scss",
    "chars": 1909,
    "preview": "@import '../../../stylesheets/colors';\n@import '../../../stylesheets/variables';\n\n.announcement-banner {\n  background: #"
  },
  {
    "path": "client/components/AnnouncementBanner/AnnouncementBanner.tsx",
    "chars": 1723,
    "preview": "import React, { useState, useEffect } from 'react'\n\nconst STORAGE_KEY = 'bundlephobia_rspack_banner_dismissed'\nconst EXP"
  },
  {
    "path": "client/components/AnnouncementBanner/index.ts",
    "chars": 105,
    "preview": "export { AnnouncementBanner } from './AnnouncementBanner'\nexport { default } from './AnnouncementBanner'\n"
  },
  {
    "path": "client/components/AutocompleteInput/AutocompleteInput.scss",
    "chars": 3599,
    "preview": "@use \"sass:math\";\n\n@import '../../../stylesheets/colors';\n@import '../../../stylesheets/variables';\n\n.autocomplete-input"
  },
  {
    "path": "client/components/AutocompleteInput/AutocompleteInput.tsx",
    "chars": 3808,
    "preview": "import React from 'react'\nimport cx from 'classnames'\nimport AutoComplete from 'react-autocomplete'\n\nimport SearchIcon f"
  },
  {
    "path": "client/components/AutocompleteInput/components/SuggestionItem.tsx",
    "chars": 763,
    "preview": "import cx from 'classnames'\n\ninterface SuggestionItemProps {\n  item: {\n    highlight: string | null\n    package: {\n     "
  },
  {
    "path": "client/components/AutocompleteInput/hooks/useAutocompleteInput.ts",
    "chars": 1360,
    "preview": "import React from 'react'\nimport debounce from 'debounce'\n\nimport { parsePackageString } from '../../../../utils/common."
  },
  {
    "path": "client/components/AutocompleteInput/hooks/useFontSize.ts",
    "chars": 558,
    "preview": "import React from 'react'\n\nexport function useFontSize({ value }: { value: string }) {\n  const searchFontSize = React.us"
  },
  {
    "path": "client/components/AutocompleteInput/index.ts",
    "chars": 56,
    "preview": "export { AutocompleteInput } from './AutocompleteInput'\n"
  },
  {
    "path": "client/components/AutocompleteInputBox/AutocompleteInputBox.scss",
    "chars": 650,
    "preview": "@import '../../../stylesheets/colors';\n@import '../../../stylesheets/variables';\n\n.autocomplete-input-box {\n  border: 1p"
  },
  {
    "path": "client/components/AutocompleteInputBox/AutocompleteInputBox.tsx",
    "chars": 636,
    "preview": "import React, { Component } from 'react'\nimport cx from 'classnames'\n\nimport { WithClassName } from '../../../types'\n\nty"
  },
  {
    "path": "client/components/AutocompleteInputBox/index.ts",
    "chars": 95,
    "preview": "import AutocompleteInputBox from './AutocompleteInputBox'\n\nexport default AutocompleteInputBox\n"
  },
  {
    "path": "client/components/BarGraph/BarGraph.scss",
    "chars": 2767,
    "preview": "@use \"sass:math\";\n\n@import '../../../stylesheets/colors';\n@import '../../../stylesheets/variables';\n\n$bar-width: 1.6vw;\n"
  },
  {
    "path": "client/components/BarGraph/BarGraph.tsx",
    "chars": 5388,
    "preview": "import React, { PureComponent } from 'react'\n\nimport { formatSize } from '../../../utils'\nimport TreeShakeIcon from '../"
  },
  {
    "path": "client/components/BarGraph/index.ts",
    "chars": 59,
    "preview": "import BarGraph from './BarGraph'\n\nexport default BarGraph\n"
  },
  {
    "path": "client/components/BarVersion/BarVersion.scss",
    "chars": 694,
    "preview": "@import '../../../stylesheets/colors';\n@import '../../../stylesheets/variables';\n\n.bar-graph__bar-version {\n  @include f"
  },
  {
    "path": "client/components/BarVersion/BarVersion.tsx",
    "chars": 279,
    "preview": "import truncate from 'truncate'\n\ninterface Props {\n  version: string\n}\n\nexport function BarVersion({ version }: Props) {"
  },
  {
    "path": "client/components/BlogLayout/BlogLayout.scss",
    "chars": 1242,
    "preview": "@import '../../../stylesheets/colors';\n@import '../../../stylesheets/variables';\n\n.page-container.blog {\n  .page-content"
  },
  {
    "path": "client/components/BlogLayout/BlogLayout.tsx",
    "chars": 396,
    "preview": "import React from 'react'\n\nimport { WithClassName } from '../../../types'\nimport ResultLayout from '../ResultLayout'\n\nty"
  },
  {
    "path": "client/components/BlogLayout/index.ts",
    "chars": 39,
    "preview": "export { default } from './BlogLayout'\n"
  },
  {
    "path": "client/components/BuildProgressIndicator/BuildProgressIndicator.scss",
    "chars": 507,
    "preview": "@import '../../../stylesheets/colors';\n@import '../../../stylesheets/variables';\n\n.build-progress-indicator {\n  display:"
  },
  {
    "path": "client/components/BuildProgressIndicator/BuildProgressIndicator.tsx",
    "chars": 2445,
    "preview": "import React, { Component } from 'react'\n\nimport ProgressHex from '../ProgressHex'\n\nconst OptimisticLoadTimeout = 700\n\nt"
  },
  {
    "path": "client/components/BuildProgressIndicator/index.ts",
    "chars": 101,
    "preview": "import BuildProgressIndicator from './BuildProgressIndicator'\n\nexport default BuildProgressIndicator\n"
  },
  {
    "path": "client/components/Header/Header.tsx",
    "chars": 2577,
    "preview": "import React, { Component } from 'react'\nimport Sidebar from 'react-sidebar'\nimport Link from 'next/link'\n\nimport { With"
  },
  {
    "path": "client/components/Header/index.ts",
    "chars": 53,
    "preview": "import Header from './Header'\n\nexport default Header\n"
  },
  {
    "path": "client/components/Icons/SearchIcon.tsx",
    "chars": 825,
    "preview": "import React from 'react'\n\nimport { WithClassName } from '../../../types'\n\nexport default function SearchIcon({ classNam"
  },
  {
    "path": "client/components/Icons/SideEffectIcon.scss",
    "chars": 606,
    "preview": "svg.sideeffect-icon-animated {\n  overflow: hidden;\n\n  .side-effect-icon-svg__circle,\n  .side-effect-icon-svg__arrows {\n "
  },
  {
    "path": "client/components/Icons/SideEffectIcon.tsx",
    "chars": 331,
    "preview": "import React from 'react'\nimport cx from 'classnames'\n\nimport { WithClassName } from '../../../types'\nimport SideEffectI"
  },
  {
    "path": "client/components/Icons/TreeShakeIcon.scss",
    "chars": 699,
    "preview": "svg.treeshake-icon-animated {\n  .tree-shake-icon-svg__bush {\n    transition: transform 0.3s;\n    transform-origin: 50% 1"
  },
  {
    "path": "client/components/Icons/TreeShakeIcon.tsx",
    "chars": 327,
    "preview": "import React from 'react'\nimport cx from 'classnames'\n\nimport { WithClassName } from '../../../types'\nimport TreeShakeIc"
  },
  {
    "path": "client/components/JumpingDots/JumpingDots.scss",
    "chars": 554,
    "preview": "@import '../../../stylesheets/variables';\n\n.jumping-dots {\n  position: relative;\n  text-align: center;\n  padding: 0 $glo"
  },
  {
    "path": "client/components/JumpingDots/JumpingDots.tsx",
    "chars": 267,
    "preview": "import React from 'react'\n\nexport default function JumpingDots() {\n  return (\n    <span className=\"jumping-dots\">\n      "
  },
  {
    "path": "client/components/JumpingDots/index.ts",
    "chars": 40,
    "preview": "export { default } from './JumpingDots'\n"
  },
  {
    "path": "client/components/Layout/Layout.scss",
    "chars": 4684,
    "preview": "@use \"sass:math\";\n\n@import '../../../stylesheets/variables';\n@import '../../../stylesheets/base';\n@import '../../../styl"
  },
  {
    "path": "client/components/Layout/Layout.tsx",
    "chars": 3409,
    "preview": "import React, { Component } from 'react'\nimport Link from 'next/link'\n\nimport API from '../../api'\nimport Heart from '.."
  },
  {
    "path": "client/components/Layout/index.ts",
    "chars": 53,
    "preview": "import Layout from './Layout'\n\nexport default Layout\n"
  },
  {
    "path": "client/components/MetaTags.tsx",
    "chars": 2011,
    "preview": "import React from 'react'\nimport Head from 'next/head'\n\nexport const DEFAULT_DESCRIPTION_START =\n  'Bundlephobia helps y"
  },
  {
    "path": "client/components/PageNav/PageNav.tsx",
    "chars": 1403,
    "preview": "import Link from 'next/link'\nimport React from 'react'\nimport GithubLogo from '../../assets/github-logo.svg'\n\ntype PageN"
  },
  {
    "path": "client/components/PageNav/index.ts",
    "chars": 36,
    "preview": "export { default } from './PageNav'\n"
  },
  {
    "path": "client/components/ProgressHex/ProgressHex.scss",
    "chars": 236,
    "preview": ".progress-hex {\n  width: 8rem;\n  height: 8rem;\n  contain: strict;\n  will-change: transform;\n\n  circle {\n    fill: #21212"
  },
  {
    "path": "client/components/ProgressHex/ProgressHex.tsx",
    "chars": 4221,
    "preview": "import React, { Component } from 'react'\nimport ProgressHexAnimator from './progress-hex-timeline'\n\ntype ProgressHexProp"
  },
  {
    "path": "client/components/ProgressHex/index.ts",
    "chars": 40,
    "preview": "export { default } from './ProgressHex'\n"
  },
  {
    "path": "client/components/ProgressHex/progress-hex-timeline.ts",
    "chars": 7128,
    "preview": "import anime, { AnimeAnimParams } from 'animejs'\n\nimport colors from '../../config/colors'\nimport { randomFromArray, zer"
  },
  {
    "path": "client/components/QuickStatsBar/QuickStatsBar.scss",
    "chars": 1914,
    "preview": "@use \"sass:math\";\n\n@import '../../../stylesheets/colors';\n@import '../../../stylesheets/variables';\n\n.quick-stats-bar {\n"
  },
  {
    "path": "client/components/QuickStatsBar/QuickStatsBar.tsx",
    "chars": 3862,
    "preview": "import React, { Component } from 'react'\n\nimport { sanitizeHTML } from '../../../utils/common.utils'\nimport TreeShakeIco"
  },
  {
    "path": "client/components/QuickStatsBar/index.ts",
    "chars": 74,
    "preview": "import QuickStatsBar from './QuickStatsBar'\n\nexport default QuickStatsBar\n"
  },
  {
    "path": "client/components/ResultLayout/ResultLayout.scss",
    "chars": 1778,
    "preview": "@import '../../../stylesheets/variables';\n@import '../../../stylesheets/colors';\n\n.page-header {\n  padding: $global-spac"
  },
  {
    "path": "client/components/ResultLayout/ResultLayout.tsx",
    "chars": 563,
    "preview": "import React, { Component } from 'react'\nimport cx from 'classnames'\n\nimport Layout from '../../components/Layout'\nimpor"
  },
  {
    "path": "client/components/ResultLayout/index.ts",
    "chars": 71,
    "preview": "import ResultLayout from './ResultLayout'\n\nexport default ResultLayout\n"
  },
  {
    "path": "client/components/Separator.tsx",
    "chars": 4822,
    "preview": "import React from 'react'\n\ntype SeparatorProps = {\n  text?: string\n  align?: React.CSSProperties['justifyContent']\n  sho"
  },
  {
    "path": "client/components/SimilarPackageCard/SimilarPackageCard.scss",
    "chars": 3257,
    "preview": "@use \"sass:math\";\n\n@import '../../../stylesheets/colors';\n@import '../../../stylesheets/variables';\n\n.similar-package-ca"
  },
  {
    "path": "client/components/SimilarPackageCard/SimilarPackageCard.tsx",
    "chars": 5010,
    "preview": "import React, { Component } from 'react'\nimport cx from 'classnames'\nimport Link from 'next/link'\nimport queryString fro"
  },
  {
    "path": "client/components/SimilarPackageCard/index.ts",
    "chars": 89,
    "preview": "import SimilarPackageCard from './SimilarPackageCard'\n\nexport default SimilarPackageCard\n"
  },
  {
    "path": "client/components/Stat/Stat.scss",
    "chars": 2150,
    "preview": "@import '../../../stylesheets/variables';\n@import '../../../stylesheets/colors';\n\n.stat-container {\n  margin: 0 24px;\n\n "
  },
  {
    "path": "client/components/Stat/Stat.tsx",
    "chars": 1676,
    "preview": "import React from 'react'\nimport cx from 'classnames'\n\nimport { formatSize, formatTime } from '../../../utils'\nimport { "
  },
  {
    "path": "client/components/Stat/index.ts",
    "chars": 33,
    "preview": "export { default } from './Stat'\n"
  },
  {
    "path": "client/components/Treemap/Treemap.tsx",
    "chars": 1963,
    "preview": "import React, { Component } from 'react'\n\nimport squarify from './squarify'\n\ntype TreeMapProps = {\n  width: number\n  hei"
  },
  {
    "path": "client/components/Treemap/TreemapSquare.tsx",
    "chars": 868,
    "preview": "import React from 'react'\n\ntype TreemapSquareProps = {\n  style: React.CSSProperties\n  data?: any\n} & React.PropsWithChil"
  },
  {
    "path": "client/components/Treemap/index.ts",
    "chars": 111,
    "preview": "import Treemap from './Treemap'\nimport TreemapSquare from './TreemapSquare'\n\nexport { Treemap, TreemapSquare }\n"
  },
  {
    "path": "client/components/Treemap/squarify.js",
    "chars": 6797,
    "preview": "/*\n * treemap-squarify.js - open source implementation of squarified treemaps\n *\n * Treemap Squared 0.5 - Treemap Charti"
  },
  {
    "path": "client/components/Warning/Warning.scss",
    "chars": 484,
    "preview": "@import '../../../stylesheets/colors';\n@import '../../../stylesheets/variables';\n\n.warning-bar {\n  @include font-size-xs"
  },
  {
    "path": "client/components/Warning/Warning.tsx",
    "chars": 212,
    "preview": "import React, { Component } from 'react'\n\nclass Warning extends Component<React.PropsWithChildren> {\n  render() {\n    re"
  },
  {
    "path": "client/components/Warning/index.ts",
    "chars": 36,
    "preview": "export { default } from './Warning'\n"
  },
  {
    "path": "client/config/colors.ts",
    "chars": 249,
    "preview": "export default [\n  '#718af0',\n  '#6e98e6',\n  '#79c0f2',\n  '#7dd6fa',\n  '#6ed0db',\n  '#59b3aa',\n  '#7ebf80',\n  '#9bc26b',"
  },
  {
    "path": "client/config/scanBlacklist.ts",
    "chars": 650,
    "preview": "/**\n * Packages that are unlikely to be useful in\n * determining size of frontend bundles.\n */\nexport default [\n  /*** C"
  },
  {
    "path": "index.js",
    "chars": 214,
    "preview": "const { register } = require('esbuild-register/dist/node')\nconst tsconfig = require('./tsconfig.server.json')\nregister({"
  },
  {
    "path": "index.ts",
    "chars": 7661,
    "preview": "require('dotenv-defaults').config()\n\nimport next from 'next'\nimport exec from 'execa'\nimport { parse } from 'url'\n\nimpor"
  },
  {
    "path": "next.config.js",
    "chars": 671,
    "preview": "const path = require('path')\n\nmodule.exports = {\n  pageExtensions: ['page.js', 'page.tsx'],\n  sassOptions: {\n    include"
  },
  {
    "path": "nodemon.json",
    "chars": 84,
    "preview": "{\n  \"exec\": \"ts-node --project tsconfig.server.json ./index.ts\",\n  \"ext\": \"js ts\"\n}\n"
  },
  {
    "path": "package.json",
    "chars": 5085,
    "preview": "{\n  \"name\": \"bundlephobia\",\n  \"version\": \"1.0.1\",\n  \"description\": \"Find the cost of adding new frontend dependencies\",\n"
  },
  {
    "path": "pages/_app.page.tsx",
    "chars": 496,
    "preview": "import React from 'react'\nimport Head from 'next/head'\nimport { AppProps } from 'next/app'\nimport '../stylesheets/index."
  },
  {
    "path": "pages/_document.page.tsx",
    "chars": 5593,
    "preview": "import React from 'react'\nimport Document, {\n  DocumentContext,\n  Head as DocumentHead,\n  Html,\n  Main,\n  NextScript,\n} "
  },
  {
    "path": "pages/blog/components/Article.tsx",
    "chars": 1152,
    "preview": "import { useRouter } from 'next/router'\nimport React from 'react'\nimport BlogLayout from '../../../client/components/Blo"
  },
  {
    "path": "pages/blog/components/ContentfulProvider.tsx",
    "chars": 790,
    "preview": "import React from 'react'\nimport {\n  ContentfulClient,\n  ContentfulClientInterface,\n  ContentfulProvider as ContentfulPr"
  },
  {
    "path": "pages/blog/components/Post.tsx",
    "chars": 2472,
    "preview": "import React from 'react'\nimport {\n  documentToReactComponents,\n  NodeRenderer,\n} from '@contentful/rich-text-react-rend"
  },
  {
    "path": "pages/blog/digital-ocean-partnership.page.tsx",
    "chars": 106,
    "preview": "import Article from './components/Article'\n\nconst ArticleIs = () => <Article />\n\nexport default ArticleIs\n"
  },
  {
    "path": "pages/blog/index.page.tsx",
    "chars": 1393,
    "preview": "import React from 'react'\nimport BlogLayout from '../../client/components/BlogLayout'\nimport { useContentful } from 'rea"
  },
  {
    "path": "pages/compare/ComparePage.js",
    "chars": 4833,
    "preview": "import React, { PureComponent } from 'react'\nimport Head from 'next/head'\n\nimport Layout from '../../client/components/L"
  },
  {
    "path": "pages/compare/ComparePage.scss",
    "chars": 1461,
    "preview": "@import '../../stylesheets/base';\n@import '../../stylesheets/variables';\n\n.result-header {\n  padding: $global-spacing * "
  },
  {
    "path": "pages/compare/index.js",
    "chars": 68,
    "preview": "import ComparePage from './ComparePage'\n\nexport default ComparePage\n"
  },
  {
    "path": "pages/index.page.tsx",
    "chars": 5196,
    "preview": "import React from 'react'\nimport { useRouter } from 'next/router'\nimport Link from 'next/link'\n\nimport Analytics from '."
  },
  {
    "path": "pages/index.scss",
    "chars": 2342,
    "preview": "@import '../stylesheets/base';\n@import '../stylesheets/variables';\n\n.homepage {\n  height: 100vh;\n  height: calc(100vh - "
  },
  {
    "path": "pages/package/[...packageString]/ResultPage.js",
    "chars": 15409,
    "preview": "import React, { PureComponent } from 'react'\nimport Analytics from '../../../client/analytics'\n\nimport ResultLayout from"
  },
  {
    "path": "pages/package/[...packageString]/ResultPage.scss",
    "chars": 5778,
    "preview": "@use \"sass:math\";\n\n@import '../../../stylesheets/colors';\n@import '../../../stylesheets/variables';\n\n.result-page__searc"
  },
  {
    "path": "pages/package/[...packageString]/components/ExportAnalysisSection/ExportAnalysisSection.js",
    "chars": 9396,
    "preview": "import React, { Component } from 'react'\nimport Analytics from '../../../../../client/analytics'\nimport cx from 'classna"
  },
  {
    "path": "pages/package/[...packageString]/components/ExportAnalysisSection/ExportAnalysisSection.scss",
    "chars": 4444,
    "preview": "@import '../../../../../stylesheets/variables';\n@import '../../../../../stylesheets/colors';\n@import '../../../../../sty"
  },
  {
    "path": "pages/package/[...packageString]/components/ExportAnalysisSection/index.js",
    "chars": 98,
    "preview": "import ExportAnalysisSection from './ExportAnalysisSection'\n\nexport default ExportAnalysisSection\n"
  },
  {
    "path": "pages/package/[...packageString]/components/InterLinksSection/InterLinksSection.js",
    "chars": 1830,
    "preview": "import React, { useEffect, useState } from 'react'\nimport {\n  parsePackageString,\n  daysFromToday,\n} from '../../../../."
  },
  {
    "path": "pages/package/[...packageString]/components/InterLinksSection/InterLinksSection.scss",
    "chars": 372,
    "preview": "@import '../../../../../stylesheets/variables';\n@import '../../../../../stylesheets/mixins';\n\n.interlinks-section {\n  wi"
  },
  {
    "path": "pages/package/[...packageString]/components/InterLinksSection/InterLinksSectionCard/InterLinksSectionCard.js",
    "chars": 869,
    "preview": "import { sanitizeHTML } from '../../../../../../utils/common.utils'\nimport Link from 'next/link'\nimport { formatDistance"
  },
  {
    "path": "pages/package/[...packageString]/components/InterLinksSection/InterLinksSectionCard/InterLinksSectionCard.scss",
    "chars": 1762,
    "preview": "@use \"sass:math\";\n\n@import '../../../../../../stylesheets/colors';\n@import '../../../../../../stylesheets/variables';\n\n."
  },
  {
    "path": "pages/package/[...packageString]/components/InterLinksSection/InterLinksSectionCard/index.js",
    "chars": 50,
    "preview": "export { default } from './InterLinksSectionCard'\n"
  },
  {
    "path": "pages/package/[...packageString]/components/InterLinksSection/index.js",
    "chars": 46,
    "preview": "export { default } from './InterLinksSection'\n"
  },
  {
    "path": "pages/package/[...packageString]/components/SimilarPackagesSection/SimilarPackagesSection.js",
    "chars": 1023,
    "preview": "import React, { Component } from 'react'\nimport SimilarPackageCard from '../../../../../client/components/SimilarPackage"
  },
  {
    "path": "pages/package/[...packageString]/components/SimilarPackagesSection/SimilarPackagesSection.scss",
    "chars": 420,
    "preview": "@import '../../../../../stylesheets/variables';\n@import '../../../../../stylesheets/colors';\n\n.similar-packages-section_"
  },
  {
    "path": "pages/package/[...packageString]/components/SimilarPackagesSection/index.js",
    "chars": 101,
    "preview": "import SimilarPackagesSection from './SimilarPackagesSection'\n\nexport default SimilarPackagesSection\n"
  },
  {
    "path": "pages/package/[...packageString]/components/TreemapSection.js",
    "chars": 5908,
    "preview": "import React, { Component } from 'react'\nimport { formatSize } from 'utils'\nimport colors from 'client/config/colors'\nim"
  },
  {
    "path": "pages/package/[...packageString]/index.page.js",
    "chars": 114,
    "preview": "import ResultPage from './ResultPage'\nexport { getServerSideProps } from './ResultPage'\nexport default ResultPage\n"
  },
  {
    "path": "pages/scan/Scan.js",
    "chars": 5294,
    "preview": "import React, { Component } from 'react'\nimport Analytics from '../../client/analytics'\nimport ResultLayout from '../../"
  },
  {
    "path": "pages/scan/Scan.scss",
    "chars": 1851,
    "preview": "@use \"sass:math\";\n\n@import '../../stylesheets/base';\n@import '../../stylesheets/variables';\n\n.scan__dropzone {\n  border:"
  },
  {
    "path": "pages/scan/index.page.js",
    "chars": 90,
    "preview": "import Scan from './Scan'\nexport { getServerSideProps } from './Scan'\nexport default Scan\n"
  },
  {
    "path": "pages/scan-results/ScanResults.js",
    "chars": 9178,
    "preview": "import Router, { withRouter } from 'next/router'\nimport React, { Component } from 'react'\nimport Analytics from '../../c"
  },
  {
    "path": "pages/scan-results/ScanResults.scss",
    "chars": 4013,
    "preview": "@import '../../stylesheets/base';\n@import '../../stylesheets/variables';\n\n.scan-results {\n  .page-content {\n    width: $"
  },
  {
    "path": "pages/scan-results/index.page.js",
    "chars": 68,
    "preview": "import ScanResults from './ScanResults'\n\nexport default ScanResults\n"
  },
  {
    "path": "process.yml",
    "chars": 1004,
    "preview": "apps:\n  - script: './index.js'\n    args: '--project tsconfig.server.json ./index.ts'\n    name: main\n    instances: 1\n   "
  },
  {
    "path": "scripts/README.md",
    "chars": 10,
    "preview": "# scripts\n"
  },
  {
    "path": "scripts/cleanup-old-keys.ts",
    "chars": 8729,
    "preview": "import * as fs from 'fs'\nimport * as path from 'path'\nimport * as admin from 'firebase-admin'\nimport * as semver from 's"
  },
  {
    "path": "scripts/generate-top-packages.js",
    "chars": 8306,
    "preview": "/**\n * Script to generate a list of top packages to pre-populate modules-v3\n *\n * This reads from Firebase:\n * - searche"
  },
  {
    "path": "scripts/package.json",
    "chars": 58,
    "preview": "{\n  \"name\": \"scripts\",\n  \"packageManager\": \"yarn@4.0.2\"\n}\n"
  },
  {
    "path": "scripts/populate-v3.js",
    "chars": 20575,
    "preview": "/**\n * Script to pre-populate modules-v3 by building top packages\n *\n * Prerequisites:\n * - Ensure services are running "
  },
  {
    "path": "server/CustomError.js",
    "chars": 304,
    "preview": "/**\n * Wraps the original error with a identifiable\n * name.\n */\n// Use ES6 supported by Node v6.10 only!\n\nmodule.export"
  },
  {
    "path": "server/Logger.js",
    "chars": 1717,
    "preview": "const winston = require('winston')\n// const StatsD = require('hot-shots')\n// const WinstonGraylog2 = require('winston-gr"
  },
  {
    "path": "server/Queue.js",
    "chars": 6461,
    "preview": "const log = require('debug')('bp:queue')\n\nconst Job = {\n  status: {\n    READY: Symbol('ready'),\n    PROCESSING: Symbol('"
  },
  {
    "path": "server/api/BuildService.js",
    "chars": 3278,
    "preview": "const axios = require('axios')\nconst { requestQueue } = require('../init')\nconst CONFIG = require('../config')\nconst { p"
  },
  {
    "path": "server/config.js",
    "chars": 1233,
    "preview": "// Use ES6 supported by Node v6.10 only!\n\nconst path = require('path')\nconst dev = false //process.env.NODE_ENV === 'dev"
  },
  {
    "path": "server/data/similar-packages/date-time.js",
    "chars": 96,
    "preview": "// Ununsed file\n\nexport default [[{ name: 'moment' }, { name: 'luxon' }, { name: 'date-fns' }]]\n"
  },
  {
    "path": "server/data/similar-packages/index.js",
    "chars": 197,
    "preview": "// Ununsed file\n\nimport dateTimeList from './date-time'\nimport markdownList from './markdown'\nimport storageList from '."
  },
  {
    "path": "server/data/similar-packages/markdown.js",
    "chars": 16,
    "preview": "// Ununsed file\n"
  },
  {
    "path": "server/data/similar-packages/storage.js",
    "chars": 16,
    "preview": "// Ununsed file\n"
  },
  {
    "path": "server/init.js",
    "chars": 638,
    "preview": "const config = require('./config')\nconst LRU = require('lru-cache')\nconst workerpool = require('workerpool')\nconst Queue"
  },
  {
    "path": "server/middlewares/exports.middleware.js",
    "chars": 1228,
    "preview": "const semver = require('semver')\nconst CONFIG = require('../config')\nconst now = require('performance-now')\nconst logger"
  },
  {
    "path": "server/middlewares/exportsSizes.middleware.js",
    "chars": 1497,
    "preview": "const semver = require('semver')\nconst CONFIG = require('../config')\nconst now = require('performance-now')\nconst logger"
  },
  {
    "path": "server/middlewares/generateImg.middleware.js",
    "chars": 1217,
    "preview": "const { drawStatsImg } = require('../../utils/draw.utils')\nconst Cache = require('../../utils/cache.utils')\nconst send ="
  },
  {
    "path": "server/middlewares/jsonCache.middleware.js",
    "chars": 530,
    "preview": "const koaCache = require('koa-cash')\n\nfunction jsonCacheMiddleware({ get, set, hash: hashFn }) {\n  return koaCache({\n   "
  },
  {
    "path": "server/middlewares/rateLimit.middleware.js",
    "chars": 2347,
    "preview": "// Modified package `koa-better-ratelimit`\n// to accept original client ips from cloudflare\n\nconst ipchecker = require('"
  },
  {
    "path": "server/middlewares/requestLogger.middleware.js",
    "chars": 952,
    "preview": "const logger = require('../Logger')\nconst now = require('performance-now')\n\nasync function requestLoggerMiddleware(ctx, "
  },
  {
    "path": "server/middlewares/results/blockBlacklist.middleware.js",
    "chars": 970,
    "preview": "const { parsePackageString } = require('../../../utils/common.utils')\nconst CustomError = require('./../../CustomError')"
  },
  {
    "path": "server/middlewares/results/build.middleware.js",
    "chars": 1708,
    "preview": "const semver = require('semver')\nconst CONFIG = require('../../config')\nconst firebaseUtils = require('../../../utils/fi"
  },
  {
    "path": "server/middlewares/results/cachedResponse.middleware.js",
    "chars": 1523,
    "preview": "const semver = require('semver')\nconst CONFIG = require('../../config')\nconst { failureCache, debug } = require('../../i"
  },
  {
    "path": "server/middlewares/results/error.middleware.js",
    "chars": 6070,
    "preview": "const arrayToSentence = require('array-to-sentence')\nconst now = require('performance-now')\nconst { failureCache } = req"
  },
  {
    "path": "server/middlewares/results/index.js",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "server/middlewares/results/resolvePackage.middleware.js",
    "chars": 1706,
    "preview": "const { resolvePackage } = require('../../../utils/server.utils')\nconst { parsePackageString } = require('../../../utils"
  },
  {
    "path": "server/middlewares/similar-packages/fixtures.js",
    "chars": 19824,
    "preview": "const Weight = {\n  SMALL: 1,\n  MID: 3,\n  NORMAL: 5,\n  HIGH: 7,\n  MAX: 15,\n}\n\nconst categories = {\n  'classname-strings':"
  },
  {
    "path": "server/middlewares/similar-packages/similarPackages.middleware.js",
    "chars": 7198,
    "preview": "const got = require('got')\nconst remark = require('remark')\nconst strip = require('strip-markdown')\nconst natural = requ"
  },
  {
    "path": "server/worker.js",
    "chars": 293,
    "preview": "const workerpool = require('workerpool')\nconst {\n  getPackageStats,\n  getAllPackageExports,\n  getPackageExportSizes,\n} ="
  },
  {
    "path": "stylesheets/base.scss",
    "chars": 1548,
    "preview": "@import './colors';\n@import './variables';\n@import '../node_modules/normalize.css/normalize';\n@import '../node_modules/b"
  },
  {
    "path": "stylesheets/colors.scss",
    "chars": 332,
    "preview": "//  Use http://www.htmlcsscolor.com to name colors\n$gulf-blue: #2f3f5f;\n$dark-gulf-blue: #152231;\n$neptune: #82b5b3;\n$co"
  },
  {
    "path": "stylesheets/index.scss",
    "chars": 1762,
    "preview": "// pages\n@import '../pages/compare/ComparePage.scss';\n@import '../pages/package/[...packageString]/components/ExportAnal"
  },
  {
    "path": "stylesheets/mixins.scss",
    "chars": 1062,
    "preview": "@mixin horizontal-scroll-overflow-indicators {\n  overflow-y: scroll;\n  height: 100%;\n  background-image: /* Shadows */ l"
  },
  {
    "path": "stylesheets/variables.scss",
    "chars": 2131,
    "preview": "// Font related\n@mixin font-size-xxxl {\n  font-size: 5.5rem;\n\n  @media screen and (max-width: 48em) {\n    font-size: 4re"
  },
  {
    "path": "test-packages/blacklist-error/index.js",
    "chars": 63,
    "preview": "console.log(\"I'm not a blacklisted package, hence will throw\")\n"
  },
  {
    "path": "test-packages/blacklist-error/package.json",
    "chars": 117,
    "preview": "{\n  \"name\": \"@bundlephobia/test-hack-unlimited-2018\",\n  \"version\": \"0.0.3\",\n  \"license\": \"MIT\",\n  \"private\": false\n}\n"
  },
  {
    "path": "test-packages/build-error/index.js",
    "chars": 63,
    "preview": "#!/usr/bin/node node\nconsole.log('This is a cli shell script')\n"
  },
  {
    "path": "test-packages/build-error/package.json",
    "chars": 109,
    "preview": "{\n  \"name\": \"@bundlephobia/test-build-error\",\n  \"version\": \"0.0.2\",\n  \"license\": \"MIT\",\n  \"private\": false\n}\n"
  },
  {
    "path": "test-packages/entry-point-error/package.json",
    "chars": 115,
    "preview": "{\n  \"name\": \"@bundlephobia/test-entry-point-error\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"private\": false\n}\n"
  },
  {
    "path": "test-packages/entry-point-error/random-js-file.js",
    "chars": 56,
    "preview": "console.log(\"I'm not an entry point, hence will throw\")\n"
  },
  {
    "path": "test-packages/missing-dependency-error/index.js",
    "chars": 83,
    "preview": "const test = require('missing-package')\nconsole.log('I have missing dependencies')\n"
  },
  {
    "path": "test-packages/missing-dependency-error/package.json",
    "chars": 117,
    "preview": "{\n  \"name\": \"@bundlephobia/missing-dependency-error\",\n  \"version\": \"0.0.1\",\n  \"license\": \"MIT\",\n  \"private\": false\n}\n"
  },
  {
    "path": "tsconfig.json",
    "chars": 509,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"sk"
  },
  {
    "path": "tsconfig.server.json",
    "chars": 148,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"target\": \"es2017\"\n  },\n  \"ts-nod"
  },
  {
    "path": "types/amplitude.d.ts",
    "chars": 216,
    "preview": "// stub typings for amplitudeScript loaded in the _document\ndeclare global {\n  var amplitude: {\n    getInstance: () => {"
  },
  {
    "path": "types/index.ts",
    "chars": 287,
    "preview": "import React from 'react'\n\nexport type PackageInfo = {\n  name: string\n  description: string\n  repository: string\n  depen"
  },
  {
    "path": "types/react-contentful.d.ts",
    "chars": 543,
    "preview": "import { HookQueryProps } from 'react-contentful'\nimport { Document } from '@contentful/rich-text-types'\n\ndeclare module"
  },
  {
    "path": "utils/cache.utils.js",
    "chars": 1698,
    "preview": "require('dotenv-defaults').config()\nconst debug = require('debug')('bp:cache')\nconst axios = require('axios')\nconst logg"
  },
  {
    "path": "utils/common.utils.js",
    "chars": 1336,
    "preview": "// Used by the server as well as the client\n// Use ES5 only\n\nconst DOMPurify = require('dompurify')\n\nfunction parsePacka"
  },
  {
    "path": "utils/draw.utils.js",
    "chars": 6214,
    "preview": "const fabric = require('fabric/node')\nconst { formatSize, formatTime, getTimeFromSize } = require('./index')\n\nfunction d"
  },
  {
    "path": "utils/firebase.utils.js",
    "chars": 6190,
    "preview": "const { decodeFirebaseKey, encodeFirebaseKey } = require('./index')\nconst semver = require('semver')\nconst fetch = requi"
  },
  {
    "path": "utils/index.js",
    "chars": 2199,
    "preview": "// Firebase does not accept a\n// few special characters for keys\nfunction encodeFirebaseKey(key) {\n  return key.replace("
  },
  {
    "path": "utils/rebuild.utils.js",
    "chars": 10023,
    "preview": "const { blackList } = require('../server/config')\n\nrequire('dotenv-defaults').config()\nconst firebase = require('firebas"
  },
  {
    "path": "utils/server.utils.js",
    "chars": 1129,
    "preview": "require('dotenv-defaults').config()\nconst semver = require('semver')\nconst axios = require('axios')\nconst fetch = requir"
  }
]

About this extraction

This page contains the full source code of the pastelsky/bundlephobia GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 199 files (406.0 KB), approximately 116.4k tokens, and a symbol index with 349 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!