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: ['**/*'],
|
files: ['**/*'],
|
||||||
rules: {
|
rules: {
|
||||||
'multiline-comment-style': ['error', 'separate-lines'],
|
'multiline-comment-style': ['error', 'separate-lines'],
|
||||||
'rulesdir/enforce-retry-on-import': 'error',
|
|
||||||
'rulesdir/no-undefined-or': 'error',
|
'rulesdir/no-undefined-or': 'error',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -6,6 +6,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
|
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
|
||||||
const { DefinePlugin, IgnorePlugin, ProvidePlugin } = require('webpack')
|
const { DefinePlugin, IgnorePlugin, ProvidePlugin } = require('webpack')
|
||||||
|
const { RetryChunkLoadPlugin } = require('webpack-retry-chunk-load-plugin')
|
||||||
|
|
||||||
const commitHash = execSync('git rev-parse HEAD').toString().trim()
|
const commitHash = execSync('git rev-parse HEAD').toString().trim()
|
||||||
const isProduction = process.env.NODE_ENV === 'production'
|
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://vanilla-extract.style/documentation/integrations/webpack/#identifiers for docs.
|
||||||
// See https://github.com/vanilla-extract-css/vanilla-extract/issues/771#issuecomment-1249524366.
|
// See https://github.com/vanilla-extract-css/vanilla-extract/issues/771#issuecomment-1249524366.
|
||||||
new VanillaExtractPlugin({ identifiers: 'short' }),
|
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: (webpackConfig) => {
|
||||||
// Configure webpack plugins:
|
// 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",
|
"ts-transform-graphql-tag": "^0.2.1",
|
||||||
"typechain": "^5.0.0",
|
"typechain": "^5.0.0",
|
||||||
"typescript": "^4.4.3",
|
"typescript": "^4.4.3",
|
||||||
|
"webpack-retry-chunk-load-plugin": "^3.1.1",
|
||||||
"yarn-deduplicate": "^6.0.0"
|
"yarn-deduplicate": "^6.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -8,11 +8,10 @@ import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
|
|||||||
import { lazy } from 'react'
|
import { lazy } from 'react'
|
||||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||||
import { ApplicationModal } from 'state/application/reducer'
|
import { ApplicationModal } from 'state/application/reducer'
|
||||||
import { retry } from 'utils/retry'
|
|
||||||
|
|
||||||
const Bag = lazy(() => retry(() => import('nft/components/bag/Bag')))
|
const Bag = lazy(() => import('nft/components/bag/Bag'))
|
||||||
const TransactionCompleteModal = lazy(() => retry(() => import('nft/components/collection/TransactionCompleteModal')))
|
const TransactionCompleteModal = lazy(() => import('nft/components/collection/TransactionCompleteModal'))
|
||||||
const AirdropModal = lazy(() => retry(() => import('components/AirdropModal')))
|
const AirdropModal = lazy(() => import('components/AirdropModal'))
|
||||||
|
|
||||||
export default function TopLevelModals() {
|
export default function TopLevelModals() {
|
||||||
const addressClaimOpen = useModalIsOpen(ApplicationModal.ADDRESS_CLAIM)
|
const addressClaimOpen = useModalIsOpen(ApplicationModal.ADDRESS_CLAIM)
|
||||||
|
@ -35,7 +35,6 @@ import {
|
|||||||
} from 'make-plural/plurals'
|
} from 'make-plural/plurals'
|
||||||
import { PluralCategory } from 'make-plural/plurals'
|
import { PluralCategory } from 'make-plural/plurals'
|
||||||
import { ReactNode, useEffect } from 'react'
|
import { ReactNode, useEffect } from 'react'
|
||||||
import { retry } from 'utils/retry'
|
|
||||||
|
|
||||||
type LocalePlural = {
|
type LocalePlural = {
|
||||||
[key in SupportedLocale]: (n: number | string, ord?: boolean) => PluralCategory
|
[key in SupportedLocale]: (n: number | string, ord?: boolean) => PluralCategory
|
||||||
@ -80,7 +79,7 @@ const plurals: LocalePlural = {
|
|||||||
export async function dynamicActivate(locale: SupportedLocale) {
|
export async function dynamicActivate(locale: SupportedLocale) {
|
||||||
i18n.loadLocaleData(locale, { plurals: () => plurals[locale] })
|
i18n.loadLocaleData(locale, { plurals: () => plurals[locale] })
|
||||||
try {
|
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.
|
// Bundlers will either export it as default or as a named export named default.
|
||||||
i18n.load(locale, catalog.messages || catalog.default.messages)
|
i18n.load(locale, catalog.messages || catalog.default.messages)
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
@ -18,7 +18,6 @@ import { flexRowNoWrap } from 'theme/styles'
|
|||||||
import { Z_INDEX } from 'theme/zIndex'
|
import { Z_INDEX } from 'theme/zIndex'
|
||||||
import { STATSIG_DUMMY_KEY } from 'tracing'
|
import { STATSIG_DUMMY_KEY } from 'tracing'
|
||||||
import { getEnvName } from 'utils/env'
|
import { getEnvName } from 'utils/env'
|
||||||
import { retry } from 'utils/retry'
|
|
||||||
import { getCurrentPageFromLocation } from 'utils/urlRoutes'
|
import { getCurrentPageFromLocation } from 'utils/urlRoutes'
|
||||||
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
|
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
|
||||||
|
|
||||||
@ -46,12 +45,12 @@ import Swap from './Swap'
|
|||||||
import { RedirectPathToSwapOnly } from './Swap/redirects'
|
import { RedirectPathToSwapOnly } from './Swap/redirects'
|
||||||
import Tokens from './Tokens'
|
import Tokens from './Tokens'
|
||||||
|
|
||||||
const TokenDetails = lazy(() => retry(() => import('./TokenDetails')))
|
const TokenDetails = lazy(() => import('./TokenDetails'))
|
||||||
const Vote = lazy(() => retry(() => import('./Vote')))
|
const Vote = lazy(() => import('./Vote'))
|
||||||
const NftExplore = lazy(() => retry(() => import('nft/pages/explore')))
|
const NftExplore = lazy(() => import('nft/pages/explore'))
|
||||||
const Collection = lazy(() => retry(() => import('nft/pages/collection')))
|
const Collection = lazy(() => import('nft/pages/collection'))
|
||||||
const Profile = lazy(() => retry(() => import('nft/pages/profile/profile')))
|
const Profile = lazy(() => import('nft/pages/profile/profile'))
|
||||||
const Asset = lazy(() => retry(() => import('nft/pages/asset/Asset')))
|
const Asset = lazy(() => import('nft/pages/asset/Asset'))
|
||||||
|
|
||||||
const BodyWrapper = styled.div`
|
const BodyWrapper = styled.div`
|
||||||
display: flex;
|
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 { TokenInfo, TokenList } from '@uniswap/token-lists'
|
||||||
import type { ValidateFunction } from 'ajv'
|
import type { ValidateFunction } from 'ajv'
|
||||||
|
|
||||||
import { retry } from './retry'
|
|
||||||
|
|
||||||
enum ValidationSchema {
|
enum ValidationSchema {
|
||||||
LIST = 'list',
|
LIST = 'list',
|
||||||
TOKENS = 'tokens',
|
TOKENS = 'tokens',
|
||||||
@ -19,16 +17,16 @@ async function validate(schema: ValidationSchema, data: unknown): Promise<unknow
|
|||||||
let validatorImport
|
let validatorImport
|
||||||
switch (schema) {
|
switch (schema) {
|
||||||
case ValidationSchema.LIST:
|
case ValidationSchema.LIST:
|
||||||
validatorImport = await retry(() => import('utils/__generated__/validateTokenList'))
|
validatorImport = await import('utils/__generated__/validateTokenList')
|
||||||
break
|
break
|
||||||
case ValidationSchema.TOKENS:
|
case ValidationSchema.TOKENS:
|
||||||
validatorImport = await retry(() => import('utils/__generated__/validateTokens'))
|
validatorImport = await import('utils/__generated__/validateTokens')
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error('No validation function specified for token list schema')
|
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
|
const validator = validatorModule.default as ValidateFunction
|
||||||
if (validator?.(data)) {
|
if (validator?.(data)) {
|
||||||
return data
|
return data
|
||||||
|
@ -16093,7 +16093,7 @@ prettier-linter-helpers@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fast-diff "^1.1.2"
|
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"
|
version "2.8.8"
|
||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
|
||||||
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
|
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
|
||||||
@ -19439,6 +19439,13 @@ webpack-merge@^5.8.0:
|
|||||||
clone-deep "^4.0.1"
|
clone-deep "^4.0.1"
|
||||||
wildcard "^2.0.0"
|
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:
|
webpack-sources@^1.4.3:
|
||||||
version "1.4.3"
|
version "1.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
|
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
|
||||||
|
Loading…
Reference in New Issue
Block a user