feat: add sitemap for app.uniswap.org (#7408)
* feat: add sitemap for app.uniswap.org * feat: script to update lastmod * fix: deps and snapshots * fix: use xml2js * fix: improve test and sitemap
This commit is contained in:
parent
bab8506919
commit
3ced65b8a4
@ -13,10 +13,11 @@
|
|||||||
"graphql:generate:thegraph": "graphql-codegen --config graphql.thegraph.codegen.config.ts",
|
"graphql:generate:thegraph": "graphql-codegen --config graphql.thegraph.codegen.config.ts",
|
||||||
"graphql:generate": "yarn graphql:generate:data && yarn graphql:generate:thegraph",
|
"graphql:generate": "yarn graphql:generate:data && yarn graphql:generate:thegraph",
|
||||||
"graphql": "yarn graphql:fetch && yarn graphql:generate",
|
"graphql": "yarn graphql:fetch && yarn graphql:generate",
|
||||||
|
"sitemap:generate": "node scripts/generate-sitemap.js",
|
||||||
"i18n:extract": "lingui extract --locale en-US",
|
"i18n:extract": "lingui extract --locale en-US",
|
||||||
"i18n:compile": "lingui compile",
|
"i18n:compile": "lingui compile",
|
||||||
"i18n": "yarn i18n:extract --clean && yarn i18n:compile",
|
"i18n": "yarn i18n:extract --clean && yarn i18n:compile",
|
||||||
"prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\"",
|
"prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\" \"npm:sitemap:generate\"",
|
||||||
"start": "craco start",
|
"start": "craco start",
|
||||||
"start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 npx wrangler pages dev --compatibility-flags=nodejs_compat --compatibility-date=2023-08-01 --proxy=3001 --port=3000 -- yarn start",
|
"start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 npx wrangler pages dev --compatibility-flags=nodejs_compat --compatibility-date=2023-08-01 --proxy=3001 --port=3000 -- yarn start",
|
||||||
"build": "craco build",
|
"build": "craco build",
|
||||||
@ -114,6 +115,7 @@
|
|||||||
"@types/ua-parser-js": "^0.7.36",
|
"@types/ua-parser-js": "^0.7.36",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@types/wcag-contrast": "^3.0.0",
|
"@types/wcag-contrast": "^3.0.0",
|
||||||
|
"@types/xml2js": "^0.4.12",
|
||||||
"@uniswap/default-token-list": "^11.2.0",
|
"@uniswap/default-token-list": "^11.2.0",
|
||||||
"@uniswap/eslint-config": "^1.2.0",
|
"@uniswap/eslint-config": "^1.2.0",
|
||||||
"@vanilla-extract/jest-transform": "^1.1.1",
|
"@vanilla-extract/jest-transform": "^1.1.1",
|
||||||
@ -293,6 +295,7 @@
|
|||||||
"workbox-navigation-preload": "^6.1.0",
|
"workbox-navigation-preload": "^6.1.0",
|
||||||
"workbox-precaching": "^6.1.0",
|
"workbox-precaching": "^6.1.0",
|
||||||
"workbox-routing": "^6.1.0",
|
"workbox-routing": "^6.1.0",
|
||||||
|
"xml2js": "^0.6.2",
|
||||||
"zustand": "^4.3.6"
|
"zustand": "^4.3.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
19
public/sitemap.xml
Normal file
19
public/sitemap.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||||
|
<url loc="https://app.uniswap.org/" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="1"/>
|
||||||
|
<url loc="https://app.uniswap.org/tokens" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.8"/>
|
||||||
|
<url loc="https://app.uniswap.org/send" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
|
||||||
|
<url loc="https://app.uniswap.org/swap" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.9"/>
|
||||||
|
<url loc="https://app.uniswap.org/pool/v2/find" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
|
||||||
|
<url loc="https://app.uniswap.org/pool/v2" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
|
||||||
|
<url loc="https://app.uniswap.org/pool" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
|
||||||
|
<url loc="https://app.uniswap.org/pools/v2/find" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
|
||||||
|
<url loc="https://app.uniswap.org/pools/v2" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
|
||||||
|
<url loc="https://app.uniswap.org/pools" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.7"/>
|
||||||
|
<url loc="https://app.uniswap.org/add/v2" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
|
||||||
|
<url loc="https://app.uniswap.org/add" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
|
||||||
|
<url loc="https://app.uniswap.org/increase" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
|
||||||
|
<url loc="https://app.uniswap.org/migrate/v2" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
|
||||||
|
<url loc="https://app.uniswap.org/nfts" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
|
||||||
|
<url loc="https://app.uniswap.org/nfts/profile" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
|
||||||
|
</urlset>
|
25
scripts/generate-sitemap.js
Normal file
25
scripts/generate-sitemap.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const { parseStringPromise, Builder } = require('xml2js')
|
||||||
|
|
||||||
|
fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
|
||||||
|
try {
|
||||||
|
const sitemap = await parseStringPromise(data)
|
||||||
|
|
||||||
|
const lastmodDate = new Date().toISOString()
|
||||||
|
if (sitemap.urlset.url) {
|
||||||
|
sitemap.urlset.url.forEach((url) => {
|
||||||
|
url['$'].lastmod = lastmodDate
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const builder = new Builder()
|
||||||
|
const xml = builder.buildObject(sitemap)
|
||||||
|
fs.writeFile('./public/sitemap.xml', xml, (error) => {
|
||||||
|
if (error) throw error
|
||||||
|
console.log('Sitemap updated')
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
throw new Error('Error parsing sitemap.xml')
|
||||||
|
}
|
||||||
|
})
|
@ -5,51 +5,27 @@ import ErrorBoundary from 'components/ErrorBoundary'
|
|||||||
import Loader from 'components/Icons/LoadingSpinner'
|
import Loader from 'components/Icons/LoadingSpinner'
|
||||||
import NavBar, { PageTabs } from 'components/NavBar'
|
import NavBar, { PageTabs } from 'components/NavBar'
|
||||||
import { useFeatureFlagsIsLoaded } from 'featureFlags'
|
import { useFeatureFlagsIsLoaded } from 'featureFlags'
|
||||||
import { useInfoPoolPageEnabled } from 'featureFlags/flags/infoPoolPage'
|
|
||||||
import { useAtom } from 'jotai'
|
import { useAtom } from 'jotai'
|
||||||
import { useBag } from 'nft/hooks/useBag'
|
import { useBag } from 'nft/hooks/useBag'
|
||||||
import { lazy, Suspense, useEffect, useLayoutEffect, useMemo, useState } from 'react'
|
import { lazy, Suspense, useEffect, useLayoutEffect, useMemo, useState } from 'react'
|
||||||
import { Navigate, Route, Routes, useLocation, useSearchParams } from 'react-router-dom'
|
import { Route, Routes, useLocation, useSearchParams } from 'react-router-dom'
|
||||||
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
|
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
|
||||||
import { useRouterPreference } from 'state/user/hooks'
|
import { useRouterPreference } from 'state/user/hooks'
|
||||||
import { StatsigProvider, StatsigUser } from 'statsig-react'
|
import { StatsigProvider, StatsigUser } from 'statsig-react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { SpinnerSVG } from 'theme/components'
|
|
||||||
import DarkModeQueryParamReader from 'theme/components/DarkModeQueryParamReader'
|
import DarkModeQueryParamReader from 'theme/components/DarkModeQueryParamReader'
|
||||||
import { useIsDarkMode } from 'theme/components/ThemeToggle'
|
import { useIsDarkMode } from 'theme/components/ThemeToggle'
|
||||||
import { flexRowNoWrap } from 'theme/styles'
|
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, isBrowserRouterEnabled } from 'utils/env'
|
import { getEnvName } from 'utils/env'
|
||||||
import { getDownloadAppLink } from 'utils/openDownloadApp'
|
import { getDownloadAppLink } from 'utils/openDownloadApp'
|
||||||
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'
|
||||||
|
|
||||||
// High-traffic pages (index and /swap) should not be lazy-loaded.
|
import { RouteDefinition, routes, useRouterConfig } from './RouteDefinitions'
|
||||||
import Landing from './Landing'
|
|
||||||
import Swap from './Swap'
|
|
||||||
|
|
||||||
const AppChrome = lazy(() => import('./AppChrome'))
|
const AppChrome = lazy(() => import('./AppChrome'))
|
||||||
const NftExplore = lazy(() => import('nft/pages/explore'))
|
|
||||||
const Collection = lazy(() => import('nft/pages/collection'))
|
|
||||||
const Profile = lazy(() => import('nft/pages/profile'))
|
|
||||||
const Asset = lazy(() => import('nft/pages/asset/Asset'))
|
|
||||||
const AddLiquidity = lazy(() => import('pages/AddLiquidity'))
|
|
||||||
const RedirectDuplicateTokenIds = lazy(() => import('pages/AddLiquidity/redirects'))
|
|
||||||
const RedirectDuplicateTokenIdsV2 = lazy(() => import('pages/AddLiquidityV2/redirects'))
|
|
||||||
const MigrateV2 = lazy(() => import('pages/MigrateV2'))
|
|
||||||
const MigrateV2Pair = lazy(() => import('pages/MigrateV2/MigrateV2Pair'))
|
|
||||||
const NotFound = lazy(() => import('pages/NotFound'))
|
|
||||||
const Pool = lazy(() => import('pages/Pool'))
|
|
||||||
const PositionPage = lazy(() => import('pages/Pool/PositionPage'))
|
|
||||||
const PoolV2 = lazy(() => import('pages/Pool/v2'))
|
|
||||||
const PoolDetails = lazy(() => import('pages/PoolDetails'))
|
|
||||||
const PoolFinder = lazy(() => import('pages/PoolFinder'))
|
|
||||||
const RemoveLiquidity = lazy(() => import('pages/RemoveLiquidity'))
|
|
||||||
const RemoveLiquidityV3 = lazy(() => import('pages/RemoveLiquidity/V3'))
|
|
||||||
const TokenDetails = lazy(() => import('pages/TokenDetails'))
|
|
||||||
const Tokens = lazy(() => import('pages/Tokens'))
|
|
||||||
const Vote = lazy(() => import('pages/Vote'))
|
|
||||||
|
|
||||||
const BodyWrapper = styled.div`
|
const BodyWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -93,32 +69,18 @@ const HeaderWrapper = styled.div<{ transparent?: boolean }>`
|
|||||||
z-index: ${Z_INDEX.dropdown};
|
z-index: ${Z_INDEX.dropdown};
|
||||||
`
|
`
|
||||||
|
|
||||||
// this is the same svg defined in assets/images/blue-loader.svg
|
|
||||||
// it is defined here because the remote asset may not have had time to load when this file is executing
|
|
||||||
const LazyLoadSpinner = () => (
|
|
||||||
<SpinnerSVG width="94" height="94" viewBox="0 0 94 94" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path
|
|
||||||
d="M92 47C92 22.1472 71.8528 2 47 2C22.1472 2 2 22.1472 2 47C2 71.8528 22.1472 92 47 92"
|
|
||||||
stroke="#2172E5"
|
|
||||||
strokeWidth="3"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
</SpinnerSVG>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const isLoaded = useFeatureFlagsIsLoaded()
|
const isLoaded = useFeatureFlagsIsLoaded()
|
||||||
const [shouldDisableNFTRoutes, setShouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom)
|
const [, setShouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom)
|
||||||
|
|
||||||
const browserRouterEnabled = isBrowserRouterEnabled()
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const { hash, pathname } = location
|
const { pathname } = location
|
||||||
const currentPage = getCurrentPageFromLocation(pathname)
|
const currentPage = getCurrentPageFromLocation(pathname)
|
||||||
const isDarkMode = useIsDarkMode()
|
const isDarkMode = useIsDarkMode()
|
||||||
const [routerPreference] = useRouterPreference()
|
const [routerPreference] = useRouterPreference()
|
||||||
const [scrolledState, setScrolledState] = useState(false)
|
const [scrolledState, setScrolledState] = useState(false)
|
||||||
const infoPoolPageEnabled = useInfoPoolPageEnabled()
|
|
||||||
|
const routerConfig = useRouterConfig()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0)
|
||||||
@ -224,116 +186,15 @@ export default function App() {
|
|||||||
<Suspense fallback={<Loader />}>
|
<Suspense fallback={<Loader />}>
|
||||||
{isLoaded ? (
|
{isLoaded ? (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
{routes.map((route: RouteDefinition) =>
|
||||||
path="/"
|
route.enabled(routerConfig) ? (
|
||||||
element={
|
<Route key={route.path} path={route.path} element={route.getElement(routerConfig)}>
|
||||||
// If we match "/" and # is defined, we are using BrowserRouter and need to redirect.
|
{route.nestedPaths.map((nestedPath) => (
|
||||||
browserRouterEnabled && hash ? <Navigate to={hash.replace('#', '')} replace /> : <Landing />
|
<Route path={nestedPath} key={`${route.path}/${nestedPath}`} />
|
||||||
}
|
))}
|
||||||
/>
|
</Route>
|
||||||
|
) : null
|
||||||
<Route path="tokens" element={<Tokens />}>
|
|
||||||
<Route path=":chainName" />
|
|
||||||
</Route>
|
|
||||||
<Route path="tokens/:chainName/:tokenAddress" element={<TokenDetails />} />
|
|
||||||
{infoPoolPageEnabled && <Route path="pools/:chainName/:poolAddress" element={<PoolDetails />} />}
|
|
||||||
<Route
|
|
||||||
path="vote/*"
|
|
||||||
element={
|
|
||||||
<Suspense fallback={<LazyLoadSpinner />}>
|
|
||||||
<Vote />
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route path="create-proposal" element={<Navigate to="/vote/create-proposal" replace />} />
|
|
||||||
<Route path="send" element={<Navigate to={{ ...location, pathname: '/swap' }} replace />} />
|
|
||||||
<Route path="swap" element={<Swap />} />
|
|
||||||
|
|
||||||
<Route path="pool/v2/find" element={<PoolFinder />} />
|
|
||||||
<Route path="pool/v2" element={<PoolV2 />} />
|
|
||||||
<Route path="pool" element={<Pool />} />
|
|
||||||
<Route path="pool/:tokenId" element={<PositionPage />} />
|
|
||||||
|
|
||||||
<Route path="pools/v2/find" element={<PoolFinder />} />
|
|
||||||
<Route path="pools/v2" element={<PoolV2 />} />
|
|
||||||
<Route path="pools" element={<Pool />} />
|
|
||||||
<Route path="pools/:tokenId" element={<PositionPage />} />
|
|
||||||
|
|
||||||
<Route path="add/v2" element={<RedirectDuplicateTokenIdsV2 />}>
|
|
||||||
<Route path=":currencyIdA" />
|
|
||||||
<Route path=":currencyIdA/:currencyIdB" />
|
|
||||||
</Route>
|
|
||||||
<Route path="add" element={<RedirectDuplicateTokenIds />}>
|
|
||||||
{/* this is workaround since react-router-dom v6 doesn't support optional parameters any more */}
|
|
||||||
<Route path=":currencyIdA" />
|
|
||||||
<Route path=":currencyIdA/:currencyIdB" />
|
|
||||||
<Route path=":currencyIdA/:currencyIdB/:feeAmount" />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="increase" element={<AddLiquidity />}>
|
|
||||||
<Route path=":currencyIdA" />
|
|
||||||
<Route path=":currencyIdA/:currencyIdB" />
|
|
||||||
<Route path=":currencyIdA/:currencyIdB/:feeAmount" />
|
|
||||||
<Route path=":currencyIdA/:currencyIdB/:feeAmount/:tokenId" />
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="remove/v2/:currencyIdA/:currencyIdB" element={<RemoveLiquidity />} />
|
|
||||||
<Route path="remove/:tokenId" element={<RemoveLiquidityV3 />} />
|
|
||||||
|
|
||||||
<Route path="migrate/v2" element={<MigrateV2 />} />
|
|
||||||
<Route path="migrate/v2/:address" element={<MigrateV2Pair />} />
|
|
||||||
|
|
||||||
{!shouldDisableNFTRoutes && (
|
|
||||||
<>
|
|
||||||
<Route
|
|
||||||
path="/nfts"
|
|
||||||
element={
|
|
||||||
<Suspense fallback={null}>
|
|
||||||
<NftExplore />
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/nfts/asset/:contractAddress/:tokenId"
|
|
||||||
element={
|
|
||||||
<Suspense fallback={null}>
|
|
||||||
<Asset />
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/nfts/profile"
|
|
||||||
element={
|
|
||||||
<Suspense fallback={null}>
|
|
||||||
<Profile />
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/nfts/collection/:contractAddress"
|
|
||||||
element={
|
|
||||||
<Suspense fallback={null}>
|
|
||||||
<Collection />
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/nfts/collection/:contractAddress/activity"
|
|
||||||
element={
|
|
||||||
<Suspense fallback={null}>
|
|
||||||
<Collection />
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Route path="*" element={<Navigate to="/not-found" replace />} />
|
|
||||||
<Route path="/not-found" element={<NotFound />} />
|
|
||||||
</Routes>
|
</Routes>
|
||||||
) : (
|
) : (
|
||||||
<Loader />
|
<Loader />
|
||||||
|
208
src/pages/RouteDefinitions.tsx
Normal file
208
src/pages/RouteDefinitions.tsx
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
import { useInfoPoolPageEnabled } from 'featureFlags/flags/infoPoolPage'
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
|
import { lazy, ReactNode, Suspense, useMemo } from 'react'
|
||||||
|
import { Navigate, useLocation } from 'react-router-dom'
|
||||||
|
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
|
||||||
|
import { SpinnerSVG } from 'theme/components'
|
||||||
|
import { isBrowserRouterEnabled } from 'utils/env'
|
||||||
|
|
||||||
|
// High-traffic pages (index and /swap) should not be lazy-loaded.
|
||||||
|
import Landing from './Landing'
|
||||||
|
import Swap from './Swap'
|
||||||
|
|
||||||
|
const NftExplore = lazy(() => import('nft/pages/explore'))
|
||||||
|
const Collection = lazy(() => import('nft/pages/collection'))
|
||||||
|
const Profile = lazy(() => import('nft/pages/profile'))
|
||||||
|
const Asset = lazy(() => import('nft/pages/asset/Asset'))
|
||||||
|
const AddLiquidity = lazy(() => import('pages/AddLiquidity'))
|
||||||
|
const RedirectDuplicateTokenIds = lazy(() => import('pages/AddLiquidity/redirects'))
|
||||||
|
const RedirectDuplicateTokenIdsV2 = lazy(() => import('pages/AddLiquidityV2/redirects'))
|
||||||
|
const MigrateV2 = lazy(() => import('pages/MigrateV2'))
|
||||||
|
const MigrateV2Pair = lazy(() => import('pages/MigrateV2/MigrateV2Pair'))
|
||||||
|
const NotFound = lazy(() => import('pages/NotFound'))
|
||||||
|
const Pool = lazy(() => import('pages/Pool'))
|
||||||
|
const PositionPage = lazy(() => import('pages/Pool/PositionPage'))
|
||||||
|
const PoolV2 = lazy(() => import('pages/Pool/v2'))
|
||||||
|
const PoolDetails = lazy(() => import('pages/PoolDetails'))
|
||||||
|
const PoolFinder = lazy(() => import('pages/PoolFinder'))
|
||||||
|
const RemoveLiquidity = lazy(() => import('pages/RemoveLiquidity'))
|
||||||
|
const RemoveLiquidityV3 = lazy(() => import('pages/RemoveLiquidity/V3'))
|
||||||
|
const TokenDetails = lazy(() => import('pages/TokenDetails'))
|
||||||
|
const Tokens = lazy(() => import('pages/Tokens'))
|
||||||
|
const Vote = lazy(() => import('pages/Vote'))
|
||||||
|
|
||||||
|
// this is the same svg defined in assets/images/blue-loader.svg
|
||||||
|
// it is defined here because the remote asset may not have had time to load when this file is executing
|
||||||
|
const LazyLoadSpinner = () => (
|
||||||
|
<SpinnerSVG width="94" height="94" viewBox="0 0 94 94" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M92 47C92 22.1472 71.8528 2 47 2C22.1472 2 2 22.1472 2 47C2 71.8528 22.1472 92 47 92"
|
||||||
|
stroke="#2172E5"
|
||||||
|
strokeWidth="3"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</SpinnerSVG>
|
||||||
|
)
|
||||||
|
|
||||||
|
interface RouterConfig {
|
||||||
|
browserRouterEnabled?: boolean
|
||||||
|
hash?: string
|
||||||
|
infoPoolPageEnabled?: boolean
|
||||||
|
shouldDisableNFTRoutes?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience hook which organizes the router configuration into a single object.
|
||||||
|
*/
|
||||||
|
export function useRouterConfig(): RouterConfig {
|
||||||
|
const browserRouterEnabled = isBrowserRouterEnabled()
|
||||||
|
const { hash } = useLocation()
|
||||||
|
const infoPoolPageEnabled = useInfoPoolPageEnabled()
|
||||||
|
const [shouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom)
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
browserRouterEnabled,
|
||||||
|
hash,
|
||||||
|
infoPoolPageEnabled,
|
||||||
|
shouldDisableNFTRoutes: Boolean(shouldDisableNFTRoutes),
|
||||||
|
}),
|
||||||
|
[browserRouterEnabled, hash, infoPoolPageEnabled, shouldDisableNFTRoutes]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RouteDefinition {
|
||||||
|
path: string
|
||||||
|
nestedPaths: string[]
|
||||||
|
enabled: (args: RouterConfig) => boolean
|
||||||
|
getElement: (args: RouterConfig) => ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assigns the defaults to the route definition.
|
||||||
|
function createRouteDefinition(route: Partial<RouteDefinition>): RouteDefinition {
|
||||||
|
return {
|
||||||
|
getElement: () => null,
|
||||||
|
enabled: () => true,
|
||||||
|
path: '/',
|
||||||
|
nestedPaths: [],
|
||||||
|
// overwrite the defaults
|
||||||
|
...route,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const routes: RouteDefinition[] = [
|
||||||
|
createRouteDefinition({
|
||||||
|
path: '/',
|
||||||
|
getElement: (args) => {
|
||||||
|
return args.browserRouterEnabled && args.hash ? <Navigate to={args.hash.replace('#', '')} replace /> : <Landing />
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
createRouteDefinition({
|
||||||
|
path: '/tokens',
|
||||||
|
nestedPaths: [':chainName'],
|
||||||
|
getElement: () => <Tokens />,
|
||||||
|
}),
|
||||||
|
createRouteDefinition({ path: '/tokens/:chainName/:tokenAddress', getElement: () => <TokenDetails /> }),
|
||||||
|
createRouteDefinition({
|
||||||
|
path: '/pools/:chainName/:poolAddress',
|
||||||
|
getElement: () => <PoolDetails />,
|
||||||
|
enabled: (args) => Boolean(args.infoPoolPageEnabled),
|
||||||
|
}),
|
||||||
|
createRouteDefinition({
|
||||||
|
path: '/vote/*',
|
||||||
|
getElement: () => (
|
||||||
|
<Suspense fallback={<LazyLoadSpinner />}>
|
||||||
|
<Vote />
|
||||||
|
</Suspense>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
createRouteDefinition({
|
||||||
|
path: '/create-proposal',
|
||||||
|
getElement: () => <Navigate to="/vote/create-proposal" replace />,
|
||||||
|
}),
|
||||||
|
createRouteDefinition({
|
||||||
|
path: '/send',
|
||||||
|
getElement: () => <Navigate to={{ ...location, pathname: '/swap' }} replace />,
|
||||||
|
}),
|
||||||
|
createRouteDefinition({ path: '/swap', getElement: () => <Swap /> }),
|
||||||
|
createRouteDefinition({ path: '/pool/v2/find', getElement: () => <PoolFinder /> }),
|
||||||
|
createRouteDefinition({ path: '/pool/v2', getElement: () => <PoolV2 /> }),
|
||||||
|
createRouteDefinition({ path: '/pool', getElement: () => <Pool /> }),
|
||||||
|
createRouteDefinition({ path: '/pool/:tokenId', getElement: () => <PositionPage /> }),
|
||||||
|
createRouteDefinition({ path: '/pools/v2/find', getElement: () => <PoolFinder /> }),
|
||||||
|
createRouteDefinition({ path: '/pools/v2', getElement: () => <PoolV2 /> }),
|
||||||
|
createRouteDefinition({ path: '/pools', getElement: () => <Pool /> }),
|
||||||
|
createRouteDefinition({ path: '/pools/:tokenId', getElement: () => <PositionPage /> }),
|
||||||
|
createRouteDefinition({
|
||||||
|
path: '/add/v2',
|
||||||
|
nestedPaths: [':currencyIdA', ':currencyIdA/:currencyIdB'],
|
||||||
|
getElement: () => <RedirectDuplicateTokenIdsV2 />,
|
||||||
|
}),
|
||||||
|
createRouteDefinition({
|
||||||
|
path: '/add',
|
||||||
|
nestedPaths: [':currencyIdA', ':currencyIdA/:currencyIdB', ':currencyIdA/:currencyIdB/:feeAmount'],
|
||||||
|
getElement: () => <RedirectDuplicateTokenIds />,
|
||||||
|
}),
|
||||||
|
|
||||||
|
createRouteDefinition({
|
||||||
|
path: '/increase',
|
||||||
|
nestedPaths: [
|
||||||
|
':currencyIdA',
|
||||||
|
':currencyIdA/:currencyIdB',
|
||||||
|
':currencyIdA/:currencyIdB/:feeAmount',
|
||||||
|
':currencyIdA/:currencyIdB/:feeAmount/:tokenId',
|
||||||
|
],
|
||||||
|
getElement: () => <AddLiquidity />,
|
||||||
|
}),
|
||||||
|
createRouteDefinition({ path: '/remove/v2/:currencyIdA/:currencyIdB', getElement: () => <RemoveLiquidity /> }),
|
||||||
|
createRouteDefinition({ path: '/remove/:tokenId', getElement: () => <RemoveLiquidityV3 /> }),
|
||||||
|
createRouteDefinition({ path: '/migrate/v2', getElement: () => <MigrateV2 /> }),
|
||||||
|
createRouteDefinition({ path: '/migrate/v2/:address', getElement: () => <MigrateV2Pair /> }),
|
||||||
|
createRouteDefinition({
|
||||||
|
path: '/nfts',
|
||||||
|
getElement: () => (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<NftExplore />
|
||||||
|
</Suspense>
|
||||||
|
),
|
||||||
|
enabled: (args) => !args.shouldDisableNFTRoutes,
|
||||||
|
}),
|
||||||
|
createRouteDefinition({
|
||||||
|
path: '/nfts/asset/:contractAddress/:tokenId',
|
||||||
|
getElement: () => (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<Asset />
|
||||||
|
</Suspense>
|
||||||
|
),
|
||||||
|
enabled: (args) => !args.shouldDisableNFTRoutes,
|
||||||
|
}),
|
||||||
|
createRouteDefinition({
|
||||||
|
path: '/nfts/profile',
|
||||||
|
getElement: () => (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<Profile />
|
||||||
|
</Suspense>
|
||||||
|
),
|
||||||
|
enabled: (args) => !args.shouldDisableNFTRoutes,
|
||||||
|
}),
|
||||||
|
createRouteDefinition({
|
||||||
|
path: '/nfts/collection/:contractAddress',
|
||||||
|
getElement: () => (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<Collection />
|
||||||
|
</Suspense>
|
||||||
|
),
|
||||||
|
enabled: (args) => !args.shouldDisableNFTRoutes,
|
||||||
|
}),
|
||||||
|
createRouteDefinition({
|
||||||
|
path: '/nfts/collection/:contractAddress/activity',
|
||||||
|
getElement: () => (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<Collection />
|
||||||
|
</Suspense>
|
||||||
|
),
|
||||||
|
enabled: (args) => !args.shouldDisableNFTRoutes,
|
||||||
|
}),
|
||||||
|
createRouteDefinition({ path: '*', getElement: () => <Navigate to="/not-found" replace /> }),
|
||||||
|
createRouteDefinition({ path: '/not-found', getElement: () => <NotFound /> }),
|
||||||
|
]
|
200
src/pages/__snapshots__/routes.test.ts.snap
Normal file
200
src/pages/__snapshots__/routes.test.ts.snap
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Routes router definition should match snapshot 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [
|
||||||
|
":chainName",
|
||||||
|
],
|
||||||
|
"path": "/tokens",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/tokens/:chainName/:tokenAddress",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/pools/:chainName/:poolAddress",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/vote/*",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/create-proposal",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/send",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/swap",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/pool/v2/find",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/pool/v2",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/pool",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/pool/:tokenId",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/pools/v2/find",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/pools/v2",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/pools",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/pools/:tokenId",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [
|
||||||
|
":currencyIdA",
|
||||||
|
":currencyIdA/:currencyIdB",
|
||||||
|
],
|
||||||
|
"path": "/add/v2",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [
|
||||||
|
":currencyIdA",
|
||||||
|
":currencyIdA/:currencyIdB",
|
||||||
|
":currencyIdA/:currencyIdB/:feeAmount",
|
||||||
|
],
|
||||||
|
"path": "/add",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [
|
||||||
|
":currencyIdA",
|
||||||
|
":currencyIdA/:currencyIdB",
|
||||||
|
":currencyIdA/:currencyIdB/:feeAmount",
|
||||||
|
":currencyIdA/:currencyIdB/:feeAmount/:tokenId",
|
||||||
|
],
|
||||||
|
"path": "/increase",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/remove/v2/:currencyIdA/:currencyIdB",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/remove/:tokenId",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/migrate/v2",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/migrate/v2/:address",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/nfts",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/nfts/asset/:contractAddress/:tokenId",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/nfts/profile",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/nfts/collection/:contractAddress",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/nfts/collection/:contractAddress/activity",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "*",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"enabled": [Function],
|
||||||
|
"getElement": [Function],
|
||||||
|
"nestedPaths": Array [],
|
||||||
|
"path": "/not-found",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
26
src/pages/routes.test.ts
Normal file
26
src/pages/routes.test.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import { parseStringPromise } from 'xml2js'
|
||||||
|
|
||||||
|
import { routes } from './RouteDefinitions'
|
||||||
|
|
||||||
|
describe('Routes', () => {
|
||||||
|
it('sitemap URLs should exist as Router paths', async () => {
|
||||||
|
const pathNames: string[] = routes.map((routeDef) => routeDef.path)
|
||||||
|
const contents = fs.readFileSync('./public/sitemap.xml', 'utf8')
|
||||||
|
const sitemap = await parseStringPromise(contents)
|
||||||
|
|
||||||
|
const sitemapPaths = sitemap.urlset.url.map((url: any) => new URL(url['$'].loc).pathname)
|
||||||
|
|
||||||
|
sitemapPaths.forEach((path: string) => {
|
||||||
|
expect(pathNames).toContain(path)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you are updating the app routes, consider if you need to make a
|
||||||
|
* corresponding update to the sitemap.xml file.
|
||||||
|
*/
|
||||||
|
it('router definition should match snapshot', () => {
|
||||||
|
expect(routes).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
27
yarn.lock
27
yarn.lock
@ -5921,6 +5921,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/xml2js@^0.4.12":
|
||||||
|
version "0.4.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.12.tgz#d9aae03295476fd5cbc74e0b572816208dbec6d1"
|
||||||
|
integrity sha512-CZPpQKBZ8db66EP5hCjwvYrLThgZvnyZrPXK2W+UI1oOaWezGt34iOaUCX4Jah2X8+rQqjvl9VKEIT8TR1I0rA==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/yargs-parser@*":
|
"@types/yargs-parser@*":
|
||||||
version "20.2.1"
|
version "20.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129"
|
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129"
|
||||||
@ -15542,9 +15549,9 @@ mz@^2.7.0:
|
|||||||
thenify-all "^1.0.0"
|
thenify-all "^1.0.0"
|
||||||
|
|
||||||
nan@^2.14.0:
|
nan@^2.14.0:
|
||||||
version "2.14.2"
|
version "2.18.0"
|
||||||
resolved "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz"
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554"
|
||||||
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
|
integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==
|
||||||
|
|
||||||
nano-time@1.0.0:
|
nano-time@1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
@ -17227,9 +17234,9 @@ punycode@1.3.2, punycode@^1.3.2:
|
|||||||
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
|
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
|
||||||
|
|
||||||
punycode@^2.1.0, punycode@^2.1.1:
|
punycode@^2.1.0, punycode@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
|
||||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
|
||||||
|
|
||||||
pure-rand@^6.0.0:
|
pure-rand@^6.0.0:
|
||||||
version "6.0.2"
|
version "6.0.2"
|
||||||
@ -21101,6 +21108,14 @@ xml2js@^0.4.5:
|
|||||||
sax ">=0.6.0"
|
sax ">=0.6.0"
|
||||||
xmlbuilder "~11.0.0"
|
xmlbuilder "~11.0.0"
|
||||||
|
|
||||||
|
xml2js@^0.6.2:
|
||||||
|
version "0.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499"
|
||||||
|
integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==
|
||||||
|
dependencies:
|
||||||
|
sax ">=0.6.0"
|
||||||
|
xmlbuilder "~11.0.0"
|
||||||
|
|
||||||
xmlbuilder@~11.0.0:
|
xmlbuilder@~11.0.0:
|
||||||
version "11.0.1"
|
version "11.0.1"
|
||||||
resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz"
|
resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user