Skip to content

Commit 3fe5f50

Browse files
authored
feat: embedding mount into the cypress binary (real dependency) (#20930)
1 parent 1ef7ffb commit 3fe5f50

14 files changed

+183
-9
lines changed

.eslintignore

+5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ system-tests/projects/config-with-ts-syntax-error/**
3737
# cli/types is linted by tslint/dtslint
3838
cli/types
3939

40+
# cli/react, cli/vue, and cli/mount-utils are all copied from dist'd builds
41+
cli/react
42+
cli/vue
43+
cli/mount-utils
44+
4045
# packages/example is not linted (think about changing this)
4146
packages/example
4247

circle.yml

+7
Original file line numberDiff line numberDiff line change
@@ -948,9 +948,16 @@ commands:
948948
- run:
949949
name: Build NPM package
950950
command: yarn build --scope cypress
951+
- run:
952+
name: Copy Re-exported NPM Packages
953+
command: node ./scripts/post-build.js
954+
working_directory: cli
951955
- run:
952956
command: ls -la types
953957
working_directory: cli/build
958+
- run:
959+
command: ls -la vue mount-utils react
960+
working_directory: cli/build
954961
- unless:
955962
condition:
956963
equal: [ *windows-executor, << parameters.executor >> ]

cli/.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ types/sinon-chai
1212
types/net-stubbing.ts
1313
# ignore CLI output build folder
1414
build
15+
16+
# ignore packages synced at build-time via
17+
# the sync-exported-npm-with-cli.js script
18+
vue
19+
react
20+
mount-utils

cli/README.md

+41
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,47 @@ yarn add ~/{your-dirs}/cypress/cli/build/cypress-3.3.1.tgz --ignore-scripts
6060

6161
Which installs the `tgz` file we have just built from folder `Users/jane-lane/{your-dirs}/cypress/cli/build`.
6262

63+
#### Sub-package API
64+
65+
> How do deep imports from cypress/* get resolved?
66+
67+
The cypress npm package comes pre-assembled with mounting libraries for major front-end frameworks. These mounting libraries are the first examples of Cypress providing re-exported sub-packages. These sub-packages follow the same naming convention they do when they're published on **npm**, but without a leading **`@`** sign. For example:
68+
69+
##### An example of a sub-package: @cypress/vue, @cypress/react, @cypress/mount-utils
70+
71+
**Let's discuss the Vue mounting library that Cypress ships.**
72+
73+
If you'd installed the `@cypress/vue` package from NPM, you could write the following code.
74+
75+
This would be necessary when trying to use a version of Vue, React, or other library that may be newer or older than the current version of cypress itself.
76+
77+
```js
78+
import { mount } from '@cypress/vue'
79+
```
80+
81+
Now, with the sub-package API, you're able to import the latest APIs directly from Cypress without needing to install a separate dependency.
82+
83+
```js
84+
import { mount } from 'cypress/vue'
85+
```
86+
87+
The only difference is the import name, and if you still need to use a specific version of one of our external sub-packages, you may install it and import it directly.
88+
89+
##### Adding a new sub-package
90+
91+
There are a few steps when adding a new sub-package.
92+
93+
1. Make sure the sub-package's rollup build is _self-contained_ or that any dependencies are also declared in the CLI's **`package.json`**.
94+
2. Now, in the **`postbuild`** script for the sub-package you'd like to embed, invoke `node ./scripts/sync-exported-npm-with-cli.js` (relative to the sub-package, see **`npm/vue`** for an example).
95+
3. Add the sub-package's name to the following locations:
96+
- **`cli/.gitignore`**
97+
- **`cli/scripts/post-build.js`**
98+
- **`.eslintignore`** (under cli/sub-package)
99+
4. DO NOT manually update the **package.json** file. Running `yarn build` will automate this process.
100+
5. Commit the changed files.
101+
102+
[Here is an example Pull Request](https://github.com/cypress-io/cypress/pull/20930/files#diff-21b1fe66043572c76c549a4fc5f186e9a69c330b186fc91116b9b70a4d047902)
103+
63104
#### Module API
64105

65106
The module API can be tested locally using something like:

cli/package.json

+15-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,10 @@
103103
"index.js",
104104
"index.mjs",
105105
"types/**/*.d.ts",
106-
"types/net-stubbing.ts"
106+
"types/net-stubbing.ts",
107+
"mount-utils",
108+
"vue",
109+
"react"
107110
],
108111
"bin": {
109112
"cypress": "bin/cypress"
@@ -117,9 +120,20 @@
117120
"import": "./index.mjs",
118121
"require": "./index.js"
119122
},
123+
"./vue": {
124+
"import": "./vue/dist/cypress-vue.esm-bundler.js",
125+
"require": "./vue/dist/cypress-vue.cjs.js"
126+
},
120127
"./package.json": {
121128
"import": "./package.json",
122129
"require": "./package.json"
130+
},
131+
"./react": {
132+
"import": "./react/dist/cypress-react.esm-bundler.js",
133+
"require": "./react/dist/cypress-react.cjs.js"
134+
},
135+
"./mount-utils": {
136+
"require": "./mount-utils/dist/index.js"
123137
}
124138
}
125139
}

