Cypress: Proposal: add native built-in typescript support

Created on 3 Jun 2018  ·  36Comments  ·  Source: cypress-io/cypress

Overview

In 3.0.0 we had to include a dependency that automatically bundled in the typescript binary. This added some significant weight to the overall app size (35mb) which begs the question- why not just provide native support for typescript? We originally decided not to do this because of the added weight. Now that's no longer a reason. Additionally, support for typescript has continuously expanded, and as part of our overall philosophy of being zero-config, I think it's time to add support for it out of the box. We already have several dedicated doc guides for this.

As we're coming closer to bridging the gap between the browser + the node context shifts, it means that we also need to think about the plugin support for both of these contexts.

For instance we have file:preprocessor event, but this is really only for spec + support files going to the browser.

What if users want to write their plugins/index.js file using newer node features that the current version doesn't support? What if they want to write that file in typescript? What if they want to include or require other node files or lib code that also needs this?

Proposal

  • Bundle in typescript as an official dependency in Cypress.
  • Automatically parse all .ts file extensions for node files found in plugins, etc
  • Automatically serve spec files with .ts extension
  • Automatically find/use the same .tsconfig that your project would be using in node normally, but with a preference for .tsconfig files in cypress folder.
  • Likely utilize ts-node to do typescript compilation on demand / in memory. Potentially do either transpileOnly or leave full blown type checking on.

Questions

  • What if the bundled version of Typescript is newer/older than the one you have in your project? We should probably automatically try to require the one built into your project, and if none is installed fall back to ours.
  • Perhaps we could accept a flag in cypress.json that prefers the bundled/native version of typescript even if a system/project typescript dependency was found
  • How would users modify the ts-node options we use by default such as transpileOnly? We could add more cypress.json configuration options but then we're back to preferring static configuration vs configuration with code.

Concerns

Thinking about this more and looking at other frameworks. It is super nice to have Cypress "automatically" transpile things for you, because it has to work in both the node and browser environment. The problem is that customization is much harder. We have to expose to you the ability to modify the options. This worked well for the file:preprocessor plugin event - because the browser context is always executed later in the testing lifecycle. However, trying to do the same thing with node is not possible because there has to be some kind of entry point.

If we think about this outside of just Typescript we have the same issue. Imagine someone wanting to write their node plugin files using ES2018 - they'd have the same problem as they do whenever they're starting a vanilla node project for the first time. In both cases, you'd have to use babel to automatically transpile the .js or .mjs files on the fly. The difference is that it would be totally controllable by the user (we should just write some docs outlining this behavior). If you think about Typescript in this way you could argue that we should not add built in native support for it so that the user retains total control of both the node and browser context. On the other hand we could simply avoid adding lots of configuration options and instead just allow the user to turn off built in typescript support and then provide docs on how to manually wire things up in the case that you need to customize things beyond what we support.

typescript

Most helpful comment

I would love to remove the need for messing with plugins/index.js along with needing to install webpack @cypress/webpack-preprocessor typescript ts-loader

Current process https://github.com/basarat/typescript-book/blob/master/docs/testing/cypress.md

Ideally cypress ships with ts-node and all we (the users) need to do is npm install cypress typescript, write a tsconfig.json and start cracking with .spec.ts files :rose:

All 36 comments

/cc @amirrustam @bahmutov @chrisbreiding

With regards to TypeScript support, here are current ts-node register options I'm using within the API. Documenting here for discussion.

require('ts-node').register({
  pretty: true,  // Use pretty diagnostic formatter
  typeCheck: false, // Turn off type checking
  transpileOnly: true,   // Use TypeScript's faster `transpileModule`
  ignoreDiagnostics: true,  // Ignore TypeScript warnings by diagnostic code
})

Full ts-node register options:

// Register options.
  pretty?: boolean
  typeCheck?: boolean
  transpileOnly?: boolean
  cache?: boolean
  cacheDirectory?: string
  compiler?: string
  ignore?: string | string[]
  project?: string
  skipIgnore?: boolean
  skipProject?: boolean
  ignoreDiagnostics?: string | string[]
  compilerOptions?: string

I don't know as much about the implementation details of this, but I would like automatic TypeScript support, without having to set up my own preprocessor api if I have a .ts file extension in my test directories. Would love to hear @NicholasBoll opinion.

