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: adding vite-dev-server plugin #14839

Merged
merged 5 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions npm/vite-dev-server/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"plugins": [
"cypress",
"@cypress/dev"
],
"extends": [
"plugin:@cypress/dev/general",
"plugin:@cypress/dev/tests",
"plugin:@cypress/dev/react"
],
"parser": "@typescript-eslint/parser",
"env": {
"cypress/globals": true
},
"globals": {
"jest": "readonly"
},
"rules": {
"no-console": "off",
"mocha/no-global-tests": "off",
"@typescript-eslint/no-unused-vars": "off",
"react/jsx-filename-extension": [
"warn",
{
"extensions": [
".js",
".jsx",
".tsx"
]
}
]
},
"overrides": [
{
"files": [
"lib/*"
],
"rules": {
"no-console": 1
}
},
{
"files": [
"**/*.json"
],
"rules": {
"quotes": "off",
"comma-dangle": "off"
}
}
]
}
3 changes: 3 additions & 0 deletions npm/vite-dev-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ⚡️ + 🌲 Cypress Component Testing w/ Vite

> **Note** this package is not meant to be used outside of cypress component testing.
7 changes: 7 additions & 0 deletions npm/vite-dev-server/cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"experimentalComponentTesting": true,
"pluginsFile": "cypress/plugins.js",
"testFiles": "**/*.spec.*",
"componentFolder": "cypress/components",
"supportFile": "cypress/support/support.js"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { isBoolean } from 'lodash'