cli/scripts/post-build.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const shell = require('shelljs')
2+
const { resolve } = require('path')
3+
4+
shell.set('-v') // verbose
5+
shell.set('-e') // any error is fatal
6+
7+
// For each npm package that is re-published via cypress/*
8+
// make sure that it is also copied into the build directory
9+
const npmModulesToCopy = [
10+
'mount-utils',
11+
'react',
12+
'vue',
13+
]
14+
15+
npmModulesToCopy.forEach((folder) => {
16+
// cli/mount-utils => cli/build/mount-utils
17+
const from = resolve(`${__dirname}/../${folder}`)
18+
const to = resolve(`${__dirname}/../build/${folder}`)
19+
20+
shell.cp('-R', from, to)
21+
})

cli/scripts/start-build.js

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ includeTypes.forEach((folder) => {
2222
shell.cp('-R', source, 'build/types')
2323
})
2424

25+
// TODO: Add a typescript or rollup build step
26+
// The only reason start-build.js exists
27+
// is because the cli package does not have an actual
28+
// build process to compile index.js and lib
2529
shell.exec('babel lib -d build/lib')
2630
shell.exec('babel index.js -o build/index.js')
2731
shell.cp('index.mjs', 'build/index.mjs')

npm/mount-utils/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"main": "dist/index.js",
66
"scripts": {
77
"build": "tsc || echo 'built, with type errors'",
8-
"build-prod": "tsc || echo 'built, with type errors'",
8+
"postbuild": "node ../../scripts/sync-exported-npm-with-cli.js",
9+
"build-prod": "yarn build",
910
"check-ts": "tsc --noEmit",
1011
"watch": "tsc -w"
1112
},

