Skip to content

Commit 2eb0ff5

Browse files
authored
feat: improve vite DX (#18937)
1 parent bb8251b commit 2eb0ff5

File tree

25 files changed

+202
-112
lines changed

25 files changed

+202
-112
lines changed

cli/lib/util.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,17 @@ const getApplicationDataFolder = (...paths) => {
252252
const { env } = process
253253

254254
// allow overriding the app_data folder
255-
const folder = env.CYPRESS_KONFIG_ENV || env.CYPRESS_INTERNAL_ENV || 'development'
255+
let folder = env.CYPRESS_KONFIG_ENV || env.CYPRESS_INTERNAL_ENV || 'development'
256256

257257
const PRODUCT_NAME = pkg.productName || pkg.name
258258
const OS_DATA_PATH = ospath.data()
259259

260260
const ELECTRON_APP_DATA_PATH = path.join(OS_DATA_PATH, PRODUCT_NAME)
261261

262+
if (process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF) {
263+
folder = `${folder}-e2e-test`
264+
}
265+
262266
const p = path.join(ELECTRON_APP_DATA_PATH, 'cy', folder, ...paths)
263267

264268
return p

packages/data-context/src/actions/DevActions.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,18 @@ export class DevActions {
3232
}
3333
}
3434

35+
// In a setTimeout so that we flush the triggering response to the client before sending
3536
triggerRelaunch () {
36-
return this.ctx.fs.writeFile(DevActions.CY_TRIGGER_UPDATE, JSON.stringify(new Date()))
37+
setTimeout(async () => {
38+
try {
39+
await this.ctx.destroy()
40+
} catch (e) {
41+
this.ctx.logError(e)
42+
} finally {
43+
process.exitCode = 0
44+
await this.ctx.fs.writeFile(DevActions.CY_TRIGGER_UPDATE, JSON.stringify(new Date()))
45+
}
46+
}, 10)
3747
}
3848

3949
dismissRelaunch () {

packages/data-context/src/sources/EnvDataSource.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@ import type { DataContext } from '../DataContext'
22

33
/**
44
* Centralizes all of the "env"
5+
* TODO: see if/how we might want to use this?
56
*/
67
export class EnvDataSource {
78
constructor (private ctx: DataContext) {}
8-
9-
get CYPRESS_INTERNAL_VITE_APP_PORT () {
10-
return process.env.CYPRESS_INTERNAL_VITE_APP_PORT
11-
}
129
}

packages/data-context/src/sources/HtmlDataSource.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,32 @@ import { getPathToDist } from '@packages/resolve-dist'
99
export class HtmlDataSource {
1010
constructor (private ctx: DataContext) {}
1111

12-
async fetchAppInitialData () {
12+
async fetchLaunchpadInitialData () {
1313
const graphql = this.ctx.graphqlClient()
1414

1515
await Promise.all([
16+
graphql.executeQuery('HeaderBar_HeaderBarQueryDocument', {}),
1617
graphql.executeQuery('AppQueryDocument', {}),
18+
graphql.executeQuery('MainLaunchpadQueryDocument', {}),
19+
])
20+
21+
return graphql.getSSRData()
22+
}
23+
24+
async fetchAppInitialData () {
25+
const graphql = this.ctx.graphqlClient()
26+
27+
await Promise.all([
1728
graphql.executeQuery('SettingsDocument', {}),
1829
graphql.executeQuery('SpecsPageContainerDocument', {}),
1930
graphql.executeQuery('HeaderBar_HeaderBarQueryDocument', {}),
31+
graphql.executeQuery('SideBarNavigationDocument', {}),
2032
])
2133

2234
return graphql.getSSRData()
2335
}
2436

2537
async fetchAppHtml () {
26-
if (this.ctx.env.CYPRESS_INTERNAL_VITE_APP_PORT) {
27-
const response = await this.ctx.util.fetch(`http://localhost:${process.env.CYPRESS_INTERNAL_VITE_APP_PORT}/`, { method: 'GET' })
28-
const html = await response.text()
29-
30-
return html
31-
}
32-
3338
return this.ctx.fs.readFile(getPathToDist('app', 'index.html'), 'utf8')
3439
}
3540

packages/data-context/src/sources/VersionsDataSource.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ interface VersionData {
1212
}
1313

1414
export class VersionsDataSource {
15+
static result: undefined | Promise<string>
16+
1517
/**
1618
* Returns most recent and current version of Cypress
1719
* {
@@ -27,9 +29,12 @@ export class VersionsDataSource {
2729
*/
2830
async versions (): Promise<VersionData> {
2931
const currentCypressVersion = require('@packages/root')
30-
const result = await execa(`npm`, [`view`, `cypress`, `time`, `--json`])
3132

32-
const json = JSON.parse(result.stdout)
33+
VersionsDataSource.result ??= execa(`npm`, [`view`, `cypress`, `time`, `--json`]).then((result) => result.stdout)
34+
35+
const result = await VersionsDataSource.result
36+
37+
const json = JSON.parse(result)
3338

3439
delete json['modified']
3540
delete json['created']
@@ -50,7 +55,7 @@ export class VersionsDataSource {
5055
latest: latestVersion,
5156
current: {
5257
version: currentCypressVersion.version,
53-
released: currentCypressVersion.version === '0.0.0-development' ? new Date().toISOString() : json[currentCypressVersion.version],
58+
released: json[currentCypressVersion.version] ?? new Date().toISOString(),
5459
id: currentCypressVersion.version,
5560
},
5661
}

packages/frontend-shared/cypress/e2e/e2ePluginSetup.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ chai.use(sinonChai)
3030

3131
export async function e2ePluginSetup (projectRoot: string, on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) {
3232
process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF = 'true'
33+
delete process.env.CYPRESS_INTERNAL_GRAPHQL_PORT
34+
delete process.env.CYPRESS_INTERNAL_VITE_DEV
35+
delete process.env.CYPRESS_INTERNAL_VITE_APP_PORT
36+
delete process.env.CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT
3337
// require'd so we don't import the types from @packages/server which would
3438
// pollute strict type checking
3539
const { runInternalServer } = require('@packages/server/lib/modes/internal-server')

packages/frontend-shared/cypress/fixtures/config.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@
4747
{
4848
"value": {
4949
"INTERNAL_CLOUD_ENV": "staging",
50-
"INTERNAL_VITE_APP_PORT": 3333,
51-
"INTERNAL_VITE_LAUNCHPAD_PORT": 3001,
5250
"KONFIG_ENV": "staging"
5351
},
5452
"field": "env",

packages/frontend-shared/src/gql-components/topnav/TopNav.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,10 @@ const promptsEl: Ref<HTMLElement | null> = ref(null)
292292
// so it doesn't reopen on the one of the prompts
293293
294294
const versions = computed(() => {
295+
if (!props.gql.versions) {
296+
return null
297+
}
298+
295299
return {
296300
current: {
297301
released: useTimeAgo(new Date(props.gql.versions.current.released)).value,
@@ -305,7 +309,7 @@ const versions = computed(() => {
305309
})
306310
307311
const runningOldVersion = computed(() => {
308-
return props.gql.versions.current.released < props.gql.versions.latest.released
312+
return props.gql.versions ? props.gql.versions.current.released < props.gql.versions.latest.released : false
309313
})
310314
311315
onClickOutside(promptsEl, () => {

packages/frontend-shared/src/graphql/urqlClient.ts

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,37 @@ declare global {
3535
}
3636
}
3737

38-
export function makeUrqlClient (target: 'launchpad' | 'app'): Client {
39-
let gqlPort: string
40-
38+
function gqlPort () {
4139
if (GQL_PORT_MATCH) {
42-
gqlPort = GQL_PORT_MATCH[1]
43-
} else if (window.__CYPRESS_GRAPHQL_PORT__) {
44-
gqlPort = window.__CYPRESS_GRAPHQL_PORT__
45-
} else {
46-
throw new Error(`${window.location.href} cannot be visited without a gqlPort`)
40+
return GQL_PORT_MATCH[1]
41+
}
42+
43+
if (window.__CYPRESS_GRAPHQL_PORT__) {
44+
return window.__CYPRESS_GRAPHQL_PORT__
4745
}
4846

49-
const GRAPHQL_URL = `http://localhost:${gqlPort}/graphql`
47+
throw new Error(`${window.location.href} cannot be visited without a gqlPort`)
48+
}
49+
50+
export async function preloadLaunchpadData () {
51+
try {
52+
const resp = await fetch(`http://localhost:${gqlPort()}/__cypress/launchpad-preload`)
53+
54+
window.__CYPRESS_INITIAL_DATA__ = await resp.json()
55+
} catch {
56+
//
57+
}
58+
}
59+
60+
export function makeUrqlClient (target: 'launchpad' | 'app'): Client {
61+
const port = gqlPort()
62+
63+
const GRAPHQL_URL = `http://localhost:${port}/graphql`
5064

5165
// If we're in the launchpad, we connect to the known GraphQL Socket port,
5266
// otherwise we connect to the /__socket.io of the current domain, unless we've explicitly
5367
//
54-
const io = getPubSubSource({ target, gqlPort, serverPort: SERVER_PORT_MATCH?.[1] })
68+
const io = getPubSubSource({ target, gqlPort: port, serverPort: SERVER_PORT_MATCH?.[1] })
5569

5670
const exchanges: Exchange[] = [
5771
dedupExchange,
@@ -67,32 +81,29 @@ export function makeUrqlClient (target: 'launchpad' | 'app'): Client {
6781
${error.stack ?? ''}
6882
`
6983

70-
toast.error(message, {
71-
timeout: false,
72-
})
84+
if (process.env.NODE_ENV !== 'production') {
85+
toast.error(message, {
86+
timeout: false,
87+
})
88+
}
89+
7390
// eslint-disable-next-line
7491
console.error(error)
7592
},
7693
}),
7794
// https://formidable.com/open-source/urql/docs/graphcache/errors/
7895
makeCacheExchange(),
96+
ssrExchange({
97+
isClient: true,
98+
initialState: window.__CYPRESS_INITIAL_DATA__ ?? {},
99+
}),
79100
namedRouteExchange,
80101
// TODO(tim): add this when we want to use the socket as the GraphQL
81102
// transport layer for all operations
82103
// target === 'launchpad' ? fetchExchange : socketExchange(io),
83104
fetchExchange,
84105
]
85106

86-
// If we're in the launched app, we want to use the SSR exchange
87-
if (target === 'app') {
88-
exchanges.push(ssrExchange({
89-
isClient: true,
90-
initialState: window.__CYPRESS_INITIAL_DATA__ ?? {},
91-
}))
92-
}
93-
94-
exchanges.push(fetchExchange)
95-
96107
if (import.meta.env.DEV) {
97108
exchanges.unshift(devtoolsExchange)
98109
}

packages/frontend-shared/src/graphql/urqlExchangeNamedRoute.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ export const namedRouteExchange: Exchange = ({ client, forward }) => {
66
return forward(pipe(
77
ops$,
88
map((o) => {
9+
if (o.context.requestPolicy === 'cache-first' || o.context.requestPolicy === 'cache-only') {
10+
return o
11+
}
12+
913
if (!o.context.url.endsWith('/graphql')) {
1014
throw new Error(`Infinite loop detected? Ping @tgriesser to help debug`)
1115
}

packages/graphql/schemas/schema.graphql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ type Query {
608608
projects: [ProjectLike!]!
609609

610610
"""Previous versions of cypress and their release date"""
611-
versions: VersionData!
611+
versions: VersionData
612612

613613
"""Metadata about the wizard, null if we arent showing the wizard"""
614614
wizard: Wizard!

packages/graphql/src/schemaTypes/objectTypes/gql-Query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const Query = objectType({
2727
resolve: (root, args, ctx) => ctx.coreData.dev,
2828
})
2929

30-
t.nonNull.field('versions', {
30+
t.field('versions', {
3131
type: VersionData,
3232
description: 'Previous versions of cypress and their release date',
3333
resolve: (root, args, ctx) => {

packages/launchpad/src/App.vue

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,20 @@ let isShowingRelaunch = ref(false)
6262
const toast = useToast()
6363
6464
watch(query.data, () => {
65-
if (query.data.value?.dev.needsRelaunch && !isShowingRelaunch.value) {
66-
isShowingRelaunch.value = true
67-
toast.info('Server updated, click to relaunch', {
68-
timeout: false,
69-
onClick: () => {
70-
relaunchMutation.executeMutation({ action: 'trigger' })
71-
},
72-
onClose: () => {
73-
isShowingRelaunch.value = false
74-
relaunchMutation.executeMutation({ action: 'dismiss' })
75-
},
76-
})
65+
if (process.env.NODE_ENV !== 'production') {
66+
if (query.data.value?.dev.needsRelaunch && !isShowingRelaunch.value) {
67+
isShowingRelaunch.value = true
68+
toast.info('Server updated, click to relaunch', {
69+
timeout: false,
70+
onClick: () => {
71+
relaunchMutation.executeMutation({ action: 'trigger' })
72+
},
73+
onClose: () => {
74+
isShowingRelaunch.value = false
75+
relaunchMutation.executeMutation({ action: 'dismiss' })
76+
},
77+
})
78+
}
7779
}
7880
})
7981

packages/launchpad/src/error/BaseError.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ const props = defineProps<{
9797
const latestOperation = window.localStorage.getItem('latestGQLOperation')
9898
9999
const retry = async () => {
100-
const { launchpadClient } = await import('../main')
100+
const { getLaunchpadClient } = await import('../main')
101+
const launchpadClient = getLaunchpadClient()
101102
102103
const op = latestOperation ? JSON.parse(latestOperation) : null
103104

packages/launchpad/src/main.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,42 @@
11
import { createApp } from 'vue'
22
import './main.scss'
33
import 'virtual:windi.css'
4-
import urql from '@urql/vue'
4+
import urql, { Client } from '@urql/vue'
55
import App from './App.vue'
66
import Toast, { POSITION } from 'vue-toastification'
77
import 'vue-toastification/dist/index.css'
8-
import { makeUrqlClient } from '@packages/frontend-shared/src/graphql/urqlClient'
8+
import { makeUrqlClient, preloadLaunchpadData } from '@packages/frontend-shared/src/graphql/urqlClient'
99
import { createI18n } from '@cy/i18n'
1010
import { initHighlighter } from '@cy/components/ShikiHighlight.vue'
1111

1212
const app = createApp(App)
1313

14-
export const launchpadClient = makeUrqlClient('launchpad')
15-
1614
app.use(Toast, {
1715
position: POSITION.BOTTOM_RIGHT,
1816
})
1917

20-
app.use(urql, launchpadClient)
2118
app.use(createI18n())
2219

20+
let launchpadClient: Client
21+
22+
// TODO: (tim) remove this when we refactor to remove the retry plugin logic
23+
export function getLaunchpadClient () {
24+
if (!launchpadClient) {
25+
throw new Error(`Cannot access launchpadClient before app has been init`)
26+
}
27+
28+
return launchpadClient
29+
}
30+
2331
// Make sure highlighter is initialized before
2432
// we show any code to avoid jank at rendering
25-
// @ts-ignore
26-
initHighlighter().then(() => {
33+
Promise.all([
34+
// @ts-ignore
35+
initHighlighter(),
36+
preloadLaunchpadData(),
37+
]).then(() => {
38+
launchpadClient = makeUrqlClient('launchpad')
39+
app.use(urql, launchpadClient)
40+
2741
app.mount('#app')
2842
})

packages/resolve-dist/lib/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ export const getPathToIndex = (pkg: RunnerPkg) => {
2121
}
2222

2323
export const getPathToDesktopIndex = (pkg: 'desktop-gui' | 'launchpad', graphqlPort?: number) => {
24-
// For now, if we see that there's a CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT
24+
// For now, if we see that there's a CYPRESS_INTERNAL_VITE_DEV
2525
// we assume we're running Cypress targeting that (dev server)
26-
if (pkg === 'launchpad' && process.env.CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT) {
26+
if (pkg === 'launchpad' && process.env.CYPRESS_INTERNAL_VITE_DEV) {
2727
return `http://localhost:${process.env.CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT}?gqlPort=${graphqlPort}`
2828
}
2929

0 commit comments

Comments
 (0)