@jennifer-shehane sure, that is the goal, but if you read my proposal you'll see that we need to give you the ability to configure ts-node to achieve this. If we read in your .ts files without going through a hook or index.js that sets these options, then there's no way to configure the way ts-node works.

We could support these options via cypress.json but then we're back to the same ol' configuration for everything problem vs just letting people write code to do the stuff they want to do.

Just read the proposal and you'll see what the challenges are. When those questions are answered and we come up with a reasonable implementation then we can do it. Saying we want to support this is like saying we want to support all browsers. Naturally we do, but that's completely glossing over the implementation details that need to be worked out.

Whats the need for processing the file in memory, why not write them out to some temp directory?

We do write them out to a temp directory

Gotcha, is there a reason why you cant use typescript directly then? it'll reduce a dependancy and give you more options for config.

I would love to remove the need for messing with plugins/index.js along with needing to install webpack @cypress/webpack-preprocessor typescript ts-loader

Current process https://github.com/basarat/typescript-book/blob/master/docs/testing/cypress.md

Ideally cypress ships with ts-node and all we (the users) need to do is npm install cypress typescript, write a tsconfig.json and start cracking with .spec.ts files :rose:

@basarat that's a great guide, thank you! I would love to have a shorter typescript way too.
Currently we're using typescript for the intellisense and omit typescript features like type annotations so that the code looks like es5/es6. Not optimal but works for simple use cases.

First of all, nice project, i just R&D cy and its looks promising, anyway to consider the topic ts integration

documentation is laked, when i used configs in eg. tests wont run, thank of all tsconfig form git repository work better. To works with chia/mocha assertions you need extend litle bit tsconfig