npm/react/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"main": "dist/cypress-react.cjs.js",
66
"scripts": {
77
"build": "rimraf dist && yarn transpile-plugins && rollup -c rollup.config.js",
8+
"postbuild": "node ../../scripts/sync-exported-npm-with-cli.js",
89
"build-prod": "yarn build",
910
"cy:open": "node ../../scripts/cypress.js open --component",
1011
"cy:open:debug": "node --inspect-brk ../../scripts/start.js --component-testing --run-project ${PWD}",

npm/react/rollup.config.js

-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ function createEntry (options) {
2424
external: [
2525
'react',
2626
'react-dom',
27-
'@cypress/mount-utils',
2827
],
2928
plugins: [
3029
resolve(), commonjs(),

npm/vue/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"main": "dist/cypress-vue.cjs.js",
66
"scripts": {
77
"build": "rimraf dist && rollup -c rollup.config.js",
8+
"postbuild": "node ../../scripts/sync-exported-npm-with-cli.js",
89
"build-prod": "yarn build",
910
"cy:open": "node ../../scripts/cypress.js open --component --project ${PWD}",
1011
"cy:run": "node ../../scripts/cypress.js run --component --project ${PWD}",

npm/vue/rollup.config.js

-4
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ function createEntry (options) {
2222
input,
2323
external: [
2424
'vue',
25-
'@vue/test-utils',
26-
'@cypress/mount-utils',
27-
'@cypress/webpack-dev-server',
2825
],
2926
plugins: [
3027
resolve({ preferBuiltins: true }), commonjs(),
@@ -36,7 +33,6 @@ function createEntry (options) {
3633
format,
3734
globals: {
3835
vue: 'Vue',
39-
'@vue/test-utils': 'VueTestUtils',
4036
},
4137
exports: 'auto',
4238
},

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
"binary-release": "node ./scripts/binary.js release",
1313
"binary-upload": "node ./scripts/binary.js upload",
1414
"binary-zip": "node ./scripts/binary.js zip",
15-
"build": "lerna run build --stream --no-bail --ignore create-cypress-tests --ignore \"'@packages/{runner}'\" && lerna run build --stream --scope create-cypress-tests",
16-
"build-prod": "lerna run build-prod-ui --stream && lerna run build-prod --stream --ignore create-cypress-tests && lerna run build-prod --stream --scope create-cypress-tests",
15+
"build": "lerna run build --stream --no-bail --ignore create-cypress-tests --ignore \"'@packages/{runner}'\" && node ./cli/scripts/post-build.js && lerna run build --stream --scope create-cypress-tests",
16+
"build-prod": "lerna run build-prod-ui --stream && lerna run build-prod --stream --ignore create-cypress-tests && node ./cli/scripts/post-build.js && lerna run build-prod --stream --scope create-cypress-tests",
1717
"check-node-version": "node scripts/check-node-version.js",
1818
"check-terminal": "node scripts/check-terminal.js",
1919
"clean": "lerna run clean --parallel --no-bail || echo 'ok, errors while cleaning'",

scripts/sync-exported-npm-with-cli.js

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* This script is used to re-export packages that Cypress publishes on its own.
3+
* It is executed individually in a postbuild step by individual npm/* packages.
4+
* For example, usually, Cypress will publish the `npm/react` directory as a `@cypress/react` package.
5+
* However, the Cypress binary will also ship an export for `cypress/react` that's guaranteed to work
6+
* with this version of the binary
7+
*/
8+
const shell = require('shelljs')
9+
const path = require('path')
10+
const packlist = require('npm-packlist')
11+
const fs = require('fs')
12+
13+
shell.set('-v') // verbose
14+
shell.set('-e') // any error is fatal
15+
16+
// This script will be run in a postbuild task for each npm package
17+
// that will be re-exported by Cypress
18+
const currentPackageDir = process.cwd()
19+
20+
// 1. We'll run npm's own "packlist" against the npm package to be published (@cypress/react, etc)
21+
// to make sure we don't miss any files when we copy them over to the CLI package
22+
// The files that will be returned here are the ones from @cypress/react's package.json['files'] key.
23+
packlist({ path: currentPackageDir })
24+
.then((files) => {
25+
// 2. Move all of the files that would be published under @cypress/react
26+
// to be copied under cli/react (drop the @cypress namespace)
27+
const cliPath = path.join(__dirname, '..', 'cli')
28+
29+
// Typically, these packages are independently published as @cypress/package-name
30+
// e.g. @cypress/vue => import whatever from 'cypress/vue'
31+
// The files will wind up at cypress/cli/vue/*
32+
const currentPackageConfig = require(path.join(process.cwd(), 'package.json'))
33+
const exportName = currentPackageConfig.name.replace('@cypress/', '')
34+
const outDir = path.join(cliPath, exportName)
35+
36+
// 3. For each file, mkdir if not exists, and then copy the dist'd assets over
37+
// Shell is synchronous by default, but we don't actually need to await for the results
38+
// to write to the `cliPackageConfig` at the end
39+
files.forEach((f) => {
40+
// mkdir if not exists
41+
const { dir } = path.parse(f)
42+
43+
if (dir) {
44+
shell.mkdir('-p', path.join(outDir, dir))
45+
}
46+
47+
shell.cp(path.join(currentPackageDir, f), path.join(outDir, f))
48+
})
49+
50+
// After everything is copied, let's update the Cypress cli package.json['exports'] map.
51+
const isModule = currentPackageConfig.type === 'module'
52+
53+
const cliPackageConfig = require(path.join(cliPath, 'package.json'))
54+
55+
const subPackageExports = cliPackageConfig.exports[`./${exportName}`] = {}
56+
const esmEntry = isModule ? currentPackageConfig.main : currentPackageConfig.module
57+
58+
if (esmEntry) {
59+
// ./react/dist/cypress-react-esm.js, etc
60+
subPackageExports.import = `./${exportName}/${esmEntry}`
61+
}
62+
63+
if (!isModule) {
64+
// ./react/dist/cypress-react-cjs.js, etc
65+
subPackageExports.require = `./${exportName}/${currentPackageConfig.main}`
66+
}
67+
68+
if (!cliPackageConfig.files.includes(exportName)) {
69+
cliPackageConfig.files.push(exportName)
70+
}
71+
72+
const output = `${JSON.stringify(cliPackageConfig, null, 2) }\n`
73+
74+
// eslint-disable-next-line no-console
75+
console.log('Writing to CLI package.json for', exportName)
76+
77+
fs.writeFileSync(path.join(cliPath, 'package.json'), output, 'utf-8')
78+
})

0 commit comments

Comments
 (0)