build: use webpack-retry-chunk-load-plugin (#6885)
* build: use webpack retry chunk load plugin * fix * dedupe * lint * retry backoff * reduce from 1000 to 500ms * add cache bust query * rm cache bust * 3 * cache bust * Update craco.config.cjs Co-authored-by: Zach Pomerantz <zzmp@uniswap.org> --------- Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
This commit is contained in:
parent
07eb9eb9a2
commit
3a0f6920d0
@ -13,7 +13,6 @@ module.exports = {
|
||||
files: ['**/*'],
|
||||
rules: {
|
||||
'multiline-comment-style': ['error', 'separate-lines'],
|
||||
'rulesdir/enforce-retry-on-import': 'error',
|
||||
'rulesdir/no-undefined-or': 'error',
|
||||
},
|
||||
},
|
||||
|
@ -6,6 +6,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const path = require('path')
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
|
||||
const { DefinePlugin, IgnorePlugin, ProvidePlugin } = require('webpack')
|
||||
const { RetryChunkLoadPlugin } = require('webpack-retry-chunk-load-plugin')
|
||||
|
||||
const commitHash = execSync('git rev-parse HEAD').toString().trim()
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
@ -93,6 +94,16 @@ module.exports = {
|
||||
// See https://vanilla-extract.style/documentation/integrations/webpack/#identifiers for docs.
|
||||
// See https://github.com/vanilla-extract-css/vanilla-extract/issues/771#issuecomment-1249524366.
|
||||
new VanillaExtractPlugin({ identifiers: 'short' }),
|
||||
new RetryChunkLoadPlugin({
|
||||
cacheBust: `function() {
|
||||
return 'cache-bust=' + Date.now();
|
||||
}`,
|
||||
// Retries with exponential backoff (500ms, 1000ms, 2000ms).
|
||||
retryDelay: `function(retryAttempt) {
|
||||
return 2 ** (retryAttempt - 1) * 500;
|
||||
}`,
|
||||
maxRetries: 3,
|
||||
}),
|
||||
],
|
||||
configure: (webpackConfig) => {
|
||||
// Configure webpack plugins:
|
||||
|
@ -1,36 +0,0 @@
|
||||
/* eslint-env node */
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: 'enforce use of retry() for dynamic imports',
|
||||
category: 'Best Practices',
|
||||
recommended: false,
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
ImportExpression(node) {
|
||||
const grandParent = node.parent.parent
|
||||
if (
|
||||
!(
|
||||
grandParent &&
|
||||
grandParent.type === 'CallExpression' &&
|
||||
// Technically, we are only checking that a function named `retry` wraps the dynamic import.
|
||||
// We do not go as far as enforcing that it is import('utils/retry').retry
|
||||
grandParent.callee.name === 'retry' &&
|
||||
grandParent.arguments.length === 1 &&
|
||||
grandParent.arguments[0].type === 'ArrowFunctionExpression'
|
||||
)
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
message: 'Dynamic import should be wrapped in retry (see `utils/retry.ts`): `retry(() => import(...))`',
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
@ -127,6 +127,7 @@
|
||||
"ts-transform-graphql-tag": "^0.2.1",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.4.3",
|
||||
"webpack-retry-chunk-load-plugin": "^3.1.1",
|
||||
"yarn-deduplicate": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -8,11 +8,10 @@ import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
|
||||
import { lazy } from 'react'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import { retry } from 'utils/retry'
|
||||
|
||||
const Bag = lazy(() => retry(() => import('nft/components/bag/Bag')))
|
||||
const TransactionCompleteModal = lazy(() => retry(() => import('nft/components/collection/TransactionCompleteModal')))
|
||||
const AirdropModal = lazy(() => retry(() => import('components/AirdropModal')))
|
||||
const Bag = lazy(() => import('nft/components/bag/Bag'))
|
||||
const TransactionCompleteModal = lazy(() => import('nft/components/collection/TransactionCompleteModal'))
|
||||
const AirdropModal = lazy(() => import('components/AirdropModal'))
|
||||
|
||||
export default function TopLevelModals() {
|
||||
const addressClaimOpen = useModalIsOpen(ApplicationModal.ADDRESS_CLAIM)
|
||||
|
@ -35,7 +35,6 @@ import {
|
||||
} from 'make-plural/plurals'
|
||||
import { PluralCategory } from 'make-plural/plurals'
|
||||
import { ReactNode, useEffect } from 'react'
|
||||
import { retry } from 'utils/retry'
|
||||
|
||||
type LocalePlural = {
|
||||
[key in SupportedLocale]: (n: number | string, ord?: boolean) => PluralCategory
|
||||
@ -80,7 +79,7 @@ const plurals: LocalePlural = {
|
||||
export async function dynamicActivate(locale: SupportedLocale) {
|
||||
i18n.loadLocaleData(locale, { plurals: () => plurals[locale] })
|
||||
try {
|
||||
const catalog = await retry(() => import(`locales/${locale}.js`))
|
||||
const catalog = await import(`locales/${locale}.js`)
|
||||
// Bundlers will either export it as default or as a named export named default.
|
||||
i18n.load(locale, catalog.messages || catalog.default.messages)
|
||||
} catch (error: unknown) {
|
||||
|
@ -18,7 +18,6 @@ import { flexRowNoWrap } from 'theme/styles'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
import { STATSIG_DUMMY_KEY } from 'tracing'
|
||||
import { getEnvName } from 'utils/env'
|
||||
import { retry } from 'utils/retry'
|
||||
import { getCurrentPageFromLocation } from 'utils/urlRoutes'
|
||||
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
|
||||
|
||||
@ -46,12 +45,12 @@ import Swap from './Swap'
|
||||
import { RedirectPathToSwapOnly } from './Swap/redirects'
|
||||
import Tokens from './Tokens'
|
||||
|
||||
const TokenDetails = lazy(() => retry(() => import('./TokenDetails')))
|
||||
const Vote = lazy(() => retry(() => import('./Vote')))
|
||||
const NftExplore = lazy(() => retry(() => import('nft/pages/explore')))
|
||||
const Collection = lazy(() => retry(() => import('nft/pages/collection')))
|
||||
const Profile = lazy(() => retry(() => import('nft/pages/profile/profile')))
|
||||
const Asset = lazy(() => retry(() => import('nft/pages/asset/Asset')))
|
||||
const TokenDetails = lazy(() => import('./TokenDetails'))
|
||||
const Vote = lazy(() => import('./Vote'))
|
||||
const NftExplore = lazy(() => import('nft/pages/explore'))
|
||||
const Collection = lazy(() => import('nft/pages/collection'))
|
||||
const Profile = lazy(() => import('nft/pages/profile/profile'))
|
||||
const Asset = lazy(() => import('nft/pages/asset/Asset'))
|
||||
|
||||
const BodyWrapper = styled.div`
|
||||
display: flex;
|
||||
|
@ -1,26 +0,0 @@
|
||||
import { retry } from './retry'
|
||||
|
||||
describe('retry function', () => {
|
||||
it('should resolve when function is successful', async () => {
|
||||
const expectedResult = 'Success'
|
||||
const mockFn = jest.fn().mockResolvedValue(expectedResult)
|
||||
const result = await retry(mockFn)
|
||||
expect(result).toEqual(expectedResult)
|
||||
expect(mockFn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should retry the specified number of times before rejecting', async () => {
|
||||
const error = new Error('Failure')
|
||||
const mockFn = jest.fn().mockRejectedValue(error)
|
||||
await expect(retry(mockFn, 3, 1)).rejects.toEqual(error)
|
||||
expect(mockFn).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('should resolve when function is successful on the second attempt', async () => {
|
||||
const expectedResult = 'Success'
|
||||
const mockFn = jest.fn().mockRejectedValueOnce(new Error('Failure')).mockResolvedValue(expectedResult)
|
||||
const result = await retry(mockFn, 3, 1)
|
||||
expect(result).toEqual(expectedResult)
|
||||
expect(mockFn).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
@ -1,29 +0,0 @@
|
||||
/**
|
||||
* Executes a Promise-based function multiple times with exponential backoff (doubling).
|
||||
* @returns the result of the original function's final attempt.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const fetchWithRetry = retry(fetchData, 5, 2000);
|
||||
* fetchWithRetry.then(data => console.log(data)).catch(error => console.error(error));
|
||||
* ```
|
||||
*/
|
||||
export function retry<T>(fn: () => Promise<T>, retries = 3, delay = 1000): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const attempt = async (attempts: number, currentDelay: number) => {
|
||||
try {
|
||||
const result = await fn()
|
||||
resolve(result)
|
||||
} catch (error) {
|
||||
if (attempts === retries) {
|
||||
reject(error)
|
||||
} else {
|
||||
const exponentialBackoffDelay = currentDelay * 2
|
||||
setTimeout(() => attempt(attempts + 1, exponentialBackoffDelay), currentDelay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attempt(1, delay)
|
||||
})
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
import type { TokenInfo, TokenList } from '@uniswap/token-lists'
|
||||
import type { ValidateFunction } from 'ajv'
|
||||
|
||||
import { retry } from './retry'
|
||||
|
||||
enum ValidationSchema {
|
||||
LIST = 'list',
|
||||
TOKENS = 'tokens',
|
||||
@ -19,16 +17,16 @@ async function validate(schema: ValidationSchema, data: unknown): Promise<unknow
|
||||
let validatorImport
|
||||
switch (schema) {
|
||||
case ValidationSchema.LIST:
|
||||
validatorImport = await retry(() => import('utils/__generated__/validateTokenList'))
|
||||
validatorImport = await import('utils/__generated__/validateTokenList')
|
||||
break
|
||||
case ValidationSchema.TOKENS:
|
||||
validatorImport = await retry(() => import('utils/__generated__/validateTokens'))
|
||||
validatorImport = await import('utils/__generated__/validateTokens')
|
||||
break
|
||||
default:
|
||||
throw new Error('No validation function specified for token list schema')
|
||||
}
|
||||
|
||||
const [, validatorModule] = await Promise.all([retry(() => import('ajv')), validatorImport])
|
||||
const [, validatorModule] = await Promise.all([import('ajv'), validatorImport])
|
||||
const validator = validatorModule.default as ValidateFunction
|
||||
if (validator?.(data)) {
|
||||
return data
|
||||
|
@ -16093,7 +16093,7 @@ prettier-linter-helpers@^1.0.0:
|
||||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@^2.1.2, prettier@^2.8.0, prettier@^2.8.8:
|
||||
prettier@^2.1.2, prettier@^2.6.2, prettier@^2.8.0, prettier@^2.8.8:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
|
||||
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
|
||||
@ -19439,6 +19439,13 @@ webpack-merge@^5.8.0:
|
||||
clone-deep "^4.0.1"
|
||||
wildcard "^2.0.0"
|
||||
|
||||
webpack-retry-chunk-load-plugin@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/webpack-retry-chunk-load-plugin/-/webpack-retry-chunk-load-plugin-3.1.1.tgz#44aefc21abd01769ecd07f9a200a58a78caf930c"
|
||||
integrity sha512-BKq/7EcelyWUUI6SeBaUKB1G+fSZP0rlxIwRQ+aO6mK5tffljaHdpJ4I2q54rpaaKjSbwbZRQlaITXe93SL9nA==
|
||||
dependencies:
|
||||
prettier "^2.6.2"
|
||||
|
||||
webpack-sources@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
|
||||
|
Loading…
Reference in New Issue
Block a user