describe('Large 3rd party library with tree-shaking', () => {
it('successfully imports isBoolean from lodash', () => {
expect(isBoolean(true)).to.be.true
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import _ from 'lodash'

describe('Large 3rd party library without tree-shaking', () => {
it('successfully imports lodash', () => {
expect(_.isBoolean(true)).to.be.true
})
})
5 changes: 5 additions & 0 deletions npm/vite-dev-server/cypress/components/smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
describe('With no imports', () => {
it('should be able to run this', () => {
expect(true).to.be.true
})
})
14 changes: 14 additions & 0 deletions npm/vite-dev-server/cypress/components/support.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
describe('Support files', () => {
it('can load a support file', () => {
const $body = Cypress.$('body')

// Visual cue to help debug
const $supportNode = Cypress.$(`<h1>Support file hasn't been loaded 😿</h1>`)

$body.append($supportNode)

// @ts-ignore
expect(window.supportFileWasLoaded).to.be.true
$supportNode.text('Support file was loaded! ⚡️')
})
})
9 changes: 9 additions & 0 deletions npm/vite-dev-server/cypress/plugins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { startDevServer } from '@cypress/vite-dev-server'

module.exports = (on, config) => {
on('dev-server:start', async (options) => {
return startDevServer({ options })
})

return config
}
3 changes: 3 additions & 0 deletions npm/vite-dev-server/cypress/support/support.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
before(() => {
window.supportFileWasLoaded = true
})
49 changes: 49 additions & 0 deletions npm/vite-dev-server/index-template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
</head>
<body>
<div id="app"></div>
<script type="module">

function appendTargetIfNotExists (id, tag = 'div', parent = document.body) {
let node = document.getElementById(id)

if (!node) {
node = document.createElement(tag)
node.setAttribute('id', id)
parent.appendChild(node)
}

node.innerHTML = ''

return node
}

let importsToLoad = [() => import("{{{specPath}}}")];
if ("{{{supportPath}}}") {
importsToLoad.push(() => import("{{{supportPath}}}"));
}

const Cypress = window.Cypress = parent.Cypress

if (!Cypress) {
throw new Error('Tests cannot run without a reference to Cypress!')
}

Cypress.onSpecWindow(window, importsToLoad)
Cypress.action('app:window:before:load', window)

beforeEach(() => {
const root = appendTargetIfNotExists('__cy_root')

root.appendChild(appendTargetIfNotExists('__cy_app'))
})

</script>
</body>
</html>
2 changes: 2 additions & 0 deletions npm/vite-dev-server/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="../../cli/types/cypress.d.ts" />
12 changes: 12 additions & 0 deletions npm/vite-dev-server/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
Root HTML for my cool app -- in the real world, this would be the user's.
This is necessary to allow Vite to start up with <pre><code>yarn vite</code></pre>
</body>
</html>
1 change: 1 addition & 0 deletions npm/vite-dev-server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist')
30 changes: 30 additions & 0 deletions npm/vite-dev-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@cypress/vite-dev-server",
"version": "0.0.0-development",
"description": "Launches Vite Dev Server for Component Testing",
"private": true,
"main": "index.js",
"scripts": {
"build": "tsc",
"build-prod": "tsc",
"cy:open": "node ../../scripts/start.js --component-testing --project ${PWD}",
"cy:run": "node ../../scripts/cypress.js open-ct --run-project ${PWD}",
"test": "yarn cy:run",
"watch": "tsc -w"
},
"dependencies": {
"debug": "4.3.2",
"mustache": "4.1.0"
},
"devDependencies": {
"@types/mustache": "4.1.1",
"vite": "2.0.0-beta.59"
},
"peerDependencies": {
"vite": ">= 2"
},
"files": [
"dist"
],
"license": "MIT"
}
37 changes: 37 additions & 0 deletions npm/vite-dev-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { EventEmitter } from 'events'
import { debug as debugFn } from 'debug'
import { start as createDevServer } from './startServer'
import { UserConfig } from 'vite'
import { Server } from 'http'
const debug = debugFn('cypress:vite-dev-server:vite')

interface Options {
JessicaSachs marked this conversation as resolved.
Show resolved Hide resolved
specs: any[] // Cypress.Cypress['spec'][] // Why isn't this working? It works for webpack-dev-server
config: Record<string, string>
devServerEvents: EventEmitter
[key: string]: unknown
}

export interface StartDevServer {
/* this is the Cypress options object */
options: Options
/* support passing a path to the user's webpack config */
viteConfig?: UserConfig // TODO: implement taking in the user's vite configuration. Right now we don't
}

export interface ResolvedDevServerConfig {
port: number
server: Server
}

export async function startDevServer (startDevServerArgs: StartDevServer): Promise<ResolvedDevServerConfig> {
const viteDevServer = await createDevServer(startDevServerArgs)

return new Promise(async (resolve) => {
const app = await viteDevServer.listen()
const port = app.config.server.port

debug('Component testing vite server started on port', port)
resolve({ port, server: app.httpServer })
})
}
41 changes: 41 additions & 0 deletions npm/vite-dev-server/src/makeCypressPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { EventEmitter } from 'events'
import { relative, resolve } from 'path'
import { readFileSync } from 'fs'
import { Plugin, ViteDevServer } from 'vite'
import { render } from 'mustache'

const pluginName = 'cypress-transform-html'
const indexHtmlPath = resolve(__dirname, '../index-template.html')
const readIndexHtml = () => readFileSync(indexHtmlPath).toString()

function handleIndex (indexHtml, projectRoot, supportFilePath, req, res) {
const specPath = `/${req.headers.__cypress_spec_path}`
const supportPath = supportFilePath ? `/${relative(projectRoot, supportFilePath)}` : null

res.end(render(indexHtml, { supportPath, specPath }))
}

export const makeCypressPlugin = (
projectRoot: string,
supportFilePath: string,
devServerEvents: EventEmitter,
): Plugin => {
return {
name: pluginName,
enforce: 'pre',
configureServer: (server: ViteDevServer) => {
const indexHtml = readIndexHtml()

server.middlewares.use('/index.html', (req, res) => handleIndex(indexHtml, projectRoot, supportFilePath, req, res))
},
handleHotUpdate: () => {
console.log('HOT UPDATE')
devServerEvents.emit('dev-server:compile:success')

return []
},
// TODO subscribe on the compile error hook and call the
// devServerEvents.emit('dev-server:compile:error', err)
// it looks like for now (02.02.2021) there is no way to subscribe to an error
}
}
27 changes: 27 additions & 0 deletions npm/vite-dev-server/src/makeHtmlPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { relative, resolve } from 'path'
import { readFileSync } from 'fs'
import { Plugin, ViteDevServer } from 'vite'
import { render } from 'mustache'

const pluginName = 'cypress-transform-html'
const indexHtmlPath = resolve(__dirname, '../index-template.html')
const readIndexHtml = () => readFileSync(indexHtmlPath).toString()

function handleIndex (indexHtml, projectRoot, supportFilePath, req, res) {
const specPath = `/${req.headers.__cypress_spec_path}`
const supportPath = supportFilePath ? `/${relative(projectRoot, supportFilePath)}` : null

res.end(render(indexHtml, { supportPath, specPath }))
}

export const makeHtmlPlugin = (projectRoot: string, supportFilePath: string): Plugin => {
return {
name: pluginName,
enforce: 'pre',
configureServer: (server: ViteDevServer) => {
const indexHtml = readIndexHtml()

server.middlewares.use('/index.html', (req, res) => handleIndex(indexHtml, projectRoot, supportFilePath, req, res))
},
}
}
54 changes: 54 additions & 0 deletions npm/vite-dev-server/src/startServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import Debug from 'debug'
import { StartDevServer } from '.'
import { createServer, ViteDevServer, InlineConfig } from 'vite'
import { resolve } from 'path'
import { makeCypressPlugin } from './makeCypressPlugin'
import { EventEmitter } from 'events'

const debug = Debug('cypress:vite-dev-server:start')

// TODO: Pull in types for Options so we can infer these
const serverConfig = (projectRoot: string, supportFilePath: string, devServerEvents: EventEmitter): InlineConfig => {
return {
root: resolve(__dirname, projectRoot),
base: '/__cypress/src/',
JessicaSachs marked this conversation as resolved.
Show resolved Hide resolved
plugins: [makeCypressPlugin(projectRoot, supportFilePath, devServerEvents)],
server: {
port: 0,
},
}
}

const resolveServerConfig = ({ viteConfig, options }: StartDevServer) => {
const defaultServerConfig = serverConfig(
options.config.projectRoot,
options.config.supportFile,
options.devServerEvents,
)

const requiredOptions = {
base: defaultServerConfig.base,
root: defaultServerConfig.root,
}

const finalConfig = { ...defaultServerConfig, ...viteConfig, ...requiredOptions }

finalConfig.plugins = [...(viteConfig.plugins || []), defaultServerConfig.plugins[0]]
finalConfig.server.port = defaultServerConfig.server.port

debug(`the resolved server config is ${JSON.stringify(finalConfig, null, 2)}`)

return finalConfig
}

export async function start (devServerOptions: StartDevServer): Promise<ViteDevServer> {
if (!devServerOptions.viteConfig) {
debug('User did not pass in any Vite dev server configuration')
devServerOptions.viteConfig = {}
}

debug('starting vite dev server')
const resolvedConfig = resolveServerConfig(devServerOptions)

return createServer(resolvedConfig)
}
Loading