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

feat: Add out-of-the-tbox typescript support #38

Merged
merged 18 commits into from
Apr 3, 2020
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
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