Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve vite DX #18937

Merged
merged 12 commits into from
Nov 17, 2021
6 changes: 5 additions & 1 deletion cli/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,13 +252,17 @@ const getApplicationDataFolder = (...paths) => {
const { env } = process

// allow overriding the app_data folder
const folder = env.CYPRESS_KONFIG_ENV || env.CYPRESS_INTERNAL_ENV || 'development'
let folder = env.CYPRESS_KONFIG_ENV || env.CYPRESS_INTERNAL_ENV || 'development'

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

const ELECTRON_APP_DATA_PATH = path.join(OS_DATA_PATH, PRODUCT_NAME)

if (process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF) {
folder = `${folder}-e2e-test`
}

const p = path.join(ELECTRON_APP_DATA_PATH, 'cy', folder, ...paths)

return p
Expand Down
12 changes: 11 additions & 1 deletion packages/data-context/src/actions/DevActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,18 @@ export class DevActions {
}
}

// In a setTimeout so that we flush the triggering response to the client before sending
triggerRelaunch () {
return this.ctx.fs.writeFile(DevActions.CY_TRIGGER_UPDATE, JSON.stringify(new Date()))
setTimeout(async () => {
try {
await this.ctx.destroy()
} catch (e) {
this.ctx.logError(e)
} finally {
process.exitCode = 0
await this.ctx.fs.writeFile(DevActions.CY_TRIGGER_UPDATE, JSON.stringify(new Date()))
}
}, 10)
}

