lazy load LinkBox and psl

This commit is contained in:
Samuel Newman
2025-12-11 11:54:32 +02:00
parent 4e3c3e9905
commit 6f67a446f3
4 changed files with 74 additions and 22 deletions
+2 -1
View File
@@ -1,10 +1,11 @@
import {describe, expect, it} from '@jest/globals' import {describe, expect, it} from '@jest/globals'
// co-located with LinkBox.tsx so we can lazy load PSL
import {splitApexDomain} from '../../../src/components/dialogs/LinkWarning/LinkBox'
import { import {
isPossiblyAUrl, isPossiblyAUrl,
isTrustedUrl, isTrustedUrl,
linkRequiresWarning, linkRequiresWarning,
splitApexDomain,
} from '../../../src/lib/strings/url-helpers' } from '../../../src/lib/strings/url-helpers'
describe('linkRequiresWarning', () => { describe('linkRequiresWarning', () => {
@@ -0,0 +1,54 @@
import {useMemo} from 'react'
import {View} from 'react-native'
import psl from 'psl'
import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography'
export function splitApexDomain(hostname: string): [string, string] {
const hostnamep = psl.parse(hostname)
if (hostnamep.error || !hostnamep.listed || !hostnamep.domain) {
return ['', hostname]
}
return [
hostnamep.subdomain ? `${hostnamep.subdomain}.` : '',
hostnamep.domain,
]
}
export default function LinkBox({href}: {href: string}) {
const t = useTheme()
const [scheme, hostname, rest] = useMemo(() => {
try {
const urlp = new URL(href)
const [subdomain, apexdomain] = splitApexDomain(urlp.hostname)
return [
urlp.protocol + '//' + subdomain,
apexdomain,
urlp.pathname.replace(/\/$/, '') + urlp.search + urlp.hash,
]
} catch {
return ['', href, '']
}
}, [href])
return (
<View
style={[
t.atoms.bg,
t.atoms.border_contrast_medium,
a.px_md,
{paddingVertical: 10},
a.rounded_sm,
a.border,
]}>
<Text style={[a.text_md, a.leading_snug, t.atoms.text_contrast_medium]}>
{scheme}
<Text
style={[a.text_md, a.leading_snug, t.atoms.text, a.font_semi_bold]}>
{hostname}
</Text>
{rest}
</Text>
</View>
)
}
@@ -1,16 +1,18 @@
import {useCallback, useMemo} from 'react' import {lazy, Suspense, useCallback, useMemo} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useOpenLink} from '#/lib/hooks/useOpenLink' import {useOpenLink} from '#/lib/hooks/useOpenLink'
import {shareUrl} from '#/lib/sharing' import {shareUrl} from '#/lib/sharing'
import {isPossiblyAUrl, splitApexDomain} from '#/lib/strings/url-helpers' import {isPossiblyAUrl} from '#/lib/strings/url-helpers'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useGlobalDialogsControlContext} from './Context' import {useGlobalDialogsControlContext} from '../Context'
const LinkBox = lazy(() => import('./LinkBox.tsx'))
export function LinkWarningDialog() { export function LinkWarningDialog() {
const {linkWarningDialogControl} = useGlobalDialogsControlContext() const {linkWarningDialogControl} = useGlobalDialogsControlContext()
@@ -93,7 +95,11 @@ function LinkWarningDialogInner({
<Text style={[t.atoms.text_contrast_high, a.text_md, a.leading_snug]}> <Text style={[t.atoms.text_contrast_high, a.text_md, a.leading_snug]}>
<Trans>This link is taking you to the following website:</Trans> <Trans>This link is taking you to the following website:</Trans>
</Text> </Text>
{link && <LinkBox href={link.href} />} {link && (
<Suspense fallback={<FallbackLinkBox href={link.href} />}>
<LinkBox href={link.href} />
</Suspense>
)}
{potentiallyMisleading && ( {potentiallyMisleading && (
<Text <Text
style={[t.atoms.text_contrast_high, a.text_md, a.leading_snug]}> style={[t.atoms.text_contrast_high, a.text_md, a.leading_snug]}>
@@ -112,7 +118,6 @@ function LinkWarningDialogInner({
accessibilityHint={_(msg`Opens link ${link?.href ?? ''}`)} accessibilityHint={_(msg`Opens link ${link?.href ?? ''}`)}
onPress={onPressVisit} onPress={onPressVisit}
size="large" size="large"
variant="solid"
color={potentiallyMisleading ? 'secondary_inverted' : 'primary'}> color={potentiallyMisleading ? 'secondary_inverted' : 'primary'}>
<ButtonText> <ButtonText>
{link?.share ? ( {link?.share ? (
@@ -139,15 +144,19 @@ function LinkWarningDialogInner({
) )
} }
function LinkBox({href}: {href: string}) { /**
* Same as LinkBox but does not split the apex domain from the subdomain.
* This shows while we lazy load the LinkBox component, so that we can
* split `psl` out of the main bundle.
*/
function FallbackLinkBox({href}: {href: string}) {
const t = useTheme() const t = useTheme()
const [scheme, hostname, rest] = useMemo(() => { const [scheme, hostname, rest] = useMemo(() => {
try { try {
const urlp = new URL(href) const urlp = new URL(href)
const [subdomain, apexdomain] = splitApexDomain(urlp.hostname)
return [ return [
urlp.protocol + '//' + subdomain, urlp.protocol + '//',
apexdomain, urlp.hostname,
urlp.pathname.replace(/\/$/, '') + urlp.search + urlp.hash, urlp.pathname.replace(/\/$/, '') + urlp.search + urlp.hash,
] ]
} catch { } catch {
-12
View File
@@ -1,5 +1,4 @@
import {AtUri} from '@atproto/api' import {AtUri} from '@atproto/api'
import psl from 'psl'
import TLDs from 'tlds' import TLDs from 'tlds'
import {BSKY_SERVICE} from '#/lib/constants' import {BSKY_SERVICE} from '#/lib/constants'
@@ -310,17 +309,6 @@ export function isPossiblyAUrl(str: string): boolean {
return isValidDomain(firstWord) return isValidDomain(firstWord)
} }
export function splitApexDomain(hostname: string): [string, string] {
const hostnamep = psl.parse(hostname)
if (hostnamep.error || !hostnamep.listed || !hostnamep.domain) {
return ['', hostname]
}
return [
hostnamep.subdomain ? `${hostnamep.subdomain}.` : '',
hostnamep.domain,
]
}
export function createBskyAppAbsoluteUrl(path: string): string { export function createBskyAppAbsoluteUrl(path: string): string {
const sanitizedPath = path.replace(BSKY_APP_HOST, '').replace(/^\/+/, '') const sanitizedPath = path.replace(BSKY_APP_HOST, '').replace(/^\/+/, '')
return `${BSKY_APP_HOST.replace(/\/$/, '')}/${sanitizedPath}` return `${BSKY_APP_HOST.replace(/\/$/, '')}/${sanitizedPath}`