Projects STRLCPY gradejs Commits 13e47ae0
🤬
  • feat: Implement server-side rendering (#43)

    * feat: Implement server-side rendering
    
    * chore: setup for elastic beanstalk
    
    * feat: Rebuild icons
    
    * chore: reconfigure deploy scripts for ebs
    
    * feat: pass required environment from server to client bundle
    
    * fix: update settings and readme
    
    * fix: plausible redirect & confighooks
    
    * fix: update vercel config
    
    * fix: review fixes
    
    * fix: review
  • Loading...
  • Oleg Klimenko committed with GitHub 2 years ago
    13e47ae0
    1 parent a21f5e09
  • ■ ■ ■ ■ ■ ■
    .eslintignore
    1 1  .eslintrc.js
    2 2  index.d.ts
    3  -webpack.config.js
    4 3  node_modules/
    5 4  packages/*/build
    6 5  packages/*/dist
    7 6   
    8  - 
  • ■ ■ ■ ■ ■
    .gitignore
    skipped 13 lines
    14 14   
    15 15  # misc
    16 16  .DS_Store
     17 +.log
    17 18   
    18 19  npm-debug.log*
    19 20  yarn-debug.log*
    skipped 4 lines
  • ■ ■ ■ ■ ■ ■
    .platform/confighooks/prebuild/01_extract_artifacts.sh
     1 +#!/bin/bash
     2 + 
     3 +bash `dirname "$0"`/../../hooks/prebuild/01_extract_artifacts.sh
     4 + 
  • ■ ■ ■ ■ ■
    .platform/hooks/prebuild/01_extract_artifacts.sh
     1 +#!/bin/bash
     2 + 
     3 +tar xf build.tar
     4 + 
     5 + 
  • ■ ■ ■ ■ ■ ■
    .platform/nginx/conf.d/elasticbeanstalk/001_plausible_redirect.conf
     1 +location = /api/event {
     2 + proxy_pass https://plausible.io/api/event;
     3 + proxy_buffering on;
     4 + proxy_http_version 1.1;
     5 + 
     6 + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     7 + proxy_set_header X-Forwarded-Proto $scheme;
     8 + proxy_set_header X-Forwarded-Host $host;
     9 +}
     10 + 
  • ■ ■ ■ ■ ■
    CONTRIBUTING.md
    skipped 57 lines
    58 58   
    59 59  - Open the [AWS RDS](https://us-east-2.console.aws.amazon.com/rds/home) page.
    60 60  - In the database list click on the `Create Database` button.
    61  -- Select the following parameters: Standart create, Amazon Aurora PostgreSQL-Compatible Edition version 13, Production template.
     61 +- Select the following parameters: Standard create, Amazon Aurora PostgreSQL-Compatible Edition version 13, Production template.
    62 62  - Enter the database name, for example `gradejs-public`.
    63 63  - Select a burstable DB instance class: `db.t3.medium`
    64 64  - Select `Don't create an Aurora Replica` option.
    skipped 9 lines
    74 74  4. Create [Amazon SQS](https://us-east-2.console.aws.amazon.com/sqs/v2/home) worker queues:
    75 75   
    76 76  - Open the page and click on the `Create queue` button.
    77  -- Select the standart queue option and specify the name `gradejs-public`.
     77 +- Select the standard queue option and specify the name `gradejs-public`.
    78 78  - Set the `visibility timeout` to 2 minutes.
    79 79  - Click on the `Create queue` button.
    80 80   
    skipped 2 lines
    83 83  - Open the [AWS Elastic Beanstalk](https://us-east-2.console.aws.amazon.com/elasticbeanstalk/home) page and click on the `Create a new environment` button.
    84 84  - Select `Web server environment` option.
    85 85  - Type the newly created application name (`gradejs`).
    86  -- Specify an environment name, for example `gradejs-public-api`. Leave the domain and description name fileds blank.
     86 +- Specify an environment name, for example `gradejs-public-api`. Leave the domain and description name fields blank.
    87 87  - Select the latest version of Node.js 16 managed platform on Amazon Linux 2. The version minimal required version is 5.5.1.
    88 88  - Use the sample application code and click on the `Configure more options` button.
    89 89  - Select the `Custom configuration` configuration preset.
    90 90  - In the `Software` section click on the `Edit` button.
    91 91  - Enable the `CloudWatch` log streaming with a 5 day retention.
    92  -- Specify the `AWS_REGION` environment variable. It should be the same as your current AWS environemnt. For example, `us-east-2`.
     92 +- Specify the `AWS_REGION` environment variable. It should be the same as your current AWS environment. For example, `us-east-2`.
    93 93  - Specify the `DB_URL` environment variable. It should point to the newly created RDS database. For example: `postgres://postgres:<secret>@<hostname>/gradejs`.
    94 94  - Specify the `INTERNAL_API_ORIGIN` environment variable. Consider using our staging internal API origin: `http://fpjs-dev-gradejs-internal-api.eba-fybi4md5.us-east-1.elasticbeanstalk.com`
    95 95  - Specify the `SQS_WORKER_QUEUE_URL` queue. It should be a newly created `gradejs-backend` queue. For example `https://sqs.us-east-2.amazonaws.com/<account_id>/gradejs-backend`.
    96  -- Specify the `AWS_REGION` environment variable. It should be the same as your current AWS environemnt. For example, `us-east-2`.
     96 +- Specify the `CORS_ALLOWED_ORIGIN` variable. Use comma-separated URLs your API will be talking to, for example: `http://localhost:3000,https://staging.gradejs.com`.
    97 97  - Specify the `EB_START` environment variable: `api`.
    98 98  - Click on the `Save` button and then click on the `Create environment` button.
    99 99   
    skipped 1 lines
    101 101   
    102 102  - Select `Worker environment` option.
    103 103  - Type the newly created application name (`gradejs-public`).
    104  -- Specify an environment name: `gradejs-public-worker`. Leave the domain and description name fileds blank.
     104 +- Specify an environment name: `gradejs-public-worker`. Leave the domain and description name fields blank.
    105 105  - Select the latest version of Node.js 16 managed platform on Amazon Linux 2. The version minimal required version is 5.5.1.
    106 106  - Use the sample application code and click on the `Configure more options` button.
    107 107  - Select the `Custom configuration` configuration preset.
    skipped 3 lines
    111 111  - Set the `EB_START` environment variable to `worker`.
    112 112  - Click on the `Save` button and then click on the `Create environment` button.
    113 113   
    114  -7. Setting up a `Continuous Deploy` pipeline:
     114 +7. Create an [AWS Elastic Beanstalk](https://us-east-2.console.aws.amazon.com/elasticbeanstalk/home) web environment:
     115 + 
     116 +- Open the [AWS Elastic Beanstalk](https://us-east-2.console.aws.amazon.com/elasticbeanstalk/home) page and click on the `Create a new environment` button.
     117 +- Select `Web server environment` option.
     118 +- Type the newly created application name (`gradejs-web`).
     119 +- Specify an environment name, for example `gradejs-public-web`. Leave the domain and description name fields blank.
     120 +- Select the latest version of Node.js 16 managed platform on Amazon Linux 2. The version minimal required version is 5.5.1.
     121 +- Use the sample application code and click on the `Configure more options` button.
     122 +- Select the `Custom configuration` configuration preset.
     123 +- In the `Software` section click on the `Edit` button.
     124 +- Enable the `CloudWatch` log streaming with a 5 day retention.
     125 +- Specify the `API_ORIGIN` environment variable. It should point to public API entrypoint you created in chapter 5.
     126 +- Specify the `CORS_ORIGIN` environment variable, set it to empty string to disable CORS checks.
     127 +- Specify `PLAUSIBLE_DOMAIN`, `GA_ID` (strings) and `DUMP_ANALYTICS` (bool) to setup analytics if required. Set to empty if not required.
     128 +- Specify the `EB_START` environment variable: `web`.
     129 +- Click on the `Save` button and then click on the `Create environment` button.
     130 + 
     131 +8. Setting up a `Continuous Deploy` pipeline:
    115 132   
    116 133  - Go to the [AWS CodePipeline](https://us-east-2.console.aws.amazon.com/codesuite/codepipeline/pipelines) page and click on the `Create pipeline` page.
    117 134  - Type a pipeline name `gradejs-public@production-deploy` and click on the `Next` button.
    skipped 14 lines
  • ■ ■ ■ ■ ■ ■
    buildspec.yml
    skipped 8 lines
    9 9   - docker-compose -f docker-compose.aws.yml up -d db
    10 10   - yarn install
    11 11   - yarn build:backend
     12 + - yarn workspace @gradejs-public/web build
    12 13   - yarn workspace @gradejs-public/shared migration:run
    13 14   - yarn test
    14 15   - docker-compose -f docker-compose.aws.yml down --volumes
     16 + - bash ./cli/archive_artifacts.sh
    15 17  artifacts:
    16 18   files:
    17  - - packages/public-api/build/**/*
    18  - - packages/public-api/package.json
    19  - - packages/worker/build/**/*
    20  - - packages/worker/package.json
    21  - - packages/shared/build/**/*
    22  - - packages/shared/package.json
    23  - - package.json
    24  - - eb-start.sh
     19 + - build.tar
     20 + - .platform/**/*
    25 21   
  • ■ ■ ■ ■ ■ ■
    cli/archive_artifacts.sh
     1 +#!/bin/bash
     2 + 
     3 +# Strip dev packages
     4 +yarn install --production --pure-lockfile
     5 + 
     6 +# Tar is required to keep the symlinks inside node_modules alive
     7 +# and thus avoid running `yarn install --production` on server
     8 +tar cf build.tar \
     9 + packages/public-api/build \
     10 + packages/public-api/package.json \
     11 + packages/public-api/node_modules \
     12 + packages/worker/build \
     13 + packages/worker/package.json \
     14 + packages/worker/node_modules \
     15 + packages/shared/build \
     16 + packages/shared/package.json \
     17 + packages/shared/node_modules \
     18 + packages/web/dist \
     19 + packages/web/package.json \
     20 + packages/web/node_modules \
     21 + node_modules \
     22 + package.json \
     23 + eb-start.sh
     24 + 
  • ■ ■ ■ ■ ■ ■
    cli/local_start.sh
    skipped 53 lines
    54 54  API_PID=$!
    55 55   
    56 56  echo "Starting web package dev server"
    57  -API_ORIGIN=http://localhost:8083 CORS_ORIGIN=http://localhost:3000 \
    58  - npm run start:dev --prefix packages/web 2>&1 &
     57 +PORT=3000 API_ORIGIN=http://localhost:8083 CORS_ORIGIN=http://localhost:3000 PLAUSIBLE_DOMAIN= GA_ID= DUMP_ANALYTICS= \
     58 + npm run dev:start --prefix packages/web 2>&1 &
    59 59  WEB_PID=$!
    60 60   
    61 61  # Some magic to shut down all services at once when requested
    skipped 54 lines
  • ■ ■ ■ ■ ■
    eb-start.sh
    1  - 
    2 1  #!/bin/bash
    3 2   
    4 3  case "$EB_START" in
    skipped 7 lines
    12 11   npm start --prefix packages/worker
    13 12   ;;
    14 13   
     14 + "web")
     15 + echo "Starting web package"
     16 + npm start --prefix packages/web
     17 + ;;
    15 18   *)
    16 19   echo "Unknown package to start EB_START=$EB_START"
    17 20   exit 1
    18 21   ;;
    19 22  esac
     23 + 
  • ■ ■ ■ ■
    package.json
    skipped 21 lines
    22 22   "build:public-api": "yarn workspace @gradejs-public/public-api run build",
    23 23   "build:backend": "yarn build:shared && yarn build:public-api && yarn build:worker",
    24 24   "dev:start": "bash cli/local_start.sh",
    25  - "dev:start:onlyWeb": "API_ORIGIN=https://api.staging.gradejs.com npm start --prefix packages/web"
     25 + "dev:start:web": "API_ORIGIN=https://api.staging.gradejs.com npm dev:start --prefix packages/web"
    26 26   },
    27 27   "devDependencies": {
    28 28   "@swc/core": "^1.2.233",
    skipped 15 lines
  • ■ ■ ■ ■ ■
    packages/public-api/tsconfig.build.json
    skipped 10 lines
    11 11   "**/*.test.ts"
    12 12   ]
    13 13  }
     14 + 
  • ■ ■ ■ ■ ■ ■
    packages/shared/src/utils/env.ts
    skipped 3 lines
    4 4   Port = 'PORT',
    5 5   DatabaseUrl = 'DB_URL',
    6 6   
     7 + // Web related
     8 + PublicApiOrigin = 'API_ORIGIN',
     9 + PublicCorsOrigin = 'CORS_ORIGIN',
     10 + PlausibleDomain = 'PLAUSIBLE_DOMAIN',
     11 + AnalyticsId = 'GA_ID',
     12 + VerboseAnalytics = 'DUMP_ANALYTICS',
     13 + 
    7 14   // AWS
    8 15   AwsRegion = 'AWS_REGION',
    9 16   SqsWorkerQueueUrl = 'SQS_WORKER_QUEUE_URL',
    skipped 54 lines
    64 71   keys.forEach((key) => key && getEnv(key));
    65 72  }
    66 73   
     74 +export const getClientVars = () => {
     75 + return [
     76 + Env.PublicApiOrigin,
     77 + Env.PublicCorsOrigin,
     78 + Env.PlausibleDomain,
     79 + Env.AnalyticsId,
     80 + Env.VerboseAnalytics,
     81 + ].reduce((acc, val) => {
     82 + acc[val] = getEnvUnsafe(val) ?? '';
     83 + return acc;
     84 + }, {} as Record<string, string>);
     85 +};
     86 + 
  • ■ ■ ■ ■ ■ ■
    packages/shared/tsconfig.build.json
    skipped 5 lines
    6 6   "baseUrl": "./src"
    7 7   },
    8 8   "include": [
    9  - "./src/**/*",
    10  - "../../index.d.ts"
     9 + "./src/**/*"
    11 10   ],
    12 11   "exclude": [
    13 12   "**/*.test.ts"
    14 13   ]
    15 14  }
     15 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/global.d.ts
    skipped 8 lines
    9 9   export default path;
    10 10  }
    11 11   
     12 +declare const __isServer__: boolean; // Replaced with DefinePlugin during build
     13 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/index.html
    skipped 14 lines
    15 15   content="GradeJS analyzes production JavaScript files and matches bundled NPM packages."
    16 16   />
    17 17   <meta property="og:image" content="/static/sharing-image.png" />
     18 + <script type="text/javascript">
     19 + window.process = { env: {} };
     20 + </script>
    18 21   
    19 22   <title>GradeJS | Production Webpack Bundle Analyzer</title>
    20 23   </head>
    skipped 12 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/package.json
    skipped 6 lines
    7 7   "prettier": "prettier --check ./src",
    8 8   "typecheck": "tsc --project tsconfig.json --noEmit",
    9 9   "test": "echo 'Not implemented yet'",
    10  - "start": "webpack serve --open",
    11 10   "storybook": "start-storybook -p 6006",
    12  - "build": "rm -rf dist && webpack --mode=production",
    13  - "build:storybook": "build-storybook -o dist/storybook"
     11 + "build:storybook": "build-storybook -o dist/storybook",
     12 + "build": "ts-node --swc webpack/build.ts --production",
     13 + "dev:start": "./webpack/start_dev.sh",
     14 + "start": "node dist/main.js",
     15 + "dev:build": "ts-node --swc webpack/build.ts"
    14 16   },
    15 17   "repository": {
    16 18   "type": "git",
    skipped 23 lines
    40 42   "html-webpack-plugin": "^5.3.2",
    41 43   "mini-css-extract-plugin": "^2.3.0",
    42 44   "node-sass": "^7.0.1",
     45 + "nodemon": "^2.0.19",
     46 + "null-loader": "^4.0.1",
    43 47   "sass-loader": "^12.1.0",
    44 48   "storybook": "^6.3.8",
    45 49   "style-loader": "^3.2.1",
    46  - "svg-sprite-loader": "git+https://github.com/ctizen/svg-sprite-loader.git#f73ede9aa3e357b7d671d2fb89e9d8846c0aa2ba",
     50 + "svg-sprite-loader": "git+https://github.com/ctizen/svg-sprite-loader.git#5fc8046921ce575698126e2fa17742678b0b91ac",
    47 51   "ts-loader": "^9.2.5",
    48 52   "typescript": "^4.6.4",
    49 53   "webpack": "^5.52.1",
    skipped 1 lines
    51 55   "webpack-dev-server": "^4.2.1"
    52 56   },
    53 57   "dependencies": {
     58 + "@gradejs-public/shared": "^0.1.0",
    54 59   "@reduxjs/toolkit": "^1.8.3",
    55 60   "@trpc/client": "^9.27.0",
    56 61   "@types/lodash.memoize": "^4.1.7",
    57 62   "clsx": "^1.1.1",
     63 + "express": "^4.18.1",
    58 64   "lodash.memoize": "^4.1.2",
    59 65   "plausible-tracker": "^0.3.8",
    60 66   "react": "^17.0.2",
    skipped 9 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/App.tsx
    skipped 1 lines
    2 2  import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
    3 3  import { HomePage } from './pages/Home';
    4 4  import { WebsiteResultsPage } from './pages/WebsiteResults';
    5  -import { initAnalytics } from '../services/analytics';
    6  -const locationChangeHandler = initAnalytics();
    7  - 
     5 +type AppProps = {
     6 + locationChangeHandler: (url?: string | URL) => void;
     7 +};
    8 8  /**
    9 9   * The App component has not any router wrapper because it uses both with tests, storybook and browser.
    10 10   * Each environment should had a high order router component
    skipped 3 lines
    14 14   * <App />
    15 15   * </BrowserRouter>
    16 16   */
    17  -export function App() {
     17 +export function App({ locationChangeHandler }: AppProps) {
    18 18   const location = useLocation();
    19 19   locationChangeHandler(location.pathname);
    20 20   return (
    skipped 8 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/layouts/Filters/Filters.tsx
    skipped 1 lines
    2 2  import React, { useEffect } from 'react';
    3 3  import clsx from 'clsx';
    4 4  import { SubmitHandler, useForm } from 'react-hook-form';
    5  -import { Button, TextInput } from 'components/ui';
     5 +import { Button, TextInput } from '../../ui';
    6 6  import Dropdown from '../../ui/Dropdown/Dropdown';
    7 7  import Radio from '../../ui/Radio/Radio';
    8 8  import styles from './Filters.module.scss';
    skipped 21 lines
    30 30   const watchFilterByName = watch('filter');
    31 31   let hideHandle: () => void;
    32 32   useEffect(() => {
    33  - document.body.addEventListener('click', hideHandle);
     33 + if (typeof document !== 'undefined') {
     34 + document.body.addEventListener('click', hideHandle);
     35 + }
    34 36   });
    35 37   
    36 38   return (
    skipped 76 lines
  • ■ ■ ■ ■ ■
    packages/web/src/components/pages/WebsiteResults.tsx
    skipped 21 lines
    22 22   );
    23 23   const setFilters = (filters: FiltersState) => dispatch(applyFilters(filters));
    24 24   
     25 + // TODO: discuss. Looks ugly
     26 + // Fetch data for SSR if host is already processed
     27 + if (__isServer__ && hostname) {
     28 + dispatch(getWebsite({ hostname, useRetry: false }));
     29 + }
     30 + 
    25 31   useEffect(() => {
    26 32   if (hostname && !isLoading && isPending) {
    27  - const promise = dispatch(getWebsite(hostname));
     33 + const promise = dispatch(getWebsite({ hostname }));
    28 34   return function cleanup() {
    29 35   promise.abort();
    30 36   };
    skipped 63 lines
  • ■ ■ ■ ■
    packages/web/src/components/ui/Icon/Icon.tsx
    skipped 33 lines
    34 34   color={color}
    35 35   xmlns='http://www.w3.org/2000/svg'
    36 36   >
    37  - <use xlinkHref={`/sprite.svg#${icons[kind].id}`} />
     37 + <use xlinkHref={`/static/sprite.svg#${icons[kind].id}`} />
    38 38   </svg>
    39 39   );
    40 40  }
    skipped 1 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/index.tsx
    skipped 3 lines
    4 4  import { BrowserRouter } from 'react-router-dom';
    5 5  import { store } from './store';
    6 6  import { App } from './components/App';
     7 +import { initAnalytics } from './services/analytics';
    7 8  import 'styles/global.scss';
    8 9   
    9  -ReactDOM.render(
     10 +const locationChangeHandler = initAnalytics();
     11 + 
     12 +ReactDOM.hydrate(
    10 13   <Provider store={store}>
    11 14   <BrowserRouter>
    12  - <App />
     15 + <App locationChangeHandler={locationChangeHandler} />
    13 16   </BrowserRouter>
    14 17   </Provider>,
    15 18   document.getElementById('app')
    skipped 2 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/server.tsx
     1 +import express from 'express';
     2 +import React from 'react';
     3 +import { Provider } from 'react-redux';
     4 +import ReactDOMServer from 'react-dom/server';
     5 +import { StaticRouter } from 'react-router-dom/server';
     6 +// TODO: fix import from different monorepo package
     7 +import { getPort, getClientVars } from '../../shared/src/utils/env';
     8 +import { store } from './store';
     9 +import { App } from './components/App';
     10 +import path from 'path';
     11 +import { readFileSync } from 'fs';
     12 + 
     13 +const app = express();
     14 +const layout = readFileSync(path.resolve(__dirname, 'static', 'index.html'), { encoding: 'utf-8' });
     15 + 
     16 +app.use('/static', express.static(path.join(__dirname, 'static')));
     17 + 
     18 +app.get('*', (req, res) => {
     19 + const html = ReactDOMServer.renderToString(
     20 + <Provider store={store}>
     21 + <StaticRouter location={req.url}>
     22 + <App locationChangeHandler={() => {}} />
     23 + </StaticRouter>
     24 + </Provider>
     25 + );
     26 + 
     27 + res.send(
     28 + layout
     29 + .replace('<div id="app"></div>', '<div id="app">' + html + '</div>')
     30 + // Little magic to support process.env calls on client side without additional replacements in bundle
     31 + .replace(
     32 + 'window.process = { env: {} };',
     33 + 'window.process = { env: ' + JSON.stringify(getClientVars()) + ' };'
     34 + )
     35 + );
     36 +});
     37 + 
     38 +app.listen(getPort(8080));
     39 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/services/analytics.ts
    skipped 24 lines
    25 25   pageView(url ? (typeof url === 'string' ? url : url.pathname) : window.location.pathname);
    26 26  }
    27 27   
    28  -export function pageView(url: string = window.location.pathname, referrer?: string) {
     28 +export function pageView(url?: string, referrer?: string) {
     29 + if (!plausible) {
     30 + return;
     31 + }
     32 + 
     33 + if (!url) {
     34 + url = window.location.pathname;
     35 + }
     36 + 
    29 37   if (process.env.PLAUSIBLE_DOMAIN) {
    30 38   plausible.trackPageview({
    31 39   url: window.location.origin + url,
    skipped 12 lines
    44 52  }
    45 53   
    46 54  export function trackCustomEvent(category: string, action: string, props?: CustomEventProperties) {
     55 + if (!plausible) {
     56 + return;
     57 + }
     58 + 
    47 59   if (process.env.PLAUSIBLE_DOMAIN) {
    48 60   plausible.trackEvent(`${category}_${action}`, { props });
    49 61   }
    skipped 10 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/services/apiClient.ts
    skipped 1 lines
    2 2  import type { inferProcedureOutput } from '@trpc/server';
    3 3  import type { AppRouter, Api } from '../../../public-api/src/router';
    4 4   
     5 +// polyfill fetch, trpc needs it
     6 +import f from 'node-fetch';
     7 + 
    5 8  // Helper types
    6 9  export type TQuery = keyof AppRouter['_def']['queries'];
    7 10  export type TMutation = keyof AppRouter['_def']['mutations'];
    skipped 10 lines
    18 21   
    19 22  export const client = createTRPCClient<AppRouter>({
    20 23   url: process.env.API_ORIGIN,
     24 + fetch: typeof window === 'undefined' ? (f as any) : window.fetch.bind(window),
     25 + headers: {
     26 + Origin: process.env.CORS_ORIGIN,
     27 + },
    21 28  });
    22 29   
    23 30  export type SyncWebsiteOutput = InferMutationOutput<'syncWebsite'>;
    skipped 4 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/store/slices/websiteResults.ts
    skipped 23 lines
    24 24  const hasPendingPages = (result: DetectionResult) =>
    25 25   !!result.webpages.find((item) => item.status === 'pending');
    26 26   
    27  -const getWebsite = createAsyncThunk('websiteResults/getWebsite', async (hostname: string) => {
    28  - const loadStartTime = Date.now();
    29  - let results = await client.mutation('syncWebsite', hostname);
    30  - while (hasPendingPages(results)) {
    31  - await sleep(5000);
    32  - results = await client.mutation('syncWebsite', hostname);
     27 +const getWebsite = createAsyncThunk(
     28 + 'websiteResults/getWebsite',
     29 + async ({ hostname, useRetry = true }: { hostname: string; useRetry?: boolean }) => {
     30 + const loadStartTime = Date.now();
     31 + let results = await client.mutation('syncWebsite', hostname);
     32 + if (useRetry) {
     33 + while (hasPendingPages(results)) {
     34 + await sleep(5000);
     35 + results = await client.mutation('syncWebsite', hostname);
     36 + }
     37 + }
     38 + // TODO: move to tracking middleware?
     39 + trackCustomEvent('HostnamePage', 'WebsiteLoaded', {
     40 + value: Date.now() - loadStartTime,
     41 + });
     42 + return results;
    33 43   }
    34  - // TODO: move to tracking middleware?
    35  - trackCustomEvent('HostnamePage', 'WebsiteLoaded', {
    36  - value: Date.now() - loadStartTime,
    37  - });
    38  - return results;
    39  -});
     44 +);
    40 45   
    41 46  const websiteResults = createSlice({
    42 47   name: 'websiteResults',
    skipped 31 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/tsconfig.build.json
     1 +{
     2 + "extends": "./tsconfig.json",
     3 + "compilerOptions": {
     4 + "outDir": "./webpack",
     5 + "baseUrl": "./src"
     6 + },
     7 + "include": [
     8 + "./src/**/*"
     9 + ],
     10 + "exclude": [
     11 + "**/*.test.ts"
     12 + ],
     13 + "files": ["global.d.ts"]
     14 +}
     15 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/vercel.json
    1 1  {
    2 2   "rewrites": [{
    3  - "source": "/api/event",
    4  - "destination": "https://plausible.io/api/event"
    5  - },
    6  - {
    7  - "source": "/(.*)",
    8  - "destination": "/index.html"
    9  - }
    10  - ]
     3 + "source": "/(.*)",
     4 + "destination": "/storybook/index.html"
     5 + }]
    11 6  }
     7 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/webpack/build.ts
     1 +import webpack from 'webpack';
     2 +import yargs from 'yargs/yargs';
     3 +import { hideBin } from 'yargs/helpers';
     4 +import { clientConfig } from './client';
     5 +import { serverConfig } from './server';
     6 + 
     7 +const args = yargs(hideBin(process.argv))
     8 + .option('production', {
     9 + alias: 'p',
     10 + type: 'boolean',
     11 + description: 'Run in production mode',
     12 + default: false,
     13 + })
     14 + .option('watch', {
     15 + alias: 'w',
     16 + type: 'boolean',
     17 + description: 'Watch changes',
     18 + default: false,
     19 + })
     20 + .parse();
     21 + 
     22 +webpack(
     23 + [
     24 + clientConfig(args.production ? 'production' : 'development', args.watch),
     25 + serverConfig(args.production ? 'production' : 'development', args.watch),
     26 + ],
     27 + (err, stats) => {
     28 + // [Stats Object](#stats-object)
     29 + process.stdout.write(stats!.toString() + '\n');
     30 + }
     31 +);
     32 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/webpack/client.ts
     1 +import { join, resolve } from 'path';
     2 +import HtmlWebpackPlugin from 'html-webpack-plugin';
     3 +import MiniCssExtractPlugin from 'mini-css-extract-plugin';
     4 +import CopyPlugin from 'copy-webpack-plugin';
     5 +import { configCommon, pluginsCommon, srcDir } from './common';
     6 +import { Configuration } from 'webpack';
     7 + 
     8 +const distDir = 'dist/static';
     9 + 
     10 +export const clientConfig: (mode: 'development' | 'production', watch: boolean) => Configuration = (
     11 + mode,
     12 + watch
     13 +) => ({
     14 + entry: join(__dirname, '..', srcDir, 'index.tsx'),
     15 + ...configCommon(mode),
     16 + module: {
     17 + rules: [
     18 + {
     19 + test: /\.(png|svg|jpg|jpeg|gif|woff(2)?|ttf|eot)$/i,
     20 + exclude: /sprite\/([^\/]*)\.svg$/,
     21 + type: 'asset/resource',
     22 + },
     23 + {
     24 + test: /sprite\/([^\/]*)\.svg$/,
     25 + loader: 'svg-sprite-loader',
     26 + options: {
     27 + extract: true,
     28 + spriteFilename: 'sprite.svg',
     29 + },
     30 + },
     31 + {
     32 + test: /\.(tsx|ts)?$/,
     33 + use: 'ts-loader',
     34 + exclude: '/node_modules/',
     35 + },
     36 + {
     37 + test: /\.module\.scss$/,
     38 + use: [
     39 + MiniCssExtractPlugin.loader,
     40 + {
     41 + loader: 'css-loader',
     42 + options: {
     43 + modules: true,
     44 + sourceMap: mode === 'development',
     45 + },
     46 + },
     47 + {
     48 + loader: 'sass-loader',
     49 + options: {
     50 + sourceMap: mode === 'development',
     51 + },
     52 + },
     53 + ],
     54 + },
     55 + {
     56 + test: /\.s?css$/,
     57 + exclude: /\.module.scss$/,
     58 + use: [
     59 + MiniCssExtractPlugin.loader,
     60 + 'css-loader',
     61 + {
     62 + loader: 'sass-loader',
     63 + options: {
     64 + sourceMap: mode === 'development',
     65 + },
     66 + },
     67 + ],
     68 + },
     69 + ],
     70 + },
     71 + plugins: [
     72 + ...pluginsCommon(mode, false),
     73 + new HtmlWebpackPlugin({
     74 + template: join(__dirname, '..', 'index.html'),
     75 + }),
     76 + new CopyPlugin({
     77 + patterns: [
     78 + { from: 'robots.txt', to: 'robots.txt' },
     79 + { from: 'src/assets/sharing-image.png', to: 'sharing-image.png' },
     80 + ],
     81 + }),
     82 + ],
     83 + output: {
     84 + filename: 'bundle.[fullhash].js',
     85 + path: resolve(__dirname, '..', distDir),
     86 + publicPath: '/static/',
     87 + assetModuleFilename: '[hash][ext]',
     88 + },
     89 + watch,
     90 +});
     91 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/webpack/common.ts
     1 +import { resolve } from 'path';
     2 +import { Configuration, DefinePlugin } from 'webpack';
     3 +import MiniCssExtractPlugin from 'mini-css-extract-plugin';
     4 +// eslint-disable-next-line @typescript-eslint/no-var-requires
     5 +const SpritePlugin = require('svg-sprite-loader/plugin');
     6 + 
     7 +export const srcDir = 'src';
     8 + 
     9 +export const configCommon: (mode: 'development' | 'production') => Configuration = (mode) => ({
     10 + mode,
     11 + resolve: {
     12 + extensions: ['.ts', '.tsx', '.js'],
     13 + modules: [resolve(__dirname, '..', srcDir), 'node_modules'],
     14 + },
     15 + devtool: mode === 'development' ? 'eval-source-map' : false,
     16 +});
     17 + 
     18 +export const pluginsCommon = (mode: string, isServer: boolean) => [
     19 + new DefinePlugin({
     20 + __isServer__: isServer,
     21 + }),
     22 + new MiniCssExtractPlugin({
     23 + filename: mode === 'development' ? '[name].css' : '[name].[fullhash].css',
     24 + chunkFilename: mode === 'development' ? '[id].css' : '[id].[fullhash].css',
     25 + }),
     26 + new SpritePlugin(),
     27 +];
     28 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/webpack/server.ts
     1 +import { join } from 'path';
     2 +import MiniCssExtractPlugin from 'mini-css-extract-plugin';
     3 +import { configCommon, pluginsCommon, srcDir } from './common';
     4 +import { Configuration } from 'webpack';
     5 + 
     6 +const distDir = 'dist';
     7 + 
     8 +export const serverConfig: (mode: 'development' | 'production', watch: boolean) => Configuration = (
     9 + mode,
     10 + watch
     11 +) => ({
     12 + entry: join(__dirname, '..', srcDir, 'server.tsx'),
     13 + ...configCommon(mode),
     14 + module: {
     15 + rules: [
     16 + {
     17 + test: /\.(png|svg|jpg|jpeg|gif|woff(2)?|ttf|eot)$/i,
     18 + exclude: /sprite\/([^\/]*)\.svg$/,
     19 + use: 'null-loader',
     20 + },
     21 + {
     22 + test: /sprite\/([^\/]*)\.svg$/,
     23 + loader: 'svg-sprite-loader',
     24 + options: {
     25 + extract: true,
     26 + spriteFilename: 'sprite.svg',
     27 + },
     28 + },
     29 + {
     30 + test: /\.(tsx|ts)?$/,
     31 + use: 'ts-loader',
     32 + exclude: '/node_modules/',
     33 + },
     34 + {
     35 + test: /\.module\.scss$/,
     36 + use: [
     37 + MiniCssExtractPlugin.loader,
     38 + {
     39 + loader: 'css-loader',
     40 + options: {
     41 + modules: true,
     42 + },
     43 + },
     44 + 'sass-loader',
     45 + ],
     46 + },
     47 + {
     48 + test: /\.s?css$/,
     49 + exclude: /\.module.scss$/,
     50 + use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
     51 + },
     52 + ],
     53 + },
     54 + plugins: pluginsCommon(mode, true),
     55 + output: {
     56 + filename: '[name].js',
     57 + path: join(__dirname, '..', distDir),
     58 + library: { type: 'commonjs2' },
     59 + globalObject: 'this',
     60 + },
     61 + optimization: {
     62 + minimize: false,
     63 + },
     64 + target: 'node',
     65 + watch,
     66 +});
     67 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/webpack/start_dev.sh
     1 +#!/usr/bin/env bash
     2 + 
     3 +# Note: script should be run from package root.
     4 + 
     5 +# Add ts-node and nodemon to PATH
     6 +export PATH=$PATH:./node_modules/.bin:../../node_modules/.bin
     7 + 
     8 +# Cleanup
     9 +rm -rf ./dist
     10 + 
     11 +echo "Building client/server bundles"
     12 +ts-node --swc webpack/build.ts --watch 2>&1 &
     13 +WEBPACK_PID=$!
     14 + 
     15 +# Wait until server file appears
     16 +until [ -f ./dist/main.js ]
     17 +do
     18 + sleep 1
     19 +done
     20 + 
     21 +echo "Starting server bundle"
     22 +nodemon --watch dist/main.js -V dist/main.js 2>&1 &
     23 +SRV_PID=$!
     24 + 
     25 +# Some magic to shut down all services at once when requested
     26 + 
     27 +TRAPPED_SIGNAL=false
     28 +trap "TRAPPED_SIGNAL=true; kill -15 $WEBPACK_PID; kill -15 $SRV_PID" SIGTERM SIGINT
     29 + 
     30 +while :
     31 +do
     32 + kill -0 $WEBPACK_PID 2> /dev/null
     33 + WEBPACK_STATUS=$?
     34 + 
     35 + kill -0 $SRV_PID 2> /dev/null
     36 + SRV_STATUS=$?
     37 + 
     38 + if [ "$TRAPPED_SIGNAL" = "false" ]; then
     39 + if [ $WEBPACK_STATUS -ne 0 ] || [ $SRV_STATUS -ne 0 ]; then
     40 + if [ $WEBPACK_STATUS -eq 0 ]; then
     41 + kill -15 $WEBPACK_PID;
     42 + wait $WEBPACK_PID;
     43 + fi
     44 + if [ $SRV_STATUS -eq 0 ]; then
     45 + kill -15 $SRV_PID;
     46 + wait $SRV_PID;
     47 + fi
     48 + exit 1;
     49 + fi
     50 + else
     51 + if [ $WEBPACK_STATUS -ne 0 ] && [ $SRV_STATUS -ne 0 ]; then
     52 + exit 0;
     53 + fi
     54 + fi
     55 + 
     56 + sleep 1
     57 +done
     58 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/webpack.config.js
    1  -const path = require('path');
    2  -const webpack = require('webpack');
    3  -const HtmlWebpackPlugin = require('html-webpack-plugin');
    4  -const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    5  -const CopyPlugin = require('copy-webpack-plugin');
    6  -const SpritePlugin = require('svg-sprite-loader/plugin');
    7  - 
    8  -const srcDir = 'src';
    9  -const distDir = 'dist';
    10  - 
    11  -const env = ['API_ORIGIN', 'PLAUSIBLE_DOMAIN', 'GA_ID', 'DUMP_ANALYTICS'].reduce((acc, val) => {
    12  - acc[val] = `"${process.env[val]}"`;
    13  - return acc;
    14  -}, {});
    15  - 
    16  -module.exports = (_, argv) => {
    17  - const { mode = 'development' } = argv;
    18  - const isDevelopment = mode === 'development';
    19  - 
    20  - return {
    21  - mode,
    22  - entry: path.join(__dirname, srcDir, 'index.tsx'),
    23  - resolve: {
    24  - extensions: ['.ts', '.tsx', '.js'],
    25  - modules: [path.resolve(__dirname, srcDir), 'node_modules'],
    26  - },
    27  - devtool: isDevelopment ? 'eval-source-map' : false,
    28  - module: {
    29  - rules: [
    30  - {
    31  - test: /\.(png|svg|jpg|jpeg|gif|woff(2)?|ttf|eot)$/i,
    32  - exclude: /sprite\/([^\/]*)\.svg$/,
    33  - type: 'asset/resource',
    34  - },
    35  - {
    36  - test: /sprite\/([^\/]*)\.svg$/,
    37  - loader: 'svg-sprite-loader',
    38  - options: {
    39  - extract: true,
    40  - spriteFilename: 'sprite.svg',
    41  - },
    42  - },
    43  - {
    44  - test: /\.(tsx|ts)?$/,
    45  - use: 'ts-loader',
    46  - exclude: '/node_modules/',
    47  - },
    48  - {
    49  - test: /\.module\.scss$/,
    50  - use: [
    51  - isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
    52  - {
    53  - loader: 'css-loader',
    54  - options: {
    55  - modules: true,
    56  - sourceMap: isDevelopment,
    57  - },
    58  - },
    59  - {
    60  - loader: 'sass-loader',
    61  - options: {
    62  - sourceMap: isDevelopment,
    63  - },
    64  - },
    65  - ],
    66  - },
    67  - {
    68  - test: /\.s?css$/,
    69  - exclude: /\.module.scss$/,
    70  - use: [
    71  - isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
    72  - 'css-loader',
    73  - {
    74  - loader: 'sass-loader',
    75  - options: {
    76  - sourceMap: isDevelopment,
    77  - },
    78  - },
    79  - ],
    80  - },
    81  - ],
    82  - },
    83  - plugins: [
    84  - new MiniCssExtractPlugin({
    85  - filename: isDevelopment ? '[name].css' : '[name].[fullhash].css',
    86  - chunkFilename: isDevelopment ? '[id].css' : '[id].[fullhash].css',
    87  - }),
    88  - new HtmlWebpackPlugin({
    89  - template: path.join(__dirname, 'index.html'),
    90  - }),
    91  - new webpack.EnvironmentPlugin(env),
    92  - new CopyPlugin({
    93  - patterns: [
    94  - { from: 'robots.txt', to: 'robots.txt' },
    95  - { from: 'src/assets/sharing-image.png', to: 'static/sharing-image.png' },
    96  - ],
    97  - }),
    98  - new SpritePlugin(),
    99  - ],
    100  - devServer: {
    101  - static: {
    102  - directory: path.join(__dirname, distDir),
    103  - },
    104  - compress: true,
    105  - historyApiFallback: {
    106  - disableDotRule: true,
    107  - },
    108  - port: 3000,
    109  - },
    110  - output: {
    111  - filename: 'bundle.[fullhash].js',
    112  - path: path.resolve(__dirname, distDir),
    113  - publicPath: '/',
    114  - assetModuleFilename: 'static/[hash][ext]',
    115  - },
    116  - };
    117  -};
    118  - 
  • ■ ■ ■ ■ ■ ■
    packages/worker/tsconfig.build.json
    skipped 4 lines
    5 5   "baseUrl": "./src"
    6 6   },
    7 7   "include": [
    8  - "./src/**/*",
    9  - "../../index.d.ts"
     8 + "./src/**/*"
    10 9   ],
    11 10   "exclude": [
    12 11   "**/*.test.ts"
    13 12   ]
    14 13  }
     14 + 
  • ■ ■ ■ ■ ■
    yarn.lock
    skipped 5784 lines
    5785 5785   optionalDependencies:
    5786 5786   fsevents "^1.2.7"
    5787 5787   
    5788  -chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.3:
     5788 +chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3:
    5789 5789   version "3.5.3"
    5790 5790   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
    5791 5791   integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
    skipped 695 lines
    6487 6487   dependencies:
    6488 6488   ms "2.1.2"
    6489 6489   
    6490  -debug@^3.0.0, debug@^3.1.1:
     6490 +debug@^3.0.0, debug@^3.1.1, debug@^3.2.7:
    6491 6491   version "3.2.7"
    6492 6492   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
    6493 6493   integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
    skipped 2356 lines
    8850 8850   resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
    8851 8851   integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
    8852 8852   
     8853 +ignore-by-default@^1.0.1:
     8854 + version "1.0.1"
     8855 + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
     8856 + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==
     8857 + 
    8853 8858  ignore@^4.0.3:
    8854 8859   version "4.0.6"
    8855 8860   resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
    skipped 2220 lines
    11076 11081   stdout-stream "^1.4.0"
    11077 11082   "true-case-path" "^1.0.2"
    11078 11083   
     11084 +nodemon@^2.0.19:
     11085 + version "2.0.19"
     11086 + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.19.tgz#cac175f74b9cb8b57e770d47841995eebe4488bd"
     11087 + integrity sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A==
     11088 + dependencies:
     11089 + chokidar "^3.5.2"
     11090 + debug "^3.2.7"
     11091 + ignore-by-default "^1.0.1"
     11092 + minimatch "^3.0.4"
     11093 + pstree.remy "^1.1.8"
     11094 + semver "^5.7.1"
     11095 + simple-update-notifier "^1.0.7"
     11096 + supports-color "^5.5.0"
     11097 + touch "^3.1.0"
     11098 + undefsafe "^2.0.5"
     11099 + 
    11079 11100  nopt@^5.0.0:
    11080 11101   version "5.0.0"
    11081 11102   resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
    11082 11103   integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
     11104 + dependencies:
     11105 + abbrev "1"
     11106 + 
     11107 +nopt@~1.0.10:
     11108 + version "1.0.10"
     11109 + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
     11110 + integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==
    11083 11111   dependencies:
    11084 11112   abbrev "1"
    11085 11113   
    skipped 80 lines
    11166 11194   dependencies:
    11167 11195   boolbase "^1.0.0"
    11168 11196   
     11197 +null-loader@^4.0.1:
     11198 + version "4.0.1"
     11199 + resolved "https://registry.yarnpkg.com/null-loader/-/null-loader-4.0.1.tgz#8e63bd3a2dd3c64236a4679428632edd0a6dbc6a"
     11200 + integrity sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==
     11201 + dependencies:
     11202 + loader-utils "^2.0.0"
     11203 + schema-utils "^3.0.0"
     11204 + 
    11169 11205  num2fraction@^1.2.2:
    11170 11206   version "1.2.2"
    11171 11207   resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
    skipped 1001 lines
    12173 12209   version "1.8.0"
    12174 12210   resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
    12175 12211   integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
     12212 + 
     12213 +pstree.remy@^1.1.8:
     12214 + version "1.1.8"
     12215 + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
     12216 + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
    12176 12217   
    12177 12218  public-encrypt@^4.0.0:
    12178 12219   version "4.0.3"
    skipped 1013 lines
    13192 13233   dependencies:
    13193 13234   semver "^6.3.0"
    13194 13235   
    13195  -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0:
     13236 +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1:
    13196 13237   version "5.7.1"
    13197 13238   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
    13198 13239   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
    13199 13240   
    13200  -[email protected]:
     13241 +[email protected], semver@~7.0.0:
    13201 13242   version "7.0.0"
    13202 13243   resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
    13203 13244   integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
    skipped 216 lines
    13420 13461   version "3.0.7"
    13421 13462   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
    13422 13463   integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
     13464 + 
     13465 +simple-update-notifier@^1.0.7:
     13466 + version "1.0.7"
     13467 + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz#7edf75c5bdd04f88828d632f762b2bc32996a9cc"
     13468 + integrity sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==
     13469 + dependencies:
     13470 + semver "~7.0.0"
    13423 13471   
    13424 13472  sisteransi@^1.0.5:
    13425 13473   version "1.0.5"
    skipped 523 lines
    13949 13997   dependencies:
    13950 13998   has-flag "^1.0.0"
    13951 13999   
    13952  -supports-color@^5.3.0:
     14000 +supports-color@^5.3.0, supports-color@^5.5.0:
    13953 14001   version "5.5.0"
    13954 14002   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
    13955 14003   integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
    skipped 55 lines
    14011 14059   query-string "^4.3.2"
    14012 14060   traverse "^0.6.6"
    14013 14061   
    14014  -"svg-sprite-loader@git+https://github.com/ctizen/svg-sprite-loader.git#f73ede9aa3e357b7d671d2fb89e9d8846c0aa2ba":
     14062 +"svg-sprite-loader@git+https://github.com/ctizen/svg-sprite-loader.git#5fc8046921ce575698126e2fa17742678b0b91ac":
    14015 14063   version "6.0.11"
    14016  - resolved "git+https://github.com/ctizen/svg-sprite-loader.git#f73ede9aa3e357b7d671d2fb89e9d8846c0aa2ba"
     14064 + resolved "git+https://github.com/ctizen/svg-sprite-loader.git#5fc8046921ce575698126e2fa17742678b0b91ac"
    14017 14065   dependencies:
    14018 14066   bluebird "^3.5.0"
    14019 14067   deepmerge "1.3.2"
    skipped 273 lines
    14293 14341   resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
    14294 14342   integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
    14295 14343   
     14344 +touch@^3.1.0:
     14345 + version "3.1.0"
     14346 + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"
     14347 + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==
     14348 + dependencies:
     14349 + nopt "~1.0.10"
     14350 + 
    14296 14351  tough-cookie@^4.0.0:
    14297 14352   version "4.0.0"
    14298 14353   resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4"
    skipped 278 lines
    14577 14632   has-bigints "^1.0.1"
    14578 14633   has-symbols "^1.0.2"
    14579 14634   which-boxed-primitive "^1.0.2"
     14635 + 
     14636 +undefsafe@^2.0.5:
     14637 + version "2.0.5"
     14638 + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
     14639 + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
    14580 14640   
    14581 14641  unfetch@^4.2.0:
    14582 14642   version "4.2.0"
    skipped 977 lines
Please wait...
Page is in error, reload to recover