svelte: Upgrade to SvelteKit v2 (#59759)

This includes upgrading to Vite 5. I followed https://kit.svelte.dev/docs/migrating-to-sveltekit-2
and ran `pnpx svelte-migrate@latest sveltekit-2` which took care of the
Svelte changes (of which there were only a few).

Because SK2 doesn't automatically await top-level promises anymore there
is no need to have the artificaly `deferred` second level. I removed
that from all loaders. I also took the time to clean up the svelte
config file a bit.

The switch to Vite 5 is tricker and I had to resort to setting
`proxySsrExternalModules: true` to make CommonJS modules imported as ES
modules work.
However using `pnpm` to alias `lodash` to `lodash-es` generally seems to
be a good improvemnt, even if not strictly necessary for the upgrade
(until we remove `proxySsrExternalModules: true`).
This commit is contained in:
Felix Kling 2024-01-24 09:46:25 +01:00 committed by GitHub
parent b620f9a925
commit 8c5828fbef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 1039 additions and 572 deletions

View File

@ -59,10 +59,11 @@ BUILD_DEPS = [
":node_modules/@storybook/svelte",
":node_modules/@sveltejs/adapter-static",
":node_modules/@sveltejs/kit",
":node_modules/@sveltejs/vite-plugin-svelte",
":node_modules/@types/prismjs",
":node_modules/lodash-es",
":node_modules/graphql",
":node_modules/prismjs",
":node_modules/sass",
":node_modules/svelte",
":node_modules/ts-key-enum",
":node_modules/vite",

View File

@ -36,9 +36,10 @@
"@storybook/svelte": "^7.2.0",
"@storybook/sveltekit": "^7.2.0",
"@storybook/testing-library": "0.2.0",
"@sveltejs/adapter-auto": "^2.1.0",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.22.3",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-static": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@testing-library/svelte": "^4.0.3",
"@testing-library/user-event": "^14.4.3",
"@types/cookie": "^0.5.1",
@ -51,15 +52,16 @@
"msw-storybook-addon": "^1.8.0",
"prettier": "2.8.1",
"prettier-plugin-svelte": "^2.0.0",
"sass": "^1.32.4",
"signale": "^1.4.0",
"storybook": "^7.2.0",
"storybook-dark-mode": "^3.0.1",
"svelte": "^4.1.1",
"svelte-check": "^3.4.6",
"tslib": "2.1.0",
"vite": "^4.4.7",
"vite": "~5.0.0",
"vite-plugin-inspect": "^0.7.35",
"vitest": "^0.33.0"
"vitest": "^1.0.0"
},
"type": "module",
"dependencies": {
@ -73,7 +75,6 @@
"@sourcegraph/web": "workspace:*",
"@sourcegraph/wildcard": "workspace:*",
"highlight.js": "^10.0.0",
"lodash-es": "^4.17.21",
"prismjs": "^1.29.0",
"ts-key-enum": "^2.0.12"
},

View File

@ -6,7 +6,7 @@ import userEvent from '@testing-library/user-event'
import type { ComponentProps } from 'svelte'
import { describe, test, expect, vi } from 'vitest'
import { useFakeTimers, useRealTimers } from '$mocks'
import { useFakeTimers, useRealTimers } from '$testing/mocks'
import Timestamp from './Timestamp.svelte'

View File

@ -1,6 +1,6 @@
import { describe, test, vi, expect } from 'vitest'
import { mockFeatureFlags, unmockFeatureFlags, useFakeTimers, useRealTimers } from '$mocks'
import { mockFeatureFlags, unmockFeatureFlags, useFakeTimers, useRealTimers } from '$testing/mocks'
import { createFeatureFlagStore, featureFlag } from './stores'

View File

@ -1,4 +1,4 @@
import { resolvePath } from '@sveltejs/kit'
import { resolveRoute } from '$app/paths'
import type { ResolvedRevision } from '../../routes/[...repo=reporev]/+layout'
@ -23,7 +23,7 @@ export function navFromPath(path: string, repo: string): [string, string][] {
.slice(0, -1)
.map((part, index, all): [string, string] => [
part,
resolvePath(TREE_ROUTE_ID, { repo, path: all.slice(0, index + 1).join('/') }),
resolveRoute(TREE_ROUTE_ID, { repo, path: all.slice(0, index + 1).join('/') }),
])
.concat([[parts.at(-1) ?? '', '']])
}

View File

@ -29,7 +29,7 @@ export const load: LayoutLoad = async () => {
const settings = parseJSONCOrError<Settings>(result.data.viewerSettings.final)
if (isErrorLike(settings)) {
throw error(500, `Failed to parse user settings: ${settings.message}`)
error(500, `Failed to parse user settings: ${settings.message}`)
}
return {

View File

@ -3,6 +3,5 @@ import { redirect } from '@sveltejs/kit'
import type { LayoutLoad } from './$types'
export const load: LayoutLoad = () => {
// eslint-disable-next-line etc/throw-error, rxjs/throw-error
throw redirect(300, '/search')
redirect(300, '/search')
}

View File

@ -49,7 +49,7 @@
)
async function updateFileTreeProvider(repoID: Scalars['ID']['input'], commitID: string, parentPath: string) {
const result = await data.deferred.fileTree
const result = await data.fileTree
if (!result) {
treeProvider = null
return

View File

@ -51,12 +51,10 @@ export const load: LayoutLoad = async ({ parent, params }) => {
return {
parentPath,
commitHistory,
deferred: {
fileTree: fetchSidebarFileTree({
repoID: resolvedRevision.repo.id,
commitID: resolvedRevision.commitID,
filePath: parentPath,
}),
},
fileTree: fetchSidebarFileTree({
repoID: resolvedRevision.repo.id,
commitID: resolvedRevision.commitID,
filePath: parentPath,
}),
}
}

View File

@ -13,7 +13,7 @@
export let data: PageData
const { value: readme, set: setReadme, pending: readmePending } = createPromiseStore<RepoPage_Readme | null>()
$: setReadme(data.deferred.readme)
$: setReadme(data.readme)
</script>
<h3 class="header">

View File

@ -4,32 +4,29 @@ import type { PageLoad } from './$types'
import { RepoPageReadmeQuery } from './page.gql'
export const load: PageLoad = async ({ parent }) => {
const { resolvedRevision, deferred, graphqlClient } = await parent()
const { resolvedRevision, graphqlClient, fileTree } = await parent()
return {
deferred: {
...deferred,
readme: deferred.fileTree.then(result => {
const readme = findReadme(result.root.entries)
if (!readme) {
return null
}
return graphqlClient
.query({
query: RepoPageReadmeQuery,
variables: {
repoID: resolvedRevision.repo.id,
revspec: resolvedRevision.commitID,
path: readme.path,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Expected Repository')
}
return result.data.node.commit?.blob ?? null
})
}),
},
readme: fileTree.then(result => {
const readme = findReadme(result.root.entries)
if (!readme) {
return null
}
return graphqlClient
.query({
query: RepoPageReadmeQuery,
variables: {
repoID: resolvedRevision.repo.id,
revspec: resolvedRevision.commitID,
path: readme.path,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Expected Repository')
}
return result.data.node.commit?.blob ?? null
})
}),
}
}

View File

@ -37,7 +37,7 @@
const { loading, combinedBlobData, set: setBlobData } = createBlobDataHandler()
let selectedPosition: LineOrPositionOrRange | null = null
$: setBlobData(data.deferred.blob, data.deferred.highlights)
$: setBlobData(data.blob, data.highlights)
$: blobData = $combinedBlobData.blob
$: formatted = !!blobData?.richHTML
$: showRaw = $page.url.searchParams.get('view') === 'raw'
@ -57,10 +57,10 @@
</svelte:head>
<FileHeader>
<Icon slot="icon" svgPath={data.deferred.compare ? mdiCodeBracesBox : mdiFileCodeOutline} />
<Icon slot="icon" svgPath={data.compare ? mdiCodeBracesBox : mdiFileCodeOutline} />
<svelte:fragment slot="actions">
{#if data.deferred.compare}
<span>{data.deferred.compare.revisionToCompare}</span>
{#if data.compare}
<span>{data.compare.revisionToCompare}</span>
{:else}
{#if !formatted || showRaw}
<WrapLinesAction />
@ -73,9 +73,9 @@
</svelte:fragment>
</FileHeader>
<div class="content" class:loading={$loading} class:compare={!!data.deferred.compare}>
{#if data.deferred.compare}
{#await data.deferred.compare.diff}
<div class="content" class:loading={$loading} class:compare={!!data.compare}>
{#if data.compare}
{#await data.compare.diff}
<LoadingSpinner />
{:then fileDiff}
{#if fileDiff}

View File

@ -7,58 +7,56 @@ export const load: PageLoad = async ({ parent, params, url }) => {
return {
filePath: params.path,
deferred: {
blob: graphqlClient
.query({
query: BlobPageQuery,
variables: {
repoID: resolvedRevision.repo.id,
revspec: resolvedRevision.commitID,
path: params.path,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository' || !result.data.node.commit?.blob) {
throw new Error('Commit or file not found')
}
return result.data.node.commit.blob
}),
highlights: graphqlClient
.query({
query: BlobSyntaxHighlightQuery,
variables: {
repoID: resolvedRevision.repo.id,
revspec: resolvedRevision.commitID,
path: params.path,
disableTimeout: false,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Expected Repository')
}
return result.data.node.commit?.blob?.highlight.lsif
}),
compare: revisionToCompare
? {
revisionToCompare,
diff: graphqlClient
.query({
query: BlobDiffQuery,
variables: {
repoID: resolvedRevision.repo.id,
revspec: revisionToCompare,
paths: [params.path],
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Expected Repository')
}
return result.data.node.commit?.diff.fileDiffs.nodes[0]
}),
}
: null,
},
blob: graphqlClient
.query({
query: BlobPageQuery,
variables: {
repoID: resolvedRevision.repo.id,
revspec: resolvedRevision.commitID,
path: params.path,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository' || !result.data.node.commit?.blob) {
throw new Error('Commit or file not found')
}
return result.data.node.commit.blob
}),
highlights: graphqlClient
.query({
query: BlobSyntaxHighlightQuery,
variables: {
repoID: resolvedRevision.repo.id,
revspec: resolvedRevision.commitID,
path: params.path,
disableTimeout: false,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Expected Repository')
}
return result.data.node.commit?.blob?.highlight.lsif
}),
compare: revisionToCompare
? {
revisionToCompare,
diff: graphqlClient
.query({
query: BlobDiffQuery,
variables: {
repoID: resolvedRevision.repo.id,
revspec: revisionToCompare,
paths: [params.path],
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Expected Repository')
}
return result.data.node.commit?.diff.fileDiffs.nodes[0]
}),
}
: null,
}
}

View File

@ -13,13 +13,13 @@
export let data: PageData
const { value: tree, set: setTree } = createPromiseStore<PageData['deferred']['treeEntries']>()
const { value: tree, set: setTree } = createPromiseStore<PageData['treeEntries']>()
const { value: commitInfo, set: setCommitInfo } = createPromiseStore<Promise<TreePage_TreeWithCommitInfo | null>>()
const { value: readme, set: setReadme } = createPromiseStore<Promise<TreePage_Readme | null>>()
$: setTree(data.deferred.treeEntries)
$: setCommitInfo(data.deferred.commitInfo)
$: setReadme(data.deferred.readme)
$: setTree(data.treeEntries)
$: setCommitInfo(data.commitInfo)
$: setReadme(data.readme)
$: entries = $tree?.entries ?? []
$: entriesWithCommitInfo = $commitInfo?.entries ?? []
</script>

View File

@ -19,48 +19,46 @@ export const load: PageLoad = async ({ params, parent }) => {
return {
filePath: params.path,
deferred: {
treeEntries,
commitInfo: graphqlClient
treeEntries,
commitInfo: graphqlClient
.query({
query: TreePageCommitInfoQuery,
variables: {
repoID: resolvedRevision.repo.id,
commitID: resolvedRevision.commitID,
filePath: params.path,
first: null,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Unable to load repository')
}
return result.data.node.commit?.tree ?? null
}),
readme: treeEntries.then(result => {
if (!result) {
return null
}
const readme = findReadme(result.entries)
if (!readme) {
return null
}
return graphqlClient
.query({
query: TreePageCommitInfoQuery,
query: TreePageReadmeQuery,
variables: {
repoID: resolvedRevision.repo.id,
commitID: resolvedRevision.commitID,
filePath: params.path,
first: null,
revspec: resolvedRevision.commitID,
path: readme.path,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Unable to load repository')
throw new Error('Expected Repository')
}
return result.data.node.commit?.tree ?? null
}),
readme: treeEntries.then(result => {
if (!result) {
return null
}
const readme = findReadme(result.entries)
if (!readme) {
return null
}
return graphqlClient
.query({
query: TreePageReadmeQuery,
variables: {
repoID: resolvedRevision.repo.id,
revspec: resolvedRevision.commitID,
path: readme.path,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Expected Repository')
}
return result.data.node.commit?.blob ?? null
})
}),
},
return result.data.node.commit?.blob ?? null
})
}),
}
}

View File

@ -11,7 +11,7 @@ export const load: LayoutLoad = async ({ parent }) => {
const { resolvedRevisionOrError } = await parent()
if (isErrorLike(resolvedRevisionOrError)) {
throw error(404, resolvedRevisionOrError)
error(404, resolvedRevisionOrError)
}
return {

View File

@ -9,7 +9,7 @@
export let data: PageData
const { pending, value: branches, set } = createPromiseStore<GitBranchesOverview>()
$: set(data.deferred.overview)
$: set(data.overview)
$: defaultBranch = $branches?.defaultBranch
$: activeBranches = $branches?.branches.nodes.filter(branch => branch.id !== defaultBranch?.id)
</script>

View File

@ -4,22 +4,20 @@ import { GitBranchesOverviewQuery } from './page.gql'
export const load: PageLoad = async ({ parent }) => {
const { resolvedRevision, graphqlClient } = await parent()
return {
deferred: {
overview: graphqlClient
.query({
query: GitBranchesOverviewQuery,
variables: {
first: 20,
repo: resolvedRevision.repo.id,
withBehindAhead: true,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Expected Repository')
}
return result.data.node
}),
},
overview: graphqlClient
.query({
query: GitBranchesOverviewQuery,
variables: {
first: 20,
repo: resolvedRevision.repo.id,
withBehindAhead: true,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Expected Repository')
}
return result.data.node
}),
}
}

View File

@ -9,7 +9,7 @@
export let data: PageData
const { pending, value: connection, set } = createPromiseStore<GitBranchesConnection>()
$: set(data.deferred.branches)
$: set(data.branches)
$: nodes = $connection?.nodes
$: totalCount = $connection?.totalCount
</script>

View File

@ -4,22 +4,20 @@ import { GitBranchesQuery } from './page.gql'
export const load: PageLoad = async ({ parent }) => {
const { resolvedRevision, graphqlClient } = await parent()
return {
deferred: {
branches: graphqlClient
.query({
query: GitBranchesQuery,
variables: {
repo: resolvedRevision.repo.id,
first: 20,
withBehindAhead: true,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Expected Repository')
}
return result.data.node.branches
}),
},
branches: graphqlClient
.query({
query: GitBranchesQuery,
variables: {
repo: resolvedRevision.repo.id,
first: 20,
withBehindAhead: true,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Expected Repository')
}
return result.data.node.branches
}),
}
}

View File

@ -21,7 +21,7 @@
]
const { pending, latestValue: contributorConnection, set } = createPromiseStore<ContributorConnection | null>()
$: set(data.deferred.contributors)
$: set(data.contributors)
// We want to show stale contributors data when the user navigates to
// the next or previous page for the current time period. When the user

View File

@ -32,8 +32,6 @@ export const load: PageLoad = async ({ url, parent }) => {
})
return {
after: afterDate,
deferred: {
contributors,
},
contributors,
}
}

View File

@ -9,7 +9,7 @@
export let data: PageData
const { pending, value: connection, set } = createPromiseStore<GitTagsConnection>()
$: set(data.deferred.tags)
$: set(data.tags)
$: nodes = $connection?.nodes
$: total = $connection?.totalCount

View File

@ -5,22 +5,20 @@ export const load: PageLoad = async ({ parent }) => {
const { resolvedRevision, graphqlClient } = await parent()
return {
deferred: {
tags: graphqlClient
.query({
query: GitTagsQuery,
variables: {
repo: resolvedRevision.repo.id,
first: 20,
withBehindAhead: false,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Expected Repository')
}
return result.data.node.gitRefs
}),
},
tags: graphqlClient
.query({
query: GitTagsQuery,
variables: {
repo: resolvedRevision.repo.id,
first: 20,
withBehindAhead: false,
},
})
.then(result => {
if (result.data.node?.__typename !== 'Repository') {
throw new Error('Expected Repository')
}
return result.data.node.gitRefs
}),
}
}

View File

@ -50,7 +50,7 @@ export const load: LayoutLoad = async ({ parent, params, url, depends }) => {
// Let revision errors be handled by the nested layout so that we can
// still render the main repo navigation and header
if (!isRevisionNotFoundErrorLike(repoError)) {
throw error(400, asError(repoError))
error(400, asError(repoError))
}
resolvedRevisionOrError = asError(repoError)

View File

@ -2,7 +2,7 @@ import { faker } from '@faker-js/faker'
import { cleanup } from '@testing-library/svelte'
import { vi, beforeEach, afterEach, beforeAll, afterAll } from 'vitest'
import { mockedContexts } from '$mocks'
import { mockedContexts } from '$testing/mocks'
type SvelteAPI = typeof import('svelte')

View File

@ -1,7 +1,7 @@
import { join } from 'path'
import staticAdapter from '@sveltejs/adapter-static'
import { vitePreprocess } from '@sveltejs/kit/vite'
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
let adapter
@ -52,19 +52,10 @@ const config = {
alias: {
// Makes it easier to refer to files outside packages (such as images)
$root: '../../',
$mocks: 'src/testing/mocks.ts',
// Used inside tests for easy access to helpers
$testing: 'src/testing',
// Map node-module to browser version
path: '../../node_modules/path-browserify',
// These are directories and cannot be imported from directly in
// production build. Need to import from _esm5, otherwise there will
// be runtime compatibility issues.
'rxjs/operators': '../../node_modules/rxjs/_esm5/operators/index',
'rxjs/fetch': '../../node_modules/rxjs/_esm5/fetch/index',
// Without it prod build doesnt work
'@apollo/client$': '../../node_modules/@apollo/client/index.js',
lodash: './node_modules/lodash-es',
},
typescript: {
config: config => {

View File

@ -19,8 +19,9 @@ export default defineConfig(({ mode }) => {
? {}
: {
'process.platform': '"browser"',
'process.env.VITEST': 'undefined',
'process.env': '({})',
'process.env.VITEST': 'null',
'process.env.NODE_ENV': `"${mode}"`,
'process.env': '{}',
},
css: {
preprocessorOptions: {
@ -71,6 +72,12 @@ export default defineConfig(({ mode }) => {
find: /^(.*)\.gql$/,
replacement: '$1.gql.ts',
},
// These are directories and cannot be imported from directly in
// production build.
{
find: /^rxjs\/(operators|fetch)$/,
replacement: 'rxjs/$1/index.js',
},
],
},
@ -85,6 +92,15 @@ export default defineConfig(({ mode }) => {
setupFiles: './src/testing/setup.ts',
include: ['src/**/*.test.ts'],
},
legacy: {
// Our existing codebase imports many CommonJS modules as if they were ES modules. The default
// Vite 5 behavior doesn't work with this. Enabling this should be OK since we don't
// actually use SSR at the moment, so the difference between the dev and prod builds don't matter.
// We should revisit this at some point though.
// See https://vitejs.dev/guide/migration.html#ssr-externalized-modules-value-now-matches-production
proxySsrExternalModules: true,
},
}
if (process.env.BAZEL) {

View File

@ -361,7 +361,7 @@
"linguist-languages": "^7.14.0",
"linkifyjs": "^4.1.0",
"lit-html": "^2.7.2",
"lodash": "^4.17.20",
"lodash": "npm:lodash-es@^4.17.21",
"lru-cache": "^7.8.0",
"marked": "4.0.16",
"mdi-react": "^8.1.0",

File diff suppressed because it is too large Load Diff