Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(@cypress/webpack-batteries-included-preprocessor): Ensure typescript options are set if typescript path is provided #15991

Merged
merged 2 commits into from
Apr 15, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
14 changes: 13 additions & 1 deletion npm/webpack-batteries-included-preprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,19 @@ module.exports = (on) => {
}
```

This preprocessor supports the same options as [@cypress/webpack-preprocessor](https://github.com/cypress-io/cypress/tree/master/npm/webpack-preprocessor#readme), so see its README for more information.
To enable TypeScript support, install TypeScript (if not already installed in your project `npm install --save-dev typescript`) and provide its location with the `typescript` option:

```javascript
const webpackPreprocessor = require('@cypress/webpack-batteries-included-preprocessor')

module.exports = (on) => {
on('file:preprocessor', webpackPreprocessor({
typescript: require.resolve('typescript')
}))
}
```

Other than the `typescript` option, this preprocessor supports the same options as [@cypress/webpack-preprocessor](https://github.com/cypress-io/cypress/tree/master/npm/webpack-preprocessor#readme), so see its README for more information.

## Contributing

Expand Down
104 changes: 65 additions & 39 deletions npm/webpack-batteries-included-preprocessor/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,66 @@
const path = require('path')
const webpackPreprocessor = require('@cypress/webpack-preprocessor')

const getDefaultWebpackOptions = (file, options = {}) => {
const config = {
const hasTsLoader = (rules) => {
return rules.some((rule) => {
if (!rule.use || !Array.isArray(rule.use)) return false

return rule.use.some((use) => {
return use.loader && use.loader.includes('ts-loader')
})
})
}

const addTypeScriptConfig = (file, options) => {
// shortcut if we know we've already added typescript support
if (options.__typescriptSupportAdded) return

const webpackOptions = options.webpackOptions
const rules = webpackOptions.module && webpackOptions.module.rules

// if there are no rules defined or it's not an array, we can't add to them
if (!rules || !Array.isArray(rules)) return

// if we find ts-loader configured, don't add it again
if (hasTsLoader(rules)) return

const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
// node will try to load a projects tsconfig.json instead of the node
// package using require('tsconfig'), so we alias it as 'tsconfig-package'
const configFile = require('tsconfig-package').findSync(path.dirname(file.filePath))

webpackOptions.module.rules.push({
test: /\.tsx?$/,
exclude: [/node_modules/],
use: [
{
loader: require.resolve('ts-loader'),
options: {
compiler: options.typescript,
compilerOptions: {
inlineSourceMap: true,
inlineSources: true,
downlevelIteration: true,
},
logLevel: 'error',
silent: true,
transpileOnly: true,
},
},
],
})

webpackOptions.resolve.extensions = webpackOptions.resolve.extensions.concat(['.ts', '.tsx'])
webpackOptions.resolve.plugins = [new TsconfigPathsPlugin({
configFile,
silent: true,
})]

options.__typescriptSupportAdded = true
}

const getDefaultWebpackOptions = () => {
return {
mode: 'development',
node: {
global: true,
Expand Down Expand Up @@ -67,42 +125,6 @@ const getDefaultWebpackOptions = (file, options = {}) => {
},
},
}

if (options.typescript) {
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
// node will try to load a projects tsconfig.json instead of the node
// package using require('tsconfig'), so we alias it as 'tsconfig-package'
const configFile = require('tsconfig-package').findSync(path.dirname(file.filePath))

config.module.rules.push({
test: /\.tsx?$/,
exclude: [/node_modules/],
use: [
{
loader: require.resolve('ts-loader'),
options: {
compiler: options.typescript,
compilerOptions: {
inlineSourceMap: true,
inlineSources: true,
downlevelIteration: true,
},
logLevel: 'error',
silent: true,
transpileOnly: true,
},
},
],
})

config.resolve.extensions = config.resolve.extensions.concat(['.ts', '.tsx'])
config.resolve.plugins = [new TsconfigPathsPlugin({
configFile,
silent: true,
})]
}

return config
}

const typescriptExtensionRegex = /\.tsx?$/
Expand All @@ -113,7 +135,11 @@ const preprocessor = (options = {}) => {
return Promise.reject(new Error(`You are attempting to run a TypeScript file, but do not have TypeScript installed. Ensure you have 'typescript' installed to enable TypeScript support.\n\nThe file: ${file.filePath}`))
}

options.webpackOptions = options.webpackOptions || getDefaultWebpackOptions(file, options)
options.webpackOptions = options.webpackOptions || getDefaultWebpackOptions()

if (options.typescript) {
addTypeScriptConfig(file, options)
}

return webpackPreprocessor(options)(file)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,26 +79,33 @@ describe('features', () => {
const options = { typescript: require.resolve('typescript') }

it('handles typescript (and tsconfig paths)', async () => {
await runAndEval('ts_spec.ts', options)
await runAndEval('ts_spec.ts', { ...options })
})

it('handles tsx', async () => {
await runAndEval('tsx_spec.tsx', options)
await runAndEval('tsx_spec.tsx', { ...options })
})

it('handles importing .ts and .tsx', async () => {
await runAndEval('typescript_imports_spec.js', options)
await runAndEval('typescript_imports_spec.js', { ...options })
})

it('handles esModuleInterop: false (default)', async () => {
await runAndEval('typescript_esmoduleinterop_false_spec.ts', options)
await runAndEval('typescript_esmoduleinterop_false_spec.ts', { ...options })
})

it('handles esModuleInterop: true', async () => {
await runAndEval('esmoduleinterop-true/typescript_esmoduleinterop_true_spec.ts', options)
await runAndEval('esmoduleinterop-true/typescript_esmoduleinterop_true_spec.ts', { ...options })
})

it('errors when processing .ts file and typescript option is not set', function () {
// https://github.com/cypress-io/cypress/issues/15767
// defaultOptions don't have typescript config baked in since it requires
// the path to typescript and the file, so it needs to be added later
it('adds typescript support if using defaultOptions', async () => {
await runAndEval('tsx_spec.tsx', { ...options, ...preprocessor.defaultOptions })
})

it('errors when processing .ts file and typescript option is not set', () => {
return run('ts_spec.ts')
.then(shouldntResolve)
.catch((err) => {
Expand All @@ -107,7 +114,7 @@ describe('features', () => {
})
})

it('errors when processing .tsx file and typescript option is not set', function () {
it('errors when processing .tsx file and typescript option is not set', () => {
return run('tsx_spec.tsx')
.then(shouldntResolve)
.catch((err) => {
Expand Down