dismissRelaunch () {
Expand Down
5 changes: 1 addition & 4 deletions packages/data-context/src/sources/EnvDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ import type { DataContext } from '../DataContext'

/**
* Centralizes all of the "env"
* TODO: see if/how we might want to use this?
*/
export class EnvDataSource {
constructor (private ctx: DataContext) {}

get CYPRESS_INTERNAL_VITE_APP_PORT () {
return process.env.CYPRESS_INTERNAL_VITE_APP_PORT
}
}
21 changes: 13 additions & 8 deletions packages/data-context/src/sources/HtmlDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,32 @@ import { getPathToDist } from '@packages/resolve-dist'
export class HtmlDataSource {
constructor (private ctx: DataContext) {}

async fetchAppInitialData () {
async fetchLaunchpadInitialData () {
const graphql = this.ctx.graphqlClient()

await Promise.all([
graphql.executeQuery('HeaderBar_HeaderBarQueryDocument', {}),
graphql.executeQuery('AppQueryDocument', {}),
graphql.executeQuery('MainLaunchpadQueryDocument', {}),
])

return graphql.getSSRData()
}

async fetchAppInitialData () {
const graphql = this.ctx.graphqlClient()

await Promise.all([
graphql.executeQuery('SettingsDocument', {}),
graphql.executeQuery('SpecsPageContainerDocument', {}),
graphql.executeQuery('HeaderBar_HeaderBarQueryDocument', {}),
graphql.executeQuery('SideBarNavigationDocument', {}),
])

return graphql.getSSRData()
}

async fetchAppHtml () {
if (this.ctx.env.CYPRESS_INTERNAL_VITE_APP_PORT) {
const response = await this.ctx.util.fetch(`http://localhost:${process.env.CYPRESS_INTERNAL_VITE_APP_PORT}/`, { method: 'GET' })
const html = await response.text()

return html
}

return this.ctx.fs.readFile(getPathToDist('app', 'index.html'), 'utf8')
}

Expand Down
11 changes: 8 additions & 3 deletions packages/data-context/src/sources/VersionsDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ interface VersionData {
}

export class VersionsDataSource {
static result: undefined | Promise<string>

/**
* Returns most recent and current version of Cypress
* {
Expand All @@ -27,9 +29,12 @@ export class VersionsDataSource {
*/
async versions (): Promise<VersionData> {
const currentCypressVersion = require('@packages/root')
const result = await execa(`npm`, [`view`, `cypress`, `time`, `--json`])

const json = JSON.parse(result.stdout)
VersionsDataSource.result ??= execa(`npm`, [`view`, `cypress`, `time`, `--json`]).then((result) => result.stdout)

const result = await VersionsDataSource.result

const json = JSON.parse(result)

delete json['modified']
delete json['created']
Expand All @@ -50,7 +55,7 @@ export class VersionsDataSource {
latest: latestVersion,
current: {
version: currentCypressVersion.version,
released: currentCypressVersion.version === '0.0.0-development' ? new Date().toISOString() : json[currentCypressVersion.version],
released: json[currentCypressVersion.version] ?? new Date().toISOString(),
id: currentCypressVersion.version,
},
}
Expand Down
4 changes: 4 additions & 0 deletions packages/frontend-shared/cypress/e2e/e2ePluginSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ chai.use(sinonChai)

export async function e2ePluginSetup (projectRoot: string, on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) {
process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF = 'true'
delete process.env.CYPRESS_INTERNAL_GRAPHQL_PORT
delete process.env.CYPRESS_INTERNAL_VITE_DEV
delete process.env.CYPRESS_INTERNAL_VITE_APP_PORT
delete process.env.CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT
// require'd so we don't import the types from @packages/server which would
// pollute strict type checking
const { runInternalServer } = require('@packages/server/lib/modes/internal-server')
Expand Down
2 changes: 0 additions & 2 deletions packages/frontend-shared/cypress/fixtures/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@
{
"value": {
"INTERNAL_CLOUD_ENV": "staging",
"INTERNAL_VITE_APP_PORT": 3333,
"INTERNAL_VITE_LAUNCHPAD_PORT": 3001,
"KONFIG_ENV": "staging"
},
"field": "env",
Expand Down
4 changes: 4 additions & 0 deletions packages/frontend-shared/src/gql-components/topnav/TopNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,10 @@ const promptsEl: Ref<HTMLElement | null> = ref(null)
// so it doesn't reopen on the one of the prompts

const versions = computed(() => {
if (!props.gql.versions) {
return null
}

return {
current: {
released: useTimeAgo(new Date(props.gql.versions.current.released)).value,
Expand Down
57 changes: 34 additions & 23 deletions packages/frontend-shared/src/graphql/urqlClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,37 @@ declare global {
}
}

export function makeUrqlClient (target: 'launchpad' | 'app'): Client {
let gqlPort: string

function gqlPort () {
if (GQL_PORT_MATCH) {
gqlPort = GQL_PORT_MATCH[1]
} else if (window.__CYPRESS_GRAPHQL_PORT__) {
gqlPort = window.__CYPRESS_GRAPHQL_PORT__
} else {
throw new Error(`${window.location.href} cannot be visited without a gqlPort`)
return GQL_PORT_MATCH[1]
}

if (window.__CYPRESS_GRAPHQL_PORT__) {
return window.__CYPRESS_GRAPHQL_PORT__
}

const GRAPHQL_URL = `http://localhost:${gqlPort}/graphql`
throw new Error(`${window.location.href} cannot be visited without a gqlPort`)
}

export async function preloadLaunchpadData () {
try {
const resp = await fetch(`http://localhost:${gqlPort()}/__cypress/launchpad-preload`)

window.__CYPRESS_INITIAL_DATA__ = await resp.json()
} catch {
//
}
}

export function makeUrqlClient (target: 'launchpad' | 'app'): Client {
const port = gqlPort()

const GRAPHQL_URL = `http://localhost:${port}/graphql`

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

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

toast.error(message, {
timeout: false,
})
if (process.env.NODE_ENV !== 'production') {
toast.error(message, {
timeout: false,
})
}

// eslint-disable-next-line
console.error(error)
},
}),
// https://formidable.com/open-source/urql/docs/graphcache/errors/
makeCacheExchange(),
ssrExchange({
isClient: true,
initialState: window.__CYPRESS_INITIAL_DATA__ ?? {},
}),
namedRouteExchange,
// TODO(tim): add this when we want to use the socket as the GraphQL
// transport layer for all operations
// target === 'launchpad' ? fetchExchange : socketExchange(io),
fetchExchange,
]

// If we're in the launched app, we want to use the SSR exchange
if (target === 'app') {
exchanges.push(ssrExchange({
isClient: true,
initialState: window.__CYPRESS_INITIAL_DATA__ ?? {},
}))
}

exchanges.push(fetchExchange)

if (import.meta.env.DEV) {
exchanges.unshift(devtoolsExchange)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql/schemas/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ type Query {
projects: [ProjectLike!]!

"""Previous versions of cypress and their release date"""
versions: VersionData!
versions: VersionData

"""Metadata about the wizard, null if we arent showing the wizard"""
wizard: Wizard!
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql/src/schemaTypes/objectTypes/gql-Query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const Query = objectType({
resolve: (root, args, ctx) => ctx.coreData.dev,
})

t.nonNull.field('versions', {
t.field('versions', {
type: VersionData,
description: 'Previous versions of cypress and their release date',
resolve: (root, args, ctx) => {
Expand Down
26 changes: 14 additions & 12 deletions packages/launchpad/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,20 @@ let isShowingRelaunch = ref(false)
const toast = useToast()

watch(query.data, () => {
if (query.data.value?.dev.needsRelaunch && !isShowingRelaunch.value) {
isShowingRelaunch.value = true
toast.info('Server updated, click to relaunch', {
timeout: false,
onClick: () => {
relaunchMutation.executeMutation({ action: 'trigger' })
},
onClose: () => {
isShowingRelaunch.value = false
relaunchMutation.executeMutation({ action: 'dismiss' })
},
})
if (process.env.NODE_ENV !== 'production') {
if (query.data.value?.dev.needsRelaunch && !isShowingRelaunch.value) {
isShowingRelaunch.value = true
toast.info('Server updated, click to relaunch', {
timeout: false,
onClick: () => {
relaunchMutation.executeMutation({ action: 'trigger' })
},
onClose: () => {
isShowingRelaunch.value = false
relaunchMutation.executeMutation({ action: 'dismiss' })
},
})
}
}
})

Expand Down
3 changes: 2 additions & 1 deletion packages/launchpad/src/error/BaseError.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ const props = defineProps<{
const latestOperation = window.localStorage.getItem('latestGQLOperation')

const retry = async () => {
const { launchpadClient } = await import('../main')
const { getLaunchpadClient } = await import('../main')
const launchpadClient = getLaunchpadClient()

const op = latestOperation ? JSON.parse(latestOperation) : null

Expand Down
28 changes: 21 additions & 7 deletions packages/launchpad/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
import { createApp } from 'vue'
import './main.scss'
import 'virtual:windi.css'
import urql from '@urql/vue'
import urql, { Client } from '@urql/vue'
import App from './App.vue'
import Toast, { POSITION } from 'vue-toastification'
import 'vue-toastification/dist/index.css'
import { makeUrqlClient } from '@packages/frontend-shared/src/graphql/urqlClient'
import { makeUrqlClient, preloadLaunchpadData } from '@packages/frontend-shared/src/graphql/urqlClient'
import { createI18n } from '@cy/i18n'
import { initHighlighter } from '@cy/components/ShikiHighlight.vue'

const app = createApp(App)

export const launchpadClient = makeUrqlClient('launchpad')

app.use(Toast, {
position: POSITION.BOTTOM_RIGHT,
})

app.use(urql, launchpadClient)
app.use(createI18n())

let launchpadClient: Client

// TODO: (tim) remove this when we refactor to remove the retry plugin logic
export function getLaunchpadClient () {
if (!launchpadClient) {
throw new Error(`Cannot access launchpadClient before app has been init`)
}

return launchpadClient
}

// Make sure highlighter is initialized before
// we show any code to avoid jank at rendering
// @ts-ignore
initHighlighter().then(() => {
Promise.all([
// @ts-ignore
initHighlighter(),
preloadLaunchpadData(),
]).then(() => {
launchpadClient = makeUrqlClient('launchpad')
app.use(urql, launchpadClient)

app.mount('#app')
})
4 changes: 2 additions & 2 deletions packages/resolve-dist/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ export const getPathToIndex = (pkg: RunnerPkg) => {
}

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

Expand Down
9 changes: 9 additions & 0 deletions packages/server/lib/gui/makeGraphQLServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ export async function makeGraphQLServer (ctx: DataContext) {

app.use(cors())

app.get('/__cypress/launchpad-preload', (req, res) => {
ctx.html.fetchLaunchpadInitialData().then((data) => {
res.json(data)
}).catch((e) => {
ctx.logError(e)
res.json({})
})
})

// TODO: Figure out how we want to cleanup & juggle the config, so
// it's not jammed into the projects
addGraphQLHTTP(app, ctx)
Expand Down
3 changes: 2 additions & 1 deletion packages/server/lib/modes/interactive-e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import type { LaunchArgs, PlatformName } from '@packages/types'
import EventEmitter from 'events'

const isDev = () => {
return process.env['CYPRESS_INTERNAL_ENV'] === 'development'
// TODO: (tim) ensure the process.env.LAUNCHPAD gets removed before release
return Boolean(process.env['CYPRESS_INTERNAL_ENV'] === 'development' || process.env.LAUNCHPAD)
}

export = {
Expand Down
Loading