yml { "compilerOptions": { "target": "es5", "module": "commonjs", "skipLibCheck": true, // do not check types in node_modules folder "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ "strictNullChecks": true, /* Enable strict null checks. */ "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ "alwaysStrict": true, "lib": ["es5", "dom"], "types": ["cypress"], "typeRoots": [ "node_modules/@types" ], "paths": { "*": [ "node_modules/*", "src/types/*" ] }, "strict": true, "sourceMap": true, "experimentalDecorators": true }, "include": [ "node_modules/cypress", "cypress/*/*.ts" ], "compileOnSave": false }

and add to each ts file eg.:
```ts
///
import { expect } from 'chai';
````

consider to buildin ts support in Pm2 - has solution for it but this solution has +/-, what is
pros 👍

  • fast entry point
  • independent
  • isolation
    cons 👎
  • conflicts problems in windows with global ts/ts-node
  • problems with diff version of dependency eg. pm2 uses max 6.0.5 and in develop u uses 7.0.0

Are there any updates on this issue?

@SeriousM No updates on this issue. It is still in a proposal stage.

This might be an obvious thing to say but looking directly into webpack codebase might be a good idea. Webpack does exactly what this proposal wants to achieve. You can write configs in typescript and it just uses ts-node underneath with zero additional configuration needed - you just need to install ts-node as a devDep in you project. If you want to use different tsconfig, you just run it like this: TS_NODE_PROJECT="tsconfig-ts-node.json" webpack.

And how do we connect webpack with cypress?

@aczekajski do you have a complete example, even a very simple one that shows what you are doing in action? Because for me, configuring Webpack like this https://webpack.js.org/guides/typescript/ plus adding https://github.com/cypress-io/cypress-webpack-preprocessor is not as simple, and trying to make it "built-in" seems a little bit premature

@bahmutov I think @aczekajski is only talking about that https://webpack.js.org/configuration/configuration-languages/. Seems indeed to be only a subset of this issue's objective :)

would this step by step instruction webpack + Cypress help? https://glebbahmutov.com/blog/use-typescript-with-cypress/

This guide should be on the cypress.io docs!

Now with @babel\preset-typescript, this can be accomplished by just modifying the default browserify preprocessor.

To do this manually:

install @cypress/browserify-preprocessor and @babel/preset-typescript

Modify the plugins/index.js file:

const browserify = require('@cypress/browserify-preprocessor');

module.exports = (on, config) => {
  const options = browserify.defaultOptions;
  options.browserifyOptions.extensions.push('.ts', '.tsx');
  const babelifyConfig = options.browserifyOptions.transform[1][1];
  babelifyConfig.presets.push(require.resolve('@babel/preset-typescript'));
  babelifyConfig.extensions = ['.js', '.jsx', '.ts', '.tsx'];

  on('file:preprocessor', browserify(options));
};

Ohh this is good

Sent from my iPhone

On Mar 25, 2019, at 23:07, Nathan Brown notifications@github.com wrote:

Now with @babelpreset-typescript, this can be accomplished by just modifying the default browserify preprocessor.

To do this manually:

install @cypress/browserify-preprocessor and @babel/preset-typescript

Modify the plugin/index.js file:

const browserify = require('@cypress/browserify-preprocessor');

module.exports = (on, config) => {
const options = browserify.defaultOptions;
options.browserifyOptions.extensions.push('.ts');
const babelifyConfig = options.browserifyOptions.transform[1][1];
babelifyConfig.presets.push(require.resolve('@babel/preset-typescript'));
babelifyConfig.extensions = ['.js', '.jsx', '.ts', '.tsx'];

on('file:preprocessor', browserify(options));
};

You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

I would be happy built-in babel doing the ts->js transform.
I use the IDE / tsc --noEmit to tell me any type errors anyways :rose:

Is that a command parameter?

@SeriousM --noEmit is a TypeScript compiler parameter. It tells TypeScript not to emit any .js (or .d.ts) files. You can use it to tell TypeScript to do a full type check but leave the transpilation to something else. In cypress case the emit will be done in memory by cypress+ts-loader OR hopefully cypress+internalBabelWithTsSupportWithZeroConfigNeeded :rose:

Hi everyone. I was recently trying to write tests for my Typescript+React Application and faced a lot of issues with respect to Typescript configuration. I faced a lot of issues in getting started with Jest+react-testing-library in Typescript then, making me switch to the amazing Cypress library. For the past 3 days, I have been continuously struggling to integrate Cypress+Typescript+React together. The issues are:

  1. If we try to integrate webpack preprocessor, it clashes with Create-React-App requirements and breaks the project. Integrating webpack itself is a lot of hassle, esp. if you're already using it in different parts of the project.
  2. With Browserify, tsify causes some issues with the following 2 errors continuously coming up:

SyntaxError: 'import' and 'export' may appear only with 'sourceType: module'
Cannot compile namespaces when the '--isolatedModules' flag is provided.
Though these seem like some pretty straightforward issues, I really couldn't figure them out till now.

  1. Handling multiple tsconfig.json and which folder to run which command from + setting up package.json seem like a lot of clashing configuration to do as React and Cypress both have strict & different tsconfig.json requirements.

Just like Cypress, it's difficult to switch back to Javascript after tasting Typescript capabilities. (As of now I have shifted to JS tests.)

80% of us would like to use or learn TypeScript in their next project and it is one of the most loved languages by devs. I really believe we should take this proposal forward to facilitate and encourage Typescript usage.

How about using our a "simple" TypeScript transpiler using just tsc programmatically (#3430): Would something on these lines might help: https://medium.com/bratislava-angular/up-and-running-with-cypress-and-typescript-5b9065eedbd3

@mayurdhurpate I have went through a fresh CRA + TypeScript + Cypress setup last night, there are a few gotchas related to the compiler flags CRA uses. Nothing complicated, I will describe the steps in a blog post on https://www.cypress.io/blog/ soon

I have described CRA v3 + writing Cypress tests using TypeScript in this blog post https://www.cypress.io/blog/2019/05/13/code-create-react-app-v3-and-its-cypress-tests-using-typescript/ for anyone interested

Thanks! :rose:

Would still wish a native, non additional webpack + @bahmutov/add-typescript-to-cypress process (which is already existing). The additional install + a later flicker caused by a webpack compile whenever a test loads is what I would wish gone :heart:


PS: Minor typo
image
.env file

Thanks @basarat for noticing it - I should fix it :)

Would really appreciate Typescript just working out of the box. Especially commands.

@brian-mann @jennifer-shehane what's the status of this?

The goal of this proposal is these 2 in summary:

  1. Out-of-the-box TypeScript support.
  2. Allow customization for it. (Even with Babel)

But after reading cypress code, I realized that files under cypress folder are handled in different ways.

  1. integration, support: They're transpiled by browserify and sent to client.
  2. plugins: They're required.

So, the goal can be broken into 4 like this:

  1. Out-of-the-box TypeScript support for test files.
  2. Out-of-the-box TypeScript support for plugin files.
  3. Allow customization for test files.
  4. Allow customization for plugin files.

I've created PR(#5906) to fix these problems. Here are the notes and thoughts about the process.

1. Out-of-the-box TypeScript support for test files.

About implementation

There were 2 options to make this possible.

  1. Use tsify and bundled/user-installed typescript.
  2. Use @babel/preset-typescript.

I decided to go with babel. Although it doesn't support type check, modern editors check TypeScript types. And tsify is too slow for the job.

Why not webpack?

We need to bundle webpack and loaders. It increases bundle size too much. So, I didn't do that.

How it works

When there is a user-installed @babel/preset-typescript, use it. If not, use bundled one.

Options

I'm thinking about going with no config for this. It's hard to find a reason to prefer bundled @babel/preset-typescript to user-installed one.

2. Out-of-the-box TypeScript support for plugin files.

About implementation

ts-node has to be added to packages/server dependencies. I created 4 options for this and register options accordingly.

Options & How it works

Add typescript option in cypress.json. It can have one of these 4 values.

  • undefined or default: use user-installed typescript if exists, use bundled one if not.
  • project: use user-installed typescript if exists, throw error if not.
  • bundled: always use bundled typescript
  • none: don't use typescript.

3. Allow customization for test files.

I think it's solved by "file:preprocessor".

When you want something complicated than out-of-the-box TypeScript, it should be done with this event.

4. Allow customization for plugin files.

Option ideas

I think there are 3 things that users want to customize.

  1. tsconfig
  2. ts-node options
  3. Set up everything you like

We can support them like this:

tsconfig

Add tsconfig option in cypress.json. It should have 4 options:

  • undefined or default: use user-installed tsconfig if exists, use bundled one if not.
  • project: use user-installed tsconfig if exists, throw error if not.
  • bundled: always use bundled tsconfig
  • when typescript option is none and this value is defined, throw an error.

ts-node options

Add tsnode option in cypress.json. You can add options if it is supported in ts-node.

Note: when typescript option is none and it is defined, throw an error.

Set up everything you like

Add setupNodeFile option in cypress.json. Its default value is plugins/setup_node.js. It must be js file.

In this file, users can define whatever they want here. If they want to set up babel-node with a bunch of stage-0/1 features, this is the place to do it.

If this file exists, all typescript-related options will be ignored.

Example Code
// plugins/setup_node.js
module.exports = () => {
    require('@babel/node')
}
//.babelrc
{
    // presets and plugins...
}

This setup_node.js file will be executed before plugins/index.js is loaded.

setup_node.js exports a function for the future.

The Problem

These are not implemented in #5906.

It's because of the noImplicitAny option in tsconfig. When it is set to true, server will throw an error because in packages/server, noImplicitAny is false.

Because of this, transpileOnly must be always true. If not, server will fail.

I think we should revisit this issue after #2690 is done. Or close this and create new issue for these options and wait for users' opinions.

These options might be over-engineering.

This is an excellent proposal and I’m excited about it. I love the fact how you split it into four stages for quickly merges and also how you are thinking what should be a peer dependency. I’m even thinking that if the project is not using TS already we should not bundle our TS code but instead show and error asking the user to install ts modules

Sent from my iPhone

On Dec 6, 2019, at 20:25, Kukhyeon Heo notifications@github.com wrote:


The goal of this proposal is these 2 in summary:

Out-of-the-box TypeScript support.
Allow customization for it. (Even with Babel)
But after reading cypress code, I realized that files under cypress folder are handled in different ways.

integration, support: They're transpiled by browserify and sent to client.
plugins: They're required.
So, the goal can be broken into 4 like this:

Out-of-the-box TypeScript support for test files.
Out-of-the-box TypeScript support for plugin files.
Allow customization for test files.
Allow customization for plugin files.
I've created PR(#5906) to fix these problems. Here are the notes and thoughts about the process.

  1. Out-of-the-box TypeScript support for test files.

About implementation

There were 2 options to make this possible.

Use tsify and bundled/user-installed typescript.
Use @babel/preset-typescript.
I decided to go with babel. Although it doesn't support type check, modern editors check TypeScript types. And tsify is too slow for the job.

Why not webpack?

We need to bundle webpack and loaders. It increases bundle size too much. So, I didn't do that.

How it works

When there is a user-installed @babel/preset-typescript, use it. If not, use bundled one.

Options

I'm thinking about going with no config for this. It's hard to find a reason to prefer bundled @babel/preset-typescript to user-installed one.

  1. Out-of-the-box TypeScript support for plugin files.

About implementation

ts-node has to be added to packages/server dependencies. I created 4 options for this and register options accordingly.

Options & How it works

Add typescript option in cypress.json. It can have one of these 4 values.

undefined or default: use user-installed typescript if exists, use bundled one if not.
project: use user-installed typescript if exists, throw error if not.
bundled: always use bundled typescript
none: don't use typescript.

  1. Allow customization for test files.

I think it's solved by "file:preprocessor".

When you want something complicated than out-of-the-box TypeScript, it should be done with this event.

  1. Allow customization for plugin files.

Option ideas

I think there are 3 things that users want to customize.

tsconfig
ts-node options
Set up everything you like
We can support them like this:

tsconfig

Add tsconfig option in cypress.json. It should have 4 options:

undefined or default: use user-installed tsconfig if exists, use bundled one if not.
project: use user-installed tsconfig if exists, throw error if not.
bundled: always use bundled tsconfig
when typescript option is none and this value is defined, throw an error.
ts-node options

Add tsnode option in cypress.json. You can add options if it is supported in ts-node.

Note: when typescript option is none and it is defined, throw an error.

Set up everything you like

Add setupNodeFile option in cypress.json. Its default value is plugins/setup_node.js. It must be js file.

In this file, users can define whatever they want here. If they want to set up babel-node with a bunch of stage-0/1 features, this is the place to do it.

If this file exists, all typescript-related options will be ignored.

Example Code

// plugins/setup_node.js
module.exports = () => {
require('@babel/node')
}
//.babelrc
{
// presets and plugins...
}
This setup_node.js file will be executed before plugins/index.js is loaded.

setup_node.js exports a function for the future.

The Problem

These are not implemented in #5906.

It's because of the noImplicitAny option in tsconfig. When it is set to true, server will throw an error because in packages/server, noImplicitAny is false.

Because of this, transpileOnly must be always true. If not, server will fail.

I think we should revisit this issue after #2690 is done. Or close this and create new issue for these options and wait for users' opinions.

These options might be over-engineering.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or unsubscribe.

I don't think we should ever bundle typescript, and always require the user to have it installed. Adding the tsc binary increases the size of Cypress by too much. I also don't like the idea of the version of typescript not being specified. It makes more sense to always require the user to supply typescript - since they likely already are in their project in the first place.

You are right that we need to handle both the plugin files and the spec files separately, and because of that - that's where all the complexity emerges. I think your ideas do satisfy all the conditions, but that overall its still far too complex and would require far too much documentation and edge case handling. I also really don't like the idea of introducing yet another setupNodeFile configuration option.

I don't think we need to actually introduce any new configuration options - we could automatically detect what the user is trying to do via the .ts file extension and attempt to do the right thing.

I also think that we could just use tsconfig.json by default, and avoid needing to support any ts-node options - or at the very least, if the user supplies their own environment variables, then those would "just work".

@brian-mann Sorry for the late reply. I was investigating to find the answer.

My second proposal is this:

Focus on transpiling typescript files. Let editors and CIs do type checking.

With this, we don't have to worry about tsconfig.json, ts-node options, etc. All we do is these 2:

  1. When typescript is installed in user project, use it.
  2. When typescript is not installed in user project but .ts files are used. Throw error like now.

With this, new dependency we need to add is ts-node (resolve and through2 are added by babel and browserify). If that's too much, then we can give up typescript for plugin and remove ts-node.

Please check #5906 for code. (The failed test is flaky test to be fixed in #6030)

NOTE: To remove typescript from server entirely, we need to remove dependency-tree and find/make alternative. (I opened an issue in #6051)

The code for this is done in cypress-io/cypress#5906, but has yet to be released.
We'll update this issue and reference the changelog when it's released.

Released in 4.4.0.

This comment thread has been locked. If you are still experiencing this issue after upgrading to
Cypress v4.4.0, please open a new issue.

Was this page helpful?
0 / 5 - 0 ratings