From 5fd91bde018f905477221292876aafbf1d87610d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barth=C3=A9l=C3=A9my=20Ledoux?= Date: Wed, 10 Mar 2021 12:40:59 -0600 Subject: [PATCH 1/4] chore: deliver npm modules (#15398) * fix: deliver webpack-dev-server new architecture * fix: make vite-dev-server work * fix: deliver react and vite-dev-server * fix: deliver the vue2 package too * fix: rollback react changes published to next --- .../cypress/components/Foo.tsx | 5 ++ .../cypress/components/react-no-jsx.spec.jsx | 13 +++ .../cypress/components/react-no-jsx.spec.ts | 13 +++ .../cypress/components/react-no-jsx.spec.tsx | 25 ++++++ .../cypress/fixtures/example.json | 5 ++ npm/vite-dev-server/cypress/plugins.js | 4 +- npm/vite-dev-server/index-template.html | 24 +++++- npm/vite-dev-server/package.json | 10 ++- npm/vite-dev-server/src/makeCypressPlugin.ts | 1 - npm/vue/.gitignore | 1 + npm/vue/examples/cli-ts/package.json | 4 +- npm/vue/package.json | 26 ++++-- npm/vue/rollup.config.js | 86 +++++++++++++++++++ npm/vue/src/index.ts | 9 +- npm/vue/src/plugins/webpack/index.js | 1 - npm/vue/src/renderTestingPlatform.ts | 20 +++++ npm/vue/src/support.js | 40 +-------- npm/vue/tsconfig.json | 2 +- npm/vue/webpack.config.js | 3 +- npm/webpack-dev-server/index-template.html | 1 - npm/webpack-dev-server/package.json | 1 + npm/webpack-dev-server/src/aut-runner.ts | 22 +++-- npm/webpack-dev-server/src/index.ts | 21 ++--- .../src/makeWebpackConfig.ts | 46 ++++++++-- npm/webpack-dev-server/src/startServer.ts | 19 +++- npm/webpack-dev-server/src/webpack.config.js | 1 + npm/webpack-dev-server/test/e2e.spec.ts | 12 ++- yarn.lock | 28 +++--- 28 files changed, 330 insertions(+), 113 deletions(-) create mode 100644 npm/vite-dev-server/cypress/components/Foo.tsx create mode 100644 npm/vite-dev-server/cypress/components/react-no-jsx.spec.jsx create mode 100644 npm/vite-dev-server/cypress/components/react-no-jsx.spec.ts create mode 100644 npm/vite-dev-server/cypress/components/react-no-jsx.spec.tsx create mode 100644 npm/vite-dev-server/cypress/fixtures/example.json create mode 100644 npm/vue/.gitignore create mode 100644 npm/vue/rollup.config.js create mode 100644 npm/vue/src/renderTestingPlatform.ts diff --git a/npm/vite-dev-server/cypress/components/Foo.tsx b/npm/vite-dev-server/cypress/components/Foo.tsx new file mode 100644 index 000000000000..ffdd0bfb5505 --- /dev/null +++ b/npm/vite-dev-server/cypress/components/Foo.tsx @@ -0,0 +1,5 @@ +import React from 'react' + +export const Foo: React.FC = () => { + return
Hello world!!!!
+} diff --git a/npm/vite-dev-server/cypress/components/react-no-jsx.spec.jsx b/npm/vite-dev-server/cypress/components/react-no-jsx.spec.jsx new file mode 100644 index 000000000000..7b8255976344 --- /dev/null +++ b/npm/vite-dev-server/cypress/components/react-no-jsx.spec.jsx @@ -0,0 +1,13 @@ +import React from 'react' +import { mount } from '@cypress/react' + +const Comp = () => { + return
Hello world!
+} + +describe('React', () => { + it('renders a react component', () => { + mount() + cy.get('div').contains('Hello world') + }) +}) diff --git a/npm/vite-dev-server/cypress/components/react-no-jsx.spec.ts b/npm/vite-dev-server/cypress/components/react-no-jsx.spec.ts new file mode 100644 index 000000000000..dc5596642633 --- /dev/null +++ b/npm/vite-dev-server/cypress/components/react-no-jsx.spec.ts @@ -0,0 +1,13 @@ +import React from 'react' +import { mount } from '@cypress/react' + +const Comp = () => { + return React.createElement('div', {}, 'Hello world') +} + +describe('React', () => { + it('renders a react component', () => { + mount(React.createElement(Comp)) + cy.get('div').contains('Hello world') + }) +}) diff --git a/npm/vite-dev-server/cypress/components/react-no-jsx.spec.tsx b/npm/vite-dev-server/cypress/components/react-no-jsx.spec.tsx new file mode 100644 index 000000000000..12c969717333 --- /dev/null +++ b/npm/vite-dev-server/cypress/components/react-no-jsx.spec.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import { mount } from '@cypress/react' +import { Foo } from './Foo' + +describe('React', () => { + it('renders a react component #1', () => { + mount() + cy.get('div').contains('Hello world') + }) + + it('renders a react component #2', () => { + mount() + cy.get('div').contains('Hello world') + }) + + it('renders a react component #3', () => { + mount() + cy.get('div').contains('Hello world') + }) + + it('renders a react component #4', () => { + mount() + cy.get('div').contains('Hello world') + }) +}) diff --git a/npm/vite-dev-server/cypress/fixtures/example.json b/npm/vite-dev-server/cypress/fixtures/example.json new file mode 100644 index 000000000000..da18d9352a17 --- /dev/null +++ b/npm/vite-dev-server/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} \ No newline at end of file diff --git a/npm/vite-dev-server/cypress/plugins.js b/npm/vite-dev-server/cypress/plugins.js index 9764b3ffaf61..16054ce032f7 100644 --- a/npm/vite-dev-server/cypress/plugins.js +++ b/npm/vite-dev-server/cypress/plugins.js @@ -1,9 +1,7 @@ import { startDevServer } from '@cypress/vite-dev-server' module.exports = (on, config) => { - on('dev-server:start', async (options) => { - return startDevServer({ options }) - }) + on('dev-server:start', async (options) => startDevServer({ options })) return config } diff --git a/npm/vite-dev-server/index-template.html b/npm/vite-dev-server/index-template.html index 0140f725593e..d08afceb111e 100644 --- a/npm/vite-dev-server/index-template.html +++ b/npm/vite-dev-server/index-template.html @@ -24,7 +24,27 @@ return node } - let importsToLoad = [() => import("{{{specPath}}}")]; + const specPath = "{{{specPath}}}" + + let importsToLoad = [() => import(specPath).catch(e => { + // if the import failed, it might be because of dependencies + // so we try a quick refresh just in case it is + + // Since vite does not work with IE we can use URLSearchParams without polyfill + const searchParams = new URLSearchParams(window.location.search); + const r = searchParams.has("refresh") ? parseInt(searchParams.get("refresh"), 10) + 1 : 0 + // limit the number of refresh (dependency discovery depths) + // to 2 instead of 1 for React-DOM + if (r < 2) { + searchParams.set('refresh', r) + window.location.search = searchParams + } else { + throw new Error(` + **Error during compilation.** + Check the terminal log for more info +`, e) + } + })]; if ("{{{supportPath}}}") { importsToLoad.push(() => import("{{{supportPath}}}")); } @@ -38,7 +58,7 @@ Cypress.onSpecWindow(window, importsToLoad) Cypress.action('app:window:before:load', window) - beforeEach(() => { + before(() => { const root = appendTargetIfNotExists('__cy_root') root.appendChild(appendTargetIfNotExists('__cy_app')) diff --git a/npm/vite-dev-server/package.json b/npm/vite-dev-server/package.json index be2b963b0e28..a5af7dc96ffd 100644 --- a/npm/vite-dev-server/package.json +++ b/npm/vite-dev-server/package.json @@ -17,13 +17,14 @@ }, "devDependencies": { "@types/mustache": "4.1.1", - "vite": "2.0.1" + "vite": "2.0.5" }, "peerDependencies": { "vite": ">= 2" }, "files": [ - "dist" + "dist", + "index-template.html" ], "license": "MIT", "repository": { @@ -34,5 +35,8 @@ "bugs": "https://github.com/cypress-io/cypress/issues/new?template=1-bug-report.md", "publishConfig": { "access": "public" - } + }, + "ciJobs": [ + "npm-vite-dev-server" + ] } diff --git a/npm/vite-dev-server/src/makeCypressPlugin.ts b/npm/vite-dev-server/src/makeCypressPlugin.ts index cb089183f064..878a1e87a8d8 100644 --- a/npm/vite-dev-server/src/makeCypressPlugin.ts +++ b/npm/vite-dev-server/src/makeCypressPlugin.ts @@ -29,7 +29,6 @@ export const makeCypressPlugin = ( 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 [] diff --git a/npm/vue/.gitignore b/npm/vue/.gitignore new file mode 100644 index 000000000000..46a115b981fe --- /dev/null +++ b/npm/vue/.gitignore @@ -0,0 +1 @@ +cypress/videos \ No newline at end of file diff --git a/npm/vue/examples/cli-ts/package.json b/npm/vue/examples/cli-ts/package.json index 9b31f05e18f2..7d72b041dbbd 100644 --- a/npm/vue/examples/cli-ts/package.json +++ b/npm/vue/examples/cli-ts/package.json @@ -4,8 +4,8 @@ "private": true, "scripts": { "build": "vue-cli-service build", - "cy:open": "../../node_modules/.bin/cypress open", - "cy:run": "../../node_modules/.bin/cypress run", + "cy:open": "node ../../../../scripts/cypress open", + "cy:run": "node ../../../../scripts/cypress run", "serve": "vue-cli-service serve" }, "dependencies": { diff --git a/npm/vue/package.json b/npm/vue/package.json index 32da693e5a9a..092bdea6dacc 100644 --- a/npm/vue/package.json +++ b/npm/vue/package.json @@ -2,18 +2,16 @@ "name": "@cypress/vue", "version": "0.0.0-development", "description": "Browser-based Component Testing for Vue.js with Cypress.io ✌️🌲", - "main": "dist/index.js", + "main": "dist/cypress-vue.cjs.js", "scripts": { - "build": "tsc", + "build": "rimraf dist && yarn rollup -c rollup.config.js", "build-prod": "yarn build", "cy:open": "node ../../scripts/cypress.js open-ct --project ${PWD}", "cy:run": "node ../../scripts/cypress.js run-ct --project ${PWD}", "test": "yarn cy:run", - "watch": "tsc -w" + "watch": "yarn build --watch --watch.exclude ./dist/**/*" }, "dependencies": { - "@cypress/code-coverage": "3.8.1", - "@cypress/webpack-dev-server": "0.0.0-development", "@vue/test-utils": "1.0.3", "unfetch": "4.1.0" }, @@ -21,7 +19,11 @@ "@babel/core": "7.9.0", "@babel/plugin-transform-modules-commonjs": "7.10.4", "@babel/preset-env": "7.9.5", + "@cypress/code-coverage": "3.8.1", + "@cypress/webpack-dev-server": "0.0.0-development", "@intlify/vue-i18n-loader": "1.0.0", + "@rollup/plugin-commonjs": "^17.1.0", + "@rollup/plugin-node-resolve": "^11.1.1", "@vue/cli-plugin-babel": "~4.4.0", "@vue/cli-service": "~4.4.0", "axios": "0.19.2", @@ -33,6 +35,9 @@ "eslint-plugin-vue": "^6.2.2", "find-webpack": "2.1.0", "mocha": "7.1.1", + "rollup": "^2.38.5", + "rollup-plugin-istanbul": "2.0.1", + "rollup-plugin-typescript2": "^0.29.0", "tailwindcss": "1.1.4", "typescript": "3.9.6", "vue": "2.6.11", @@ -45,12 +50,14 @@ "webpack": "4.42.0" }, "peerDependencies": { + "@cypress/webpack-dev-server": "*", "babel-loader": "8", "cypress": ">=4.5.0", "vue": "2.x" }, "files": [ - "dist/**/*" + "dist/**/*", + "src/**/*.js" ], "engines": { "node": ">=8" @@ -68,6 +75,13 @@ "cypress", "vue" ], + "unpkg": "dist/cypress-vue.browser.js", + "module": "dist/cypress-vue.esm-bundler.js", + "peerDependenciesMeta": { + "@cypress/webpack-dev-server": { + "optional": true + } + }, "publishConfig": { "access": "public", "registry": "http://registry.npmjs.org/" diff --git a/npm/vue/rollup.config.js b/npm/vue/rollup.config.js new file mode 100644 index 000000000000..4e1c4fb6033e --- /dev/null +++ b/npm/vue/rollup.config.js @@ -0,0 +1,86 @@ +import ts from 'rollup-plugin-typescript2' +import resolve from '@rollup/plugin-node-resolve' +import commonjs from '@rollup/plugin-commonjs' + +import pkg from './package.json' + +const banner = ` +/** + * ${pkg.name} v${pkg.version} + * (c) ${new Date().getFullYear()} Cypress.io + * Released under the MIT License + */ +` + +function createEntry (options) { + const { + format, + input, + isBrowser, + } = options + + const config = { + input, + external: [ + 'vue', + '@vue/test-utils', + '@cypress/webpack-dev-server', + ], + plugins: [ + resolve({ preferBuiltins: true }), commonjs(), + ], + output: { + banner, + name: 'CypressVue', + file: pkg.unpkg, + format, + globals: { + vue: 'Vue', + '@vue/test-utils': 'VueTestUtils', + }, + exports: 'auto', + }, + } + + if (input === 'src/index.ts') { + if (format === 'es') { + config.output.file = pkg.module + if (isBrowser) { + config.output.file = pkg.unpkg + } + } + + if (format === 'cjs') { + config.output.file = pkg.main + } + } else { + config.output.file = input.replace(/^src\//, 'dist/') + } + + console.log(`Building ${format}: ${config.output.file}`) + + config.plugins.push( + ts({ + check: format === 'es' && isBrowser, + tsconfigOverride: { + compilerOptions: { + declaration: format === 'es', + target: 'es5', // not sure what this should be? + module: format === 'cjs' ? 'es2015' : 'esnext', + }, + exclude: ['tests'], + }, + }), + ) + + return config +} + +export default [ + createEntry({ format: 'es', input: 'src/index.ts', isBrowser: false }), + createEntry({ format: 'es', input: 'src/index.ts', isBrowser: true }), + createEntry({ format: 'iife', input: 'src/index.ts', isBrowser: true }), + createEntry({ format: 'cjs', input: 'src/index.ts', isBrowser: false }), + createEntry({ format: 'cjs', input: 'src/support.js', isBrowser: false }), + createEntry({ format: 'cjs', input: 'src/plugins/webpack/index.js', isBrowser: false }), +] diff --git a/npm/vue/src/index.ts b/npm/vue/src/index.ts index 99aacd0c8d80..d7a442ae0434 100644 --- a/npm/vue/src/index.ts +++ b/npm/vue/src/index.ts @@ -6,6 +6,7 @@ import { VueTestUtilsConfigOptions, Wrapper, } from '@vue/test-utils' +import { renderTestingPlatform, ROOT_ID } from './renderTestingPlatform' const defaultOptions: (keyof MountOptions)[] = [ 'vue', @@ -357,15 +358,11 @@ export const mount = ( const document: Document = cy.state('document') document.body.innerHTML = '' - let el = document.getElementById('cypress-jsdom') + let el = document.getElementById(ROOT_ID) // If the target div doesn't exist, create it if (!el) { - const div = document.createElement('div') - - div.id = 'cypress-jsdom' - document.body.appendChild(div) - el = div + el = renderTestingPlatform(document.head.innerHTML) } if (typeof options.stylesheets === 'string') { diff --git a/npm/vue/src/plugins/webpack/index.js b/npm/vue/src/plugins/webpack/index.js index 5854b87e2b60..b30e6534da50 100644 --- a/npm/vue/src/plugins/webpack/index.js +++ b/npm/vue/src/plugins/webpack/index.js @@ -17,7 +17,6 @@ const { startDevServer } = require('@cypress/webpack-dev-server') * } */ const cypressPluginsFn = (on, config, webpackConfig) => { - require('@cypress/code-coverage/task')(on, config) on('dev-server:start', (options) => startDevServer({ options, webpackConfig })) return config diff --git a/npm/vue/src/renderTestingPlatform.ts b/npm/vue/src/renderTestingPlatform.ts new file mode 100644 index 000000000000..64efe6bf52dc --- /dev/null +++ b/npm/vue/src/renderTestingPlatform.ts @@ -0,0 +1,20 @@ +export const ROOT_ID = '__cy_root' + +/** Initialize an empty document with root element + * This only needs for experimentalComponentTesting +*/ +export function renderTestingPlatform (headInnerHTML: string) { + // @ts-expect-error no idea + const document = cy.state('document') + + if (document.body) document.body.innerHTML = '' + + if (document.head) document.head.innerHTML = headInnerHTML + + const rootNode = document.createElement('div') + + rootNode.setAttribute('id', ROOT_ID) + document.getElementsByTagName('body')[0].prepend(rootNode) + + return rootNode +} diff --git a/npm/vue/src/support.js b/npm/vue/src/support.js index 5e2454e3cb14..2e5c51a411c8 100644 --- a/npm/vue/src/support.js +++ b/npm/vue/src/support.js @@ -1,47 +1,11 @@ /* eslint-env mocha */ -import unfetch from 'unfetch' -require('@cypress/code-coverage/support') +const { renderTestingPlatform } = require('./renderTestingPlatform') let headInnerHTML = document.head.innerHTML -/** Initialize an empty document with root element */ -function renderTestingPlatform () { - const document = cy.state('document') - - if (document.body) document.body.innerHTML = '' - - if (document.head) document.head.innerHTML = headInnerHTML - - const rootNode = document.createElement('div') - - rootNode.setAttribute('id', 'cypress-jsdom') - document.getElementsByTagName('body')[0].prepend(rootNode) - - return cy.get('#cypress-jsdom', { log: false }) -} - -/** - * Replaces window.fetch with a polyfill based on XMLHttpRequest - * that Cypress can spy on and stub - * @see https://www.cypress.io/blog/2020/06/29/experimental-fetch-polyfill/ - */ -function polyfillFetchIfNeeded () { - // @ts-ignore - if (Cypress.config('experimentalFetchPolyfill')) { - // @ts-ignore - if (!cy.state('fetchPolyfilled')) { - delete window.fetch - window.fetch = unfetch - // @ts-ignore - cy.state('fetchPolyfilled', true) - } - } -} - beforeEach(() => { - renderTestingPlatform() - polyfillFetchIfNeeded() + renderTestingPlatform(headInnerHTML) }) before(() => { diff --git a/npm/vue/tsconfig.json b/npm/vue/tsconfig.json index edf0d1e70806..ea20f06c3e05 100644 --- a/npm/vue/tsconfig.json +++ b/npm/vue/tsconfig.json @@ -28,7 +28,7 @@ /* Module Resolution Options */ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ diff --git a/npm/vue/webpack.config.js b/npm/vue/webpack.config.js index e822524b35a4..86e7423304e7 100644 --- a/npm/vue/webpack.config.js +++ b/npm/vue/webpack.config.js @@ -4,6 +4,7 @@ const VueLoaderPlugin = require('vue-loader/lib/plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const path = require('path') +const pkg = require('package.json') module.exports = { mode: 'development', @@ -18,7 +19,7 @@ module.exports = { extensions: ['.js', '.json', '.vue'], alias: { // point at the built file - '@cypress/vue': path.join(__dirname, 'dist'), + '@cypress/vue': path.join(__dirname, pkg.main), vue: 'vue/dist/vue.esm.js', }, }, diff --git a/npm/webpack-dev-server/index-template.html b/npm/webpack-dev-server/index-template.html index 70e10108dfbf..75e28860533f 100644 --- a/npm/webpack-dev-server/index-template.html +++ b/npm/webpack-dev-server/index-template.html @@ -7,6 +7,5 @@ Components App -
diff --git a/npm/webpack-dev-server/package.json b/npm/webpack-dev-server/package.json index 5a4d58c4049e..09d4edec6bb2 100644 --- a/npm/webpack-dev-server/package.json +++ b/npm/webpack-dev-server/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "debug": "4.3.2", + "lazy-compile-webpack-plugin": "0.1.11", "semver": "^7.3.4", "webpack-merge": "^5.4.0" }, diff --git a/npm/webpack-dev-server/src/aut-runner.ts b/npm/webpack-dev-server/src/aut-runner.ts index 3ea8d7a1730d..f1bf3238547c 100644 --- a/npm/webpack-dev-server/src/aut-runner.ts +++ b/npm/webpack-dev-server/src/aut-runner.ts @@ -3,13 +3,15 @@ function appendTargetIfNotExists (id: string, tag = 'div', parent = document.body) { let node = document.getElementById(id) - if (!node) { - node = document.createElement(tag) - node.setAttribute('id', id) - parent.appendChild(node) + if (node) { + // it is required to completely remove node from the document + // cause framework can store the information between renders inside the root node (like react-dom is doing) + node.parentElement.removeChild(node) } - node.innerHTML = '' + node = document.createElement(tag) + node.setAttribute('id', id) + parent.appendChild(node) return node } @@ -24,10 +26,12 @@ export function init (importPromises, parent = (window.opener || window.parent)) Cypress.onSpecWindow(window, importPromises) Cypress.action('app:window:before:load', window) - beforeEach(() => { - const root = appendTargetIfNotExists('__cy_root') - - root.appendChild(appendTargetIfNotExists('__cy_app')) + // Before all tests we are mounting the root element, **not beforeEach** + // Cleaning up platform between tests is the responsibility of the specific adapter + // because unmounting react/vue component should be done using specific framework API + // (for devtools and to get rid of global event listeners from previous tests.) + before(() => { + appendTargetIfNotExists('__cy_root') }) return { diff --git a/npm/webpack-dev-server/src/index.ts b/npm/webpack-dev-server/src/index.ts index 8a4a2f8773c7..ad665f789b5f 100644 --- a/npm/webpack-dev-server/src/index.ts +++ b/npm/webpack-dev-server/src/index.ts @@ -1,18 +1,9 @@ -import { EventEmitter } from 'events' import { debug as debugFn } from 'debug' import { AddressInfo } from 'net' import { start as createDevServer } from './startServer' -const debug = debugFn('cypress:webpack-dev-server:webpack') +import { UserWebpackDevServerOptions } from './makeWebpackConfig' -export interface DevServerOptions { - specs: Cypress.Cypress['spec'][] - config: { - supportFile: string - projectRoot: string - webpackDevServerPublicPathRoute: string - } - devServerEvents: EventEmitter -} +const debug = debugFn('cypress:webpack-dev-server:webpack') type DoneCallback = () => unknown @@ -21,15 +12,15 @@ export interface ResolvedDevServerConfig { close: (done?: DoneCallback) => void } -export interface StartDevServer { +export interface StartDevServer extends UserWebpackDevServerOptions { /* this is the Cypress options object */ - options: DevServerOptions + options: Cypress.DevServerOptions /* support passing a path to the user's webpack config */ webpackConfig?: Record } -export async function startDevServer (startDevServerArgs: StartDevServer) { - const webpackDevServer = await createDevServer(startDevServerArgs) +export async function startDevServer (startDevServerArgs: StartDevServer, exitProcess = process.exit) { + const webpackDevServer = await createDevServer(startDevServerArgs, exitProcess) return new Promise((resolve) => { const httpSvr = webpackDevServer.listen(0, '127.0.0.1', () => { diff --git a/npm/webpack-dev-server/src/makeWebpackConfig.ts b/npm/webpack-dev-server/src/makeWebpackConfig.ts index 8cef9c6bafc4..06efdfa3dd2e 100644 --- a/npm/webpack-dev-server/src/makeWebpackConfig.ts +++ b/npm/webpack-dev-server/src/makeWebpackConfig.ts @@ -1,28 +1,51 @@ import { debug as debugFn } from 'debug' import * as path from 'path' -import { Configuration } from 'webpack' +import * as webpack from 'webpack' import { merge } from 'webpack-merge' +import defaultWebpackConfig from './webpack.config' +import LazyCompilePlugin from 'lazy-compile-webpack-plugin' import CypressCTOptionsPlugin, { CypressCTOptionsPluginOptions } from './plugin' const debug = debugFn('cypress:webpack-dev-server:makeWebpackConfig') +const WEBPACK_MAJOR_VERSION = Number(webpack.version.split('.')[0]) + +export interface UserWebpackDevServerOptions { + /** + * if `true` will compile all the specs together when the first one is request and can slow up initial build time. + * @default false + */ + disableLazyCompilation?: boolean +} + +interface MakeWebpackConfigOptions extends CypressCTOptionsPluginOptions, UserWebpackDevServerOptions { + webpackDevServerPublicPathRoute: string + isOpenMode: boolean +} const mergePublicPath = (baseValue, userValue = '/') => { return path.join(baseValue, userValue, '/') } -interface MakeWebpackConfigOptions extends CypressCTOptionsPluginOptions { - webpackDevServerPublicPathRoute: string +function getLazyCompilationWebpackConfig (options: MakeWebpackConfigOptions): webpack.Configuration { + if (options.disableLazyCompilation || !options.isOpenMode) { + return {} + } + + switch (WEBPACK_MAJOR_VERSION) { + case 4: + return { plugins: [new LazyCompilePlugin()] } + case 5: + return { experiments: { lazyCompilation: true } } as webpack.Configuration + default: + return { } + } } -export async function makeWebpackConfig (userWebpackConfig: Configuration, options: MakeWebpackConfigOptions): Promise { +export async function makeWebpackConfig (userWebpackConfig: webpack.Configuration, options: MakeWebpackConfigOptions): Promise { const { projectRoot, webpackDevServerPublicPathRoute, files, supportFile, devServerEvents } = options debug(`User passed in webpack config with values %o`, userWebpackConfig) - const defaultWebpackConfig = require('./webpack.config') - - debug(`Merging Evergreen's webpack config with users'`) - debug(`New webpack entries %o`, files) debug(`Project root`, projectRoot) debug(`Support file`, supportFile) @@ -45,7 +68,12 @@ export async function makeWebpackConfig (userWebpackConfig: Configuration, optio ], } - const mergedConfig = merge(userWebpackConfig, defaultWebpackConfig, dynamicWebpackConfig) + const mergedConfig = merge( + userWebpackConfig, + defaultWebpackConfig, + dynamicWebpackConfig, + getLazyCompilationWebpackConfig(options), + ) mergedConfig.entry = entry diff --git a/npm/webpack-dev-server/src/startServer.ts b/npm/webpack-dev-server/src/startServer.ts index 9863dac3c96e..5058aa2e0e81 100644 --- a/npm/webpack-dev-server/src/startServer.ts +++ b/npm/webpack-dev-server/src/startServer.ts @@ -6,25 +6,38 @@ import { makeWebpackConfig } from './makeWebpackConfig' const debug = Debug('cypress:webpack-dev-server:start') -export async function start ({ webpackConfig: userWebpackConfig, options }: StartDevServer): Promise { +export async function start ({ webpackConfig: userWebpackConfig, options, ...userOptions }: StartDevServer, exitProcess = process.exit): Promise { if (!userWebpackConfig) { debug('User did not pass in any webpack configuration') } - const { projectRoot, webpackDevServerPublicPathRoute } = options.config + // @ts-expect-error ?? webpackDevServerPublicPathRoute is not a valid option of Cypress.Config + const { projectRoot, webpackDevServerPublicPathRoute, isTextTerminal } = options.config const webpackConfig = await makeWebpackConfig(userWebpackConfig || {}, { files: options.specs, projectRoot, webpackDevServerPublicPathRoute, devServerEvents: options.devServerEvents, - supportFile: options.config.supportFile, + supportFile: options.config.supportFile as string, + isOpenMode: !isTextTerminal, + ...userOptions, }) debug('compiling webpack') const compiler = webpack(webpackConfig) + // When compiling in run mode + // Stop the clock early, no need to run all the tests on a failed build + if (isTextTerminal) { + compiler.hooks.done.tap('cyCustomErrorBuild', function (stats) { + if (stats.hasErrors()) { + exitProcess(1) + } + }) + } + debug('starting webpack dev server') // TODO: write a test for how we are NOT modifying publicPath diff --git a/npm/webpack-dev-server/src/webpack.config.js b/npm/webpack-dev-server/src/webpack.config.js index 1f2eef5d09d2..968bec6c5311 100644 --- a/npm/webpack-dev-server/src/webpack.config.js +++ b/npm/webpack-dev-server/src/webpack.config.js @@ -1,6 +1,7 @@ const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') +/** @type {import('webpack').Configuration} */ module.exports = { mode: 'development', optimization: { diff --git a/npm/webpack-dev-server/test/e2e.spec.ts b/npm/webpack-dev-server/test/e2e.spec.ts index b74be273940e..da5cdebf46af 100644 --- a/npm/webpack-dev-server/test/e2e.spec.ts +++ b/npm/webpack-dev-server/test/e2e.spec.ts @@ -1,5 +1,6 @@ import webpack from 'webpack' import path from 'path' +import sinon from 'sinon' import { expect } from 'chai' import { EventEmitter } from 'events' import http from 'http' @@ -51,8 +52,9 @@ const specs: Cypress.Cypress['spec'][] = [ const config = { projectRoot: root, supportFile: '', + isTextTerminal: true, webpackDevServerPublicPathRoute: root, -} +} as any as Cypress.ResolvedConfigOptions & Cypress.RuntimeConfigOptions describe('#startDevServer', () => { it('serves specs via a webpack dev server', async () => { @@ -94,6 +96,9 @@ describe('#startDevServer', () => { it('emits dev-server:compile:error event on error compilation', async () => { const devServerEvents = new EventEmitter() + + const exitSpy = sinon.stub() + const { close } = await startDevServer({ webpackConfig, options: { @@ -107,10 +112,13 @@ describe('#startDevServer', () => { ], devServerEvents, }, - }) + }, exitSpy as any) + + exitSpy() return new Promise((res) => { devServerEvents.on('dev-server:compile:error', () => { + expect(exitSpy.calledOnce).to.be.true close(() => res()) }) }) diff --git a/yarn.lock b/yarn.lock index 28e6674f304a..9c6f267bbc1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13421,7 +13421,7 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== -detect-port-alt@1.1.6: +detect-port-alt@1.1.6, detect-port-alt@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== @@ -14577,10 +14577,10 @@ es6-weak-map@^2.0.1: es6-iterator "^2.0.3" es6-symbol "^3.1.1" -esbuild@^0.8.34: - version "0.8.38" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.8.38.tgz#04dc395e15c77bbc9d6798e9b31275546bcf7b9a" - integrity sha512-wSunJl8ujgBs9eVGubc8Y6fn/DkDjNyfQBVOFTY1E7sRxr8KTjmqyLIiE0M3Z4CjMnCu/rttCugwnOzY+HiwIw== +esbuild@^0.8.52: + version "0.8.57" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.8.57.tgz#a42d02bc2b57c70bcd0ef897fe244766bb6dd926" + integrity sha512-j02SFrUwFTRUqiY0Kjplwjm1psuzO1d6AjaXKuOR9hrY0HuPsT6sV42B6myW34h1q4CRy+Y3g4RU/cGJeI/nNA== escalade@^3.0.1, escalade@^3.1.1: version "3.1.1" @@ -21463,6 +21463,14 @@ lazy-cache@^1.0.3: resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= +lazy-compile-webpack-plugin@0.1.11: + version "0.1.11" + resolved "https://registry.yarnpkg.com/lazy-compile-webpack-plugin/-/lazy-compile-webpack-plugin-0.1.11.tgz#be3b9487ccc731a606dc55bcfcd80000c72e4237" + integrity sha512-d2D72x0XqFSj83SRmgx1dvgRvmyIoXpC2lMj+XVS+xzt0FxXDPzF/2FbxOVmW9gzp6d8U29Ne4RGF4x+MTYwow== + dependencies: + detect-port-alt "^1.1.6" + loader-utils "^1.2.3" + lazy-property@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lazy-property/-/lazy-property-1.0.0.tgz#84ddc4b370679ba8bd4cdcfa4c06b43d57111147" @@ -34259,12 +34267,12 @@ vinyl@^2.0.0, vinyl@^2.1.0, vinyl@^2.2.0: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vite@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/vite/-/vite-2.0.1.tgz#348fc5c0de510aa90bd01ecf87df210ce741b38e" - integrity sha512-x7ZfikjNs+6n4cdvwb9L5r5xBCdjmtmHFHaI4JVR3nAkJbMCK/dynfDWky8/NseZ9Ncz1jVxTQ/Bcf+n1ps1Ww== +vite@2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.0.5.tgz#ac46857a3fa8686d077921e61bd48a986931df1d" + integrity sha512-QTgEDbq1WsTtr6j+++ewjhBFEk6c8v0xz4fb/OWJQKNYU8ZZtphOshwOqAlnarSstPBtWCBR0tsugXx6ajfoUg== dependencies: - esbuild "^0.8.34" + esbuild "^0.8.52" postcss "^8.2.1" resolve "^1.19.0" rollup "^2.38.5" From 1ce57554e260850472cf753de68858f47b3f7b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barth=C3=A9l=C3=A9my=20Ledoux?= Date: Wed, 10 Mar 2021 14:11:14 -0600 Subject: [PATCH 2/4] fix: trigger release of the packages (#15405) --- npm/vite-dev-server/README.md | 14 +++++++++++++- npm/vue/README.md | 2 ++ npm/webpack-dev-server/README.md | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/npm/vite-dev-server/README.md b/npm/vite-dev-server/README.md index 91e2bf5da29c..d334bb411c95 100644 --- a/npm/vite-dev-server/README.md +++ b/npm/vite-dev-server/README.md @@ -1,3 +1,15 @@ # ⚡️ + 🌲 Cypress Component Testing w/ Vite -> **Note** this package is not meant to be used outside of cypress component testing. +To install vite in you component testing environment, +1. Install it `yarn add @cypress/vite-dev-server` +2. Add it to `cypress/plugins/index.js` + +```js +import { startDevServer } from '@cypress/vite-dev-server' + +module.exports = (on, config) => { + on('dev-server:start', async (options) => startDevServer({ options })) + + return config +} +``` diff --git a/npm/vue/README.md b/npm/vue/README.md index 0c858014c359..00d1c3d12626 100644 --- a/npm/vue/README.md +++ b/npm/vue/README.md @@ -743,6 +743,8 @@ module.exports = (on, config) => { The Cypress.io Component Testing Team - [Jessica Sachs](https://github.com/jessicasachs) (Current Maintainer, [Vue Test Utils](https://github.com/vuejs/vue-test-utils) Maintainer) +- [Lachlan Miller](https://github.com/lmiller1990) (Current Maintainer, [Vue Test Utils](https://github.com/vuejs/vue-test-utils) Maintainer) +- [Bart Ledoux](https://github.com/elevatebart) (Current Maintainer, [Vue Styleguidist](https://github.com/vue-styleguidist/vue-styleguidist) Maintainer) - [Gleb Bahmutov](https://github.com/bahmutov) (Original Author, Current Maintainer of [@cypress/react](https://github.com//cypress-io/@cypress/react)) Support: if you find any problems with this module, [tweet](https://twitter.com/_jessicasachs) / [open issue](https://github.com/cypress-io/cypress/issues) on Github diff --git a/npm/webpack-dev-server/README.md b/npm/webpack-dev-server/README.md index 09ed3783d8d8..abece7ff560e 100644 --- a/npm/webpack-dev-server/README.md +++ b/npm/webpack-dev-server/README.md @@ -2,6 +2,8 @@ > **Note** this package is not meant to be used outside of cypress component testing. +Install `@cypress/vue` or `@cypress/react` to get this package working properly + ## Responsibilities - Make a `webpack.config` from the users setup From a37ab762fa8c9bc914650eda924f8bbe3d10091a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barth=C3=A9l=C3=A9my=20Ledoux?= Date: Wed, 10 Mar 2021 15:20:39 -0600 Subject: [PATCH 3/4] ci: run vite tests in ci (#15411) --- circle.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/circle.yml b/circle.yml index 5ccc1a8a7724..83914a3f027a 100644 --- a/circle.yml +++ b/circle.yml @@ -1054,6 +1054,15 @@ jobs: - run: name: Run tests command: yarn workspace @cypress/webpack-dev-server test + npm-vite-dev-server: + <<: *defaults + steps: + - attach_workspace: + at: ~/ + - check-conditional-ci + - run: + name: Run tests + command: yarn workspace @cypress/vite-dev-server test npm-webpack-batteries-included-preprocessor: <<: *defaults @@ -1745,6 +1754,9 @@ linux-workflow: &linux-workflow - npm-webpack-dev-server: requires: - build + - npm-vite-dev-server: + requires: + - build - npm-webpack-preprocessor: requires: - build From bdb7c0d907bc4c2246cdd29d60f9a2b9f6bc3e5f Mon Sep 17 00:00:00 2001 From: ElevateBart Date: Wed, 10 Mar 2021 15:49:27 -0600 Subject: [PATCH 4/4] chore: merge circle issue --- circle.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/circle.yml b/circle.yml index 264b0258a1c0..557768b708fc 100644 --- a/circle.yml +++ b/circle.yml @@ -1064,16 +1064,6 @@ jobs: name: Run tests command: yarn workspace @cypress/vite-dev-server test - npm-vite-dev-server: - <<: *defaults - steps: - - attach_workspace: - at: ~/ - - check-conditional-ci - - run: - name: Run tests - command: yarn workspace @cypress/vite-dev-server test - npm-rollup-dev-server: <<: *defaults steps: