Skip to content
This repository was archived by the owner on Jan 31, 2023. It is now read-only.

Commit 344a057

Browse files
feat: Add out-of-the-tbox typescript support (#38)
* Add doc. * Migrated typescript code from cypress Move through2 to dependencies. Fix * Add typescript. * Add e2e tests for typescript. * Test .tsx file. * Reason why simple_tsify is necessary. * Test typescript options + remove babelify even if it's not the last item * Test tsx file with enzyme. * Fix test failure. * remove enzyme * use arrow function syntax * move files into lib directory * remove duplicate file * remove need to extra test files * update error message * move e2e tests to unit tests * properly nest e2e tests * make it clear where error is coming from Co-authored-by: Chris Breiding <[email protected]>
1 parent 642a671 commit 344a057

13 files changed

+353
-48
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,16 @@ browserify({
146146
})
147147
```
148148

149+
### typescript
150+
151+
When the path to the TypeScript package is given, Cypress will automatically transpile `.ts` spec, plugin, support files. Note that this **DOES NOT** check types.
152+
153+
```javascript
154+
browserify({
155+
typescript: require.resolve('typescript')
156+
})
157+
```
158+
149159
**Default**: `undefined`
150160

151161
## Modifying default options

index.js

+41-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const path = require('path')
44
const Promise = require('bluebird')
5-
const fs = require('./fs')
5+
const fs = require('./lib/fs')
66

77
const cloneDeep = require('lodash.clonedeep')
88
const browserify = require('browserify')
@@ -60,7 +60,7 @@ const defaultOptions = {
6060
},
6161
}
6262

63-
const getBrowserifyOptions = (entry, userBrowserifyOptions = {}) => {
63+
const getBrowserifyOptions = (entry, userBrowserifyOptions = {}, typescriptPath = null) => {
6464
let browserifyOptions = cloneDeep(defaultOptions.browserifyOptions)
6565

6666
// allow user to override default options
@@ -81,6 +81,36 @@ const getBrowserifyOptions = (entry, userBrowserifyOptions = {}) => {
8181
entries: [entry],
8282
})
8383

84+
if (typescriptPath) {
85+
const transform = browserifyOptions.transform
86+
const hasTsifyTransform = transform.some(([name]) => name.includes('tsify'))
87+
const hastsifyPlugin = browserifyOptions.plugin.includes('tsify')
88+
89+
if (hasTsifyTransform || hastsifyPlugin) {
90+
const type = hasTsifyTransform ? 'transform' : 'plugin'
91+
92+
throw new Error(`Error running @cypress/browserify-preprocessor:
93+
94+
It looks like you passed the 'typescript' option and also specified a browserify ${type} for TypeScript. This may cause conflicts.
95+
96+
Please do one of the following:
97+
98+
1) Pass in the 'typescript' option and omit the browserify ${type} (Recommmended)
99+
2) Omit the 'typescript' option and continue to use your own browserify ${type}
100+
`)
101+
}
102+
103+
browserifyOptions.extensions.push('.ts', '.tsx')
104+
// remove babelify setting
105+
browserifyOptions.transform = transform.filter(([name]) => !name.includes('babelify'))
106+
// add typescript compiler
107+
browserifyOptions.transform.push([
108+
path.join(__dirname, './lib/simple_tsify'), {
109+
typescript: require(typescriptPath),
110+
},
111+
])
112+
}
113+
84114
debug('browserifyOptions: %o', browserifyOptions)
85115

86116
return browserifyOptions
@@ -127,7 +157,7 @@ const preprocessor = (options = {}) => {
127157
debug('input:', filePath)
128158
debug('output:', outputPath)
129159

130-
const browserifyOptions = getBrowserifyOptions(filePath, options.browserifyOptions)
160+
const browserifyOptions = getBrowserifyOptions(filePath, options.browserifyOptions, options.typescript)
131161
const watchifyOptions = Object.assign({}, defaultOptions.watchifyOptions, options.watchifyOptions)
132162

133163
const bundler = browserify(browserifyOptions)
@@ -222,4 +252,12 @@ const preprocessor = (options = {}) => {
222252
// provide a clone of the default options
223253
preprocessor.defaultOptions = JSON.parse(JSON.stringify(defaultOptions))
224254

255+
if (process.env.__TESTING__) {
256+
preprocessor.reset = () => {
257+
for (let filePath in bundles) {
258+
delete bundles[filePath]
259+
}
260+
}
261+
}
262+
225263
module.exports = preprocessor

fs.js renamed to lib/fs.js

File renamed without changes.

lib/simple_tsify.js

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
let through = require('through2')
2+
3+
const isJson = (code) => {
4+
try {
5+
JSON.parse(code)
6+
} catch (e) {
7+
return false
8+
}
9+
10+
return true
11+
}
12+
13+
// tsify doesn't have transpile-only option like ts-node or ts-loader.
14+
// It means it should check types whenever spec file is changed
15+
// and it slows down the test speed a lot.
16+
// We skip this slow type-checking process by using transpileModule() api.
17+
module.exports = function (b, opts) {
18+
const chunks = []
19+
20+
return through(
21+
(buf, enc, next) => {
22+
chunks.push(buf.toString())
23+
next()
24+
},
25+
function (next) {
26+
const ts = opts.typescript
27+
const text = chunks.join('')
28+
29+
if (isJson(text)) {
30+
this.push(text)
31+
} else {
32+
this.push(ts.transpileModule(text, {
33+
compilerOptions: {
34+
esModuleInterop: true,
35+
jsx: 'react',
36+
},
37+
}).outputText)
38+
}
39+
40+
next()
41+
},
42+
)
43+
}

package-lock.json

+56
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,13 @@
5252
"mocha": "5.2.0",
5353
"mockery": "2.1.0",
5454
"nsp": "3.2.1",
55+
"react": "16.13.1",
56+
"react-dom": "16.13.1",
5557
"semantic-release": "15.13.15",
5658
"sinon": "7.2.3",
5759
"sinon-chai": "3.3.0",
58-
"snap-shot-it": "7.9.2"
60+
"snap-shot-it": "7.9.2",
61+
"typescript": "3.8.3"
5962
},
6063
"dependencies": {
6164
"@babel/core": "7.4.5",
@@ -74,7 +77,8 @@
7477
"debug": "4.1.1",
7578
"fs-extra": "7.0.1",
7679
"lodash.clonedeep": "4.5.0",
77-
"watchify": "3.11.1"
80+
"watchify": "3.11.1",
81+
"through2": "^2.0.0"
7882
},
7983
"release": {
8084
"analyzeCommits": {

test/e2e/e2e_spec.js

+62-39
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ const chai = require('chai')
22
const path = require('path')
33
const snapshot = require('snap-shot-it')
44

5-
const fs = require('../../fs')
5+
process.env.__TESTING__ = true
6+
7+
const fs = require('../../lib/fs')
68
const preprocessor = require('../../index')
79

810
/* eslint-disable-next-line no-unused-vars */
911
const expect = chai.expect
1012

11-
beforeEach(function () {
13+
beforeEach(() => {
1214
fs.removeSync(path.join(__dirname, '_test-output'))
15+
preprocessor.reset()
1316
})
1417

1518
// do not generate source maps by default
@@ -25,60 +28,80 @@ const bundle = (fixtureName, options = DEFAULT_OPTIONS) => {
2528
})
2629
}
2730

28-
describe('browserify preprocessor - e2e', function () {
29-
it('correctly preprocesses the file', function () {
31+
describe('browserify preprocessor - e2e', () => {
32+
it('correctly preprocesses the file', () => {
3033
return bundle('example_spec.js').then((output) => {
3134
snapshot(output)
3235
})
3336
})
34-
})
3537

36-
describe('imports and exports', () => {
37-
it('handles imports and exports', () => {
38-
return bundle('math_spec.js').then((output) => {
39-
// check that bundled tests work
40-
eval(output)
38+
describe('imports and exports', () => {
39+
it('handles imports and exports', () => {
40+
return bundle('math_spec.js').then((output) => {
41+
// check that bundled tests work
42+
eval(output)
43+
})
4144
})
42-
})
4345

44-
it('named ES6', () => {
45-
return bundle('divide_spec.js').then((output) => {
46-
// check that bundled tests work
47-
eval(output)
46+
it('named ES6', () => {
47+
return bundle('divide_spec.js').then((output) => {
48+
// check that bundled tests work
49+
eval(output)
50+
})
4851
})
49-
})
5052

5153

52-
it('handles module.exports and import', () => {
53-
return bundle('sub_spec.js').then((output) => {
54-
// check that bundled tests work
55-
eval(output)
56-
snapshot('sub import', output)
54+
it('handles module.exports and import', () => {
55+
return bundle('sub_spec.js').then((output) => {
56+
// check that bundled tests work
57+
eval(output)
58+
snapshot('sub import', output)
59+
})
5760
})
58-
})
5961

60-
it('handles module.exports and default import', () => {
61-
return bundle('mul_spec.js').then((output) => {
62-
// check that bundled tests work
63-
eval(output)
64-
// for some reason, this bundle included full resolved path
65-
// to interop require module
66-
// which on CI generates different path.
67-
// so as long as eval works, do not snapshot it
62+
it('handles module.exports and default import', () => {
63+
return bundle('mul_spec.js').then((output) => {
64+
// check that bundled tests work
65+
eval(output)
66+
// for some reason, this bundle included full resolved path
67+
// to interop require module
68+
// which on CI generates different path.
69+
// so as long as eval works, do not snapshot it
70+
})
71+
})
72+
73+
it('handles default string import', () => {
74+
return bundle('dom_spec.js').then((output) => {
75+
// check that bundled tests work
76+
eval(output)
77+
})
6878
})
69-
})
7079

71-
it('handles default string import', () => {
72-
return bundle('dom_spec.js').then((output) => {
73-
// check that bundled tests work
74-
eval(output)
80+
it('handles non-top-level require', () => {
81+
return bundle('require_spec.js').then((output) => {
82+
// check that bundled tests work
83+
eval(output)
84+
})
7585
})
7686
})
7787

78-
it('handles non-top-level require', () => {
79-
return bundle('require_spec.js').then((output) => {
80-
// check that bundled tests work
81-
eval(output)
88+
describe('typescript', () => {
89+
it('handles .ts file when the path is given', () => {
90+
return bundle('typescript/math_spec.ts', {
91+
typescript: require.resolve('typescript'),
92+
}).then((output) => {
93+
// check that bundled tests work
94+
eval(output)
95+
})
96+
})
97+
98+
it('handles .tsx file when the path is given', () => {
99+
return bundle('typescript/react_spec.tsx', {
100+
typescript: require.resolve('typescript'),
101+
}).then((output) => {
102+
// check that bundled tests work
103+
eval(output)
104+
})
82105
})
83106
})
84107
})

test/e2e/output.js

Whitespace-only changes.
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from 'react'
2+
3+
export default () => {
4+
return (
5+
<div className="icon-star">icon</div>
6+
)
7+
}

test/fixtures/typescript/math.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
add: (a: number, b: number) => {
3+
return a + b
4+
},
5+
}

0 commit comments

Comments
 (0)