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

Commit

Permalink
feat: Add out-of-the-tbox typescript support (#38)
Browse files Browse the repository at this point in the history
* 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 <chrisbreiding@gmail.com>
  • Loading branch information
sainthkh and chrisbreiding authored Apr 3, 2020
1 parent 642a671 commit 344a057
Show file tree
Hide file tree
Showing 13 changed files with 353 additions and 48 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,16 @@ browserify({
})
```

### typescript

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.

```javascript
browserify({
typescript: require.resolve('typescript')
})
```

**Default**: `undefined`

## Modifying default options
Expand Down
44 changes: 41 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const path = require('path')
const Promise = require('bluebird')
const fs = require('./fs')
const fs = require('./lib/fs')

const cloneDeep = require('lodash.clonedeep')
const browserify = require('browserify')
Expand Down Expand Up @@ -60,7 +60,7 @@ const defaultOptions = {
},
}

const getBrowserifyOptions = (entry, userBrowserifyOptions = {}) => {
const getBrowserifyOptions = (entry, userBrowserifyOptions = {}, typescriptPath = null) => {
let browserifyOptions = cloneDeep(defaultOptions.browserifyOptions)

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

if (typescriptPath) {
const transform = browserifyOptions.transform
const hasTsifyTransform = transform.some(([name]) => name.includes('tsify'))
const hastsifyPlugin = browserifyOptions.plugin.includes('tsify')

if (hasTsifyTransform || hastsifyPlugin) {
const type = hasTsifyTransform ? 'transform' : 'plugin'

throw new Error(`Error running @cypress/browserify-preprocessor:
It looks like you passed the 'typescript' option and also specified a browserify ${type} for TypeScript. This may cause conflicts.
Please do one of the following:
1) Pass in the 'typescript' option and omit the browserify ${type} (Recommmended)
2) Omit the 'typescript' option and continue to use your own browserify ${type}
`)
}

browserifyOptions.extensions.push('.ts', '.tsx')
// remove babelify setting
browserifyOptions.transform = transform.filter(([name]) => !name.includes('babelify'))
// add typescript compiler
browserifyOptions.transform.push([
path.join(__dirname, './lib/simple_tsify'), {
typescript: require(typescriptPath),
},
])
}

debug('browserifyOptions: %o', browserifyOptions)

return browserifyOptions
Expand Down Expand Up @@ -127,7 +157,7 @@ const preprocessor = (options = {}) => {
debug('input:', filePath)
debug('output:', outputPath)

const browserifyOptions = getBrowserifyOptions(filePath, options.browserifyOptions)
const browserifyOptions = getBrowserifyOptions(filePath, options.browserifyOptions, options.typescript)
const watchifyOptions = Object.assign({}, defaultOptions.watchifyOptions, options.watchifyOptions)

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

if (process.env.__TESTING__) {
preprocessor.reset = () => {
for (let filePath in bundles) {
delete bundles[filePath]
}
}
}

module.exports = preprocessor
File renamed without changes.
43 changes: 43 additions & 0 deletions lib/simple_tsify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
let through = require('through2')

const isJson = (code) => {
try {
JSON.parse(code)
} catch (e) {
return false
}

return true
}

// tsify doesn't have transpile-only option like ts-node or ts-loader.
// It means it should check types whenever spec file is changed
// and it slows down the test speed a lot.
// We skip this slow type-checking process by using transpileModule() api.
module.exports = function (b, opts) {
const chunks = []

return through(
(buf, enc, next) => {
chunks.push(buf.toString())
next()
},
function (next) {
const ts = opts.typescript
const text = chunks.join('')

if (isJson(text)) {
this.push(text)
} else {
this.push(ts.transpileModule(text, {
compilerOptions: {
esModuleInterop: true,
jsx: 'react',
},
}).outputText)
}

next()
},
)
}
56 changes: 56 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,13 @@
"mocha": "5.2.0",
"mockery": "2.1.0",
"nsp": "3.2.1",
"react": "16.13.1",
"react-dom": "16.13.1",
"semantic-release": "15.13.15",
"sinon": "7.2.3",
"sinon-chai": "3.3.0",
"snap-shot-it": "7.9.2"
"snap-shot-it": "7.9.2",
"typescript": "3.8.3"
},
"dependencies": {
"@babel/core": "7.4.5",
Expand All @@ -74,7 +77,8 @@
"debug": "4.1.1",
"fs-extra": "7.0.1",
"lodash.clonedeep": "4.5.0",
"watchify": "3.11.1"
"watchify": "3.11.1",
"through2": "^2.0.0"
},
"release": {
"analyzeCommits": {
Expand Down
101 changes: 62 additions & 39 deletions test/e2e/e2e_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ const chai = require('chai')
const path = require('path')
const snapshot = require('snap-shot-it')

const fs = require('../../fs')
process.env.__TESTING__ = true

const fs = require('../../lib/fs')
const preprocessor = require('../../index')

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

beforeEach(function () {
beforeEach(() => {
fs.removeSync(path.join(__dirname, '_test-output'))
preprocessor.reset()
})

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

describe('browserify preprocessor - e2e', function () {
it('correctly preprocesses the file', function () {
describe('browserify preprocessor - e2e', () => {
it('correctly preprocesses the file', () => {
return bundle('example_spec.js').then((output) => {
snapshot(output)
})
})
})

describe('imports and exports', () => {
it('handles imports and exports', () => {
return bundle('math_spec.js').then((output) => {
// check that bundled tests work
eval(output)
describe('imports and exports', () => {
it('handles imports and exports', () => {
return bundle('math_spec.js').then((output) => {
// check that bundled tests work
eval(output)
})
})
})

it('named ES6', () => {
return bundle('divide_spec.js').then((output) => {
// check that bundled tests work
eval(output)
it('named ES6', () => {
return bundle('divide_spec.js').then((output) => {
// check that bundled tests work
eval(output)
})
})
})


it('handles module.exports and import', () => {
return bundle('sub_spec.js').then((output) => {
// check that bundled tests work
eval(output)
snapshot('sub import', output)
it('handles module.exports and import', () => {
return bundle('sub_spec.js').then((output) => {
// check that bundled tests work
eval(output)
snapshot('sub import', output)
})
})
})

it('handles module.exports and default import', () => {
return bundle('mul_spec.js').then((output) => {
// check that bundled tests work
eval(output)
// for some reason, this bundle included full resolved path
// to interop require module
// which on CI generates different path.
// so as long as eval works, do not snapshot it
it('handles module.exports and default import', () => {
return bundle('mul_spec.js').then((output) => {
// check that bundled tests work
eval(output)
// for some reason, this bundle included full resolved path
// to interop require module
// which on CI generates different path.
// so as long as eval works, do not snapshot it
})
})

it('handles default string import', () => {
return bundle('dom_spec.js').then((output) => {
// check that bundled tests work
eval(output)
})
})
})

it('handles default string import', () => {
return bundle('dom_spec.js').then((output) => {
// check that bundled tests work
eval(output)
it('handles non-top-level require', () => {
return bundle('require_spec.js').then((output) => {
// check that bundled tests work
eval(output)
})
})
})

it('handles non-top-level require', () => {
return bundle('require_spec.js').then((output) => {
// check that bundled tests work
eval(output)
describe('typescript', () => {
it('handles .ts file when the path is given', () => {
return bundle('typescript/math_spec.ts', {
typescript: require.resolve('typescript'),
}).then((output) => {
// check that bundled tests work
eval(output)
})
})

it('handles .tsx file when the path is given', () => {
return bundle('typescript/react_spec.tsx', {
typescript: require.resolve('typescript'),
}).then((output) => {
// check that bundled tests work
eval(output)
})
})
})
})
Empty file removed test/e2e/output.js
Empty file.
7 changes: 7 additions & 0 deletions test/fixtures/typescript/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react'

export default () => {
return (
<div className="icon-star">icon</div>
)
}
5 changes: 5 additions & 0 deletions test/fixtures/typescript/math.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
add: (a: number, b: number) => {
return a + b
},
}
Loading

3 comments on commit 344a057

@redReno
Copy link

@redReno redReno commented on 344a057 Apr 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get

Error: Cannot find module './lib/fs'
when running this or other projects that use it, such as cypress-cucumber-preprocessor.

Is it because of this error when I run npm install?

SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.12: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})

This hasn't been a problem earlier.

@chrisbreiding
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@redReno Thanks for reporting. It's an issue with publishing after this commit. Will get it fixed and publish a new version soon.

@redReno
Copy link

@redReno redReno commented on 344a057 Apr 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@redReno Thanks for reporting. It's an issue with publishing after this commit. Will get it fixed and publish a new version soon.

Thanks for the quick fix!

Please sign in to comment.