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

Add TypeScript support to LWC authoring format #2078

Open
AllanOricil opened this issue Nov 6, 2020 · 27 comments
Open

Add TypeScript support to LWC authoring format #2078

AllanOricil opened this issue Nov 6, 2020 · 27 comments

Comments

@AllanOricil
Copy link
Contributor

Is your feature request related to a problem? Please describe.
No

Describe the solution you'd like
Add typescript support

Describe alternatives you've considered
NA

Additional context
The industry of frontend frameworks are going towards Typescript, Thinking on that, will LWC support typescript in the near future?

@pmdartus pmdartus changed the title Typescript implementation Add TypeScript support to LWC authoring format Nov 9, 2020
@eight-molecules
Copy link

+1 I would love TypeScript support, but I can understand how this might be difficult on the platform because of the non-standard import scheme used before compilation.

@diervo
Copy link
Contributor

diervo commented Nov 9, 2020

For the record, LWC does allow Typescript, here is an example (it might be a bit outdated but will be good enough for making the point):
https://github.com/diervo/lwc-typescript-boilerplate

In a nutshell you just need to remove the types before running LWC compiler.

As for the platform usage: We will never be able to natively support Typescript (meaning we won't be able to ever allow you to save components in original .ts format) fundamentally due to the fact that typescript can have breaking changes from version to version, which means that we will have to support N+1 versions of platform until the ends of time, which is something we can't afford to do.

We do want to provide types and tooling to make it as seamless as possible, so all developers can choose to use Typescript from a "practically full" e2e development in platform.

@AllanOricil
Copy link
Contributor Author

AllanOricil commented Nov 9, 2020

@diervo thank you. I googled it and could not find anywhere.
It would be cool if we could develop using typescript, and during the push command sfdx would build the lwc source to javascript before saving the components on the org. This could be an sfdx extension but it would be necessary to have all the Types defined. Wouldn't it work?

@diervo
Copy link
Contributor

diervo commented Nov 9, 2020

Yes, that's the goal. I would suggest you create an item in the IdeaExchange https://trailblazer.salesforce.com/ideaSearch (if there is not one already)
and get a lot of people voting it up so it gets prioritize accordingly since the LWC team does not own the DX integration.

@timothywlewis
Copy link

Even if LWC on platform doesn't "natively" run TypeScript, tooling could make it a near-native solution:

  • tooling to retrieve type files for sObjects and LWC APIs (and do so automagically when working in VC Code)
  • tooling to transpile LWC to JS when deploying to an org, and ideally to also preserve source-mapping upon deployment (perhaps only when a debug flag is enabled)

Here's the most applicable idea to upvote: https://ideas.salesforce.com/s/idea/a0B8W00000GdZyWUAV/enable-lightning-component-developers-to-use-typescript-and-ecmascript-6

@AllanOricil AllanOricil closed this as not planned Won't fix, can't repro, duplicate, stale Mar 3, 2023
@nolanlawson nolanlawson reopened this Mar 3, 2023
@AllanOricil AllanOricil closed this as not planned Won't fix, can't repro, duplicate, stale May 18, 2023
@nolanlawson
Copy link
Collaborator

This is still planned

@zenibako
Copy link

zenibako commented Oct 10, 2024

Where can I provide feedback for this implementation? https://developer.salesforce.com/docs/platform/lwc/guide/ts.html#compile-your-typescript-components-to-javascript

I'm trying to use decorators and it's not clear how they are supposed to be handled. It works if I enable experimentalDecorators, but the docs above say it should be disabled.

@wjhsf
Copy link
Contributor

wjhsf commented Oct 10, 2024

What version of TypeScript are you using? LWC currently uses non-experimental decorators, so you must be using v5 or later. You also must have target set to "ESNext". I see that we don't mention target in the documentation; I'll make sure to have that updated.

For some tips on adopting TypeScript, you can see the LWC v7 release notes.

@FabienTaillon
Copy link

@wjhsf I have the exact same problem, had a quick chat with Clay Martin about it, but wasn't able to make it work yet.

I've added ESNext too (which is not in the doc here), but that didn't helped. Setting experimentalDecorators does work though (well at least compile doesn't fail).

Even with a basic component using a decorator (@api):

import { LightningElement, api } from 'lwc';

export default class MyTypescriptLwc extends LightningElement {
    @api recordId;
}

I'm getting the following error with experimentalDecorators set to false:
TS1240: Unable to resolve signature of property decorator when called as an expression. Argument of type 'ClassFieldDecoratorContext<MyTypescriptLwc, any> & { name: "recordId"; private: false; static: false; }' is not assignable to parameter of type 'string | symbol'.

Beside that, with this expected setup:

"compilerOptions": {
    "target": "ESNext",
    "experimentalDecorators": false
}

and with the same component but without the @api, my LWC is compiled to this JS:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const lwc_1 = require("lwc");
class MyTypescriptLwc extends lwc_1.LightningElement {
}
exports.default = MyTypescriptLwc;

Deploying this to the org (Winter '25 Scratch Org, LWC and sfdx-project.json set to API 62.0), adding the component to a FlexiPage crashes with the following error:

Uncaught Action failed: flexipageEditor:node$controller$generate [exports is not defined]
throws at https://customer-speed-5460-dev-ed.scratch.lightning.force.com/auraFW/javascript/ZzhjQmRxMXdrdzhvS0RJMG5qQVdxQTdEcXI0cnRHWU0zd2xrUnFaakQxNXc5LjMyMC4y/aura_prod.js:139:15. Caused by: Action failed: flexipageEditor:node$controller$generate [exports is not defined]
eval()@modules/c/myTypescriptLwc.js:3:53
Object.buildFqnList()@https://customer-speed-5460-dev-ed.scratch.lightning.force.com/components/flexipageEditor/component.js:14:25
Object.generate()@https://customer-speed-5460-dev-ed.scratch.lightning.force.com/components/flexipageEditor/component.js:2:328
generate()@https://customer-speed-5460-dev-ed.scratch.lightning.force.com/components/flexipageEditor/node.js:1:443

We are several trying to use it and so far I've seen no one succeeding.
cc @SCWells72

@wjhsf
Copy link
Contributor

wjhsf commented Oct 10, 2024

That's just the result of the TypeScript compilation step, correct? You'll need to update your module to emit ESM (import/export stateements) rather than CommonJS (using exports). Depending on your project setup, you might use "ESNext", "NodeNext", or "Preserve". See the TypeScript documentation for details on each option. You may also need to update the moduleResolution option.

@FabienTaillon
Copy link

Yes correct.

According to the diagram on this page I understand that after the TypeScript compilation step I can just deploy the compiled JS to the org.

I already tried to add "module": "NodeNext" to the tsconfig.json as I saw it was configured like this at Dreamforce (even though it's not on the doc), so I have this config:

{
  "extends": "../../../../.sfdx/tsconfig.sfdx.json",
  "include": ["**/*.ts", "../../../../.sfdx/typings/lwc/**/*.d.ts"],
  "exclude": ["**/__tests__/**"],
  "compilerOptions": {
    "target": "ESNext",
    "module": "NodeNext",
    "experimentalDecorators": false
  }
}

Is it what you're talking about ? I also tried to had "module": "NodeNext" but whatever config I use, the compiled JS is always the same.
It ends up, for a myTypescriptLwc LWC, in the myTypescriptLwc.js file, and this is what I deploy and makes the page crash in Lightning Experience.

@FabienTaillon
Copy link

@wjhsf ok it works with this config 🙂:

{
  "extends": "../../../../.sfdx/tsconfig.sfdx.json",
  "include": ["**/*.ts", "../../../../.sfdx/typings/lwc/**/*.d.ts"],
  "exclude": ["**/__tests__/**"],
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "experimentalDecorators": false
  }
}

I think the target and module parts of the compilerOptions section are missing in the doc, that may be where we are struggling 🙂

The TypeScript error I reported before is still there, but it doesn't prevent the file from being generated.
So I guess the error shouldn't be there but at least the compilation works 🙂

@FabienTaillon
Copy link

On the other hand, no error prevents compilation so even "real" ones ends up as a compiled LWC:

compile
import { LightningElement } from 'lwc';
export default class MyTypescriptLwc extends LightningElement {
    myNumber;
    handleClick() {
        return this.myNumber / 2;
    }
}

@wjhsf
Copy link
Contributor

wjhsf commented Oct 10, 2024

I also tried to had "module": "NodeNext" but whatever config I use, the compiled JS is always the same.

NodeNext is tricky -- it can emit both ESM and CJS, and it looks in your package.json to pick which one. To use NodeNext and emit ESM, you need to have "type": "module" in your package.json. You may encounter runtime errors, if you do this, because that setting also controls the behavior of node.

I think the target and module parts of the compilerOptions section are missing in the doc, that may be where we are struggling 🙂

Yep, updated docs should be available soon. I think the next scheduled update is Tuesday.

The TypeScript error I reported before is still there, but it doesn't prevent the file from being generated.

Yep, that's the default behavior of TypeScript. If you want errors to prevent emitting JavaScript, you can set the compiler option noEmitOnError to true. (Depending on your tooling, this option may or may not be respected, but it's what to do if you use tsc directly.)

@FabienTaillon
Copy link

Awesome, the noEmitOnError did the trick.
On my side everything is working now except the @api error that @zenibako also reported (I have to set experimentalDecorators to false to make it work). I'm using TypeScript 5.6.3 so it should work.

@AllanOricil
Copy link
Contributor Author

relates: forcedotcom/cli#3032

@wjhsf
Copy link
Contributor

wjhsf commented Oct 10, 2024

If you're using the Salesforce VS Code extension, then experimentalDecorators: true is currently still required. The extension still uses an older version of LWC. (Using true is required for up to v6, both values are supported in LWC v7, and false is required since v8.) By the time TypeScript support graduates from developer preview, the VS Code extension will be up to date and require using false, but for now I'll ensure our documentation reflects the current state of affairs.

If you're not using the VS Code extension, ensure your project is using LWC v7 or later.

@SCWells72
Copy link

Hi, @wjhsf. Semi-related, is there already an open issue here for making this work better with third-party tooling, i.e,. not having a concrete dependency on the contents of .sfdx/typings/lwc? I tried to add support to Illuminated Cloud and while I could get things working quite well in the editor -- using its own bundled *.d.ts files that already exist for strongly-typed dev in ES6 -- the need to set up a tsconfig.json against files that are only created by the VS Code Extensions and that shouldn't ever be checked into version control creates an impediment to both interactive and automated third-party tooling integration.

I've already heard that that's tracked internally at Salesforce, but is there an outward-facing issue in some issue tracker -- this one or otherwise -- that I could use to track progress on third-party tooling enablement?

@wjhsf
Copy link
Contributor

wjhsf commented Oct 10, 2024

I don't know that we have an external issue for removing all of the .sfdx/typings/lwc types, but I did open forcedotcom/lightning-language-server#602 to track the LWC package, specifically.

Longer term, the goal is to entirely replace the extension's types with the @salesforce/lightning-types npm package, which will work with any toolchain. We're currently working on populating the package (which will eventually have types for ~200 components/modules!). As we add type definitions that conflict with the extension's types, we will remove them from the extension.

@FabienTaillon
Copy link

Thank you @wjhsf !

I'll live with that in the dev preview, but there may be some issues in VS Code until we get to the correct LWC version.
Switching experimentalDecorators back to true breaks the way the TS is compiled.

Basically, just for information, for a component being:

import { LightningElement, api } from 'lwc';

export default class MyTypescriptLwc extends LightningElement {
    @api myNumber: string;
}

with this tsconfig.json:

{
  "extends": "../../../../.sfdx/tsconfig.sfdx.json",
  "include": ["**/*.ts", "../../../../.sfdx/typings/lwc/**/*.d.ts"],
  "exclude": ["**/__tests__/**"],
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "experimentalDecorators": false
  }
}

I'm getting the decorator error, but if don't enable noEmitOnError the LWC is compiled correctly:

import { LightningElement, api } from 'lwc';
export default class MyTypescriptLwc extends LightningElement {
    @api
    myNumber;
}

If I only change experimentalDecorators to true in tsconfig.json, I'm getting this:

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { LightningElement, api } from 'lwc';
export default class MyTypescriptLwc extends LightningElement {
    myNumber;
}
__decorate([
    api
], MyTypescriptLwc.prototype, "myNumber", void 0);

@wjhsf
Copy link
Contributor

wjhsf commented Oct 10, 2024

It's admittedly less than ideal, but one thing you can do to paper over the issue is add // @ts-expect-error Modern decorators are incompatible with the Salesforce VS Code extension. Or use // @ts-ignore if you want it to work in VS Code and in other environments.

@SCWells72
Copy link

One issue I'm also seeing is that the default ESLint rules don't seem to like TypeScript very much. They yield a nice, generic ESLint: Parsing error: Unexpected token (xx:yy) on the first strongly-typed property (or likely member) encountered in the type body. It looks like the resolution is to add dependencies on @typescript-eslint/parser and @typescript-eslint/eslint-plugin, though when I tried to do so, I ended up with version conflicts with what's already installed by the default package.json from a CLI-created project.

Has anyone resolved this short of just disabling ESLint in TypeScript-based projects? Is this something that will be rolled back into the CLI project template's sfdx-project.json and/or .eslintrc.json?

@wjhsf
Copy link
Contributor

wjhsf commented Oct 22, 2024

It looks like the resolution is to add dependencies on @typescript-eslint/parser and @typescript-eslint/eslint-plugin, though when I tried to do so, I ended up with version conflicts with what's already installed by the default package.json from a CLI-created project.

What gets installed with a CLI-created project? And can you elaborate more on the conflicts? Is it just breaking changes to rules between versions of typescript-eslint, or is it something else?

Something to be aware of is that eslint-plugin-lwc was written using the babel parser. Because eslint only supports one parser per file, you will end up with some rules not working correctly. Most of the incompatibility is in code involving TypeScript features, because the parsers build different ASTs (their ASTs are mostly compatible for JS features). I've only seen false negatives occur, no false positives, so you shouldn't have to deal with any invalid errors.

One mitigation for this is to run eslint in two passes, one with each parser. I'm also hoping to eventually add typescript-eslint support to the plugin (salesforce/eslint-plugin-lwc#153).

@SCWells72
Copy link

SCWells72 commented Oct 22, 2024

@wjhsf, I'm able to reproduce this very easily as follows:

$ sf project generate -d . -n eslint_typescript_error
$ cd eslint_typescript_error

Edit package.json to use "eslint": "^8.57.1" or the next step will fail due to forcedotcom/cli#3065.

$ npm install
$ sf lightning generate component --type lwc -n tsComponent -d force-app/main/default/lwc
$ cd force-app/main/default/lwc/tsComponent
$ mv tsComponent.js tsComponent.ts

Edit tsComponent.ts to be:

import { api, LightningElement } from 'lwc';

export default class TsComponent extends LightningElement {
    @api myProperty: string;
}

Run ESLint and it fails as noted previously due to a presumptive lack of TypeScript config:

$ npx eslint tsComponent.ts

/path/to/eslint_typescript_error/force-app/main/default/lwc/tsComponent/tsComponent.ts
  4:19  error  Parsing error: Unexpected token (4:19)

✖ 1 problem (1 error, 0 warnings)

If you then try to add the aforementioned dependencies, you get the following failures:

$ npm install @typescript-eslint/parser --save-dev

npm warn ERESOLVE overriding peer dependency
npm warn While resolving: salesforce-app@1.0.0
npm warn Found: @typescript-eslint/parser@6.21.0
npm warn node_modules/@typescript-eslint/parser
npm warn   @typescript-eslint/parser@"^6.11.0" from @salesforce/wire-service-jest-util@4.1.4
npm warn   node_modules/@salesforce/wire-service-jest-util
npm warn     @salesforce/wire-service-jest-util@"4.1.4" from @salesforce/sfdx-lwc-jest@5.1.0
npm warn     node_modules/@salesforce/sfdx-lwc-jest
npm warn   2 more (@typescript-eslint/eslint-plugin, the root project)
npm warn
npm warn Could not resolve dependency:
npm warn peer @typescript-eslint/parser@"^6.0.0 || ^6.0.0-alpha" from @typescript-eslint/eslint-plugin@6.21.0
npm warn node_modules/@typescript-eslint/eslint-plugin
npm warn   @typescript-eslint/eslint-plugin@"^6.11.0" from @salesforce/wire-service-jest-util@4.1.4
npm warn   node_modules/@salesforce/wire-service-jest-util
npm warn   1 more (eslint-plugin-jest)
npm warn ERESOLVE overriding peer dependency
npm warn While resolving: salesforce-app@1.0.0
npm warn Found: @typescript-eslint/parser@6.21.0
npm warn node_modules/@typescript-eslint/parser
npm warn   @typescript-eslint/parser@"^6.11.0" from @salesforce/wire-service-jest-util@4.1.4
npm warn   node_modules/@salesforce/wire-service-jest-util
npm warn     @salesforce/wire-service-jest-util@"4.1.4" from @salesforce/sfdx-lwc-jest@5.1.0
npm warn     node_modules/@salesforce/sfdx-lwc-jest
npm warn   2 more (@typescript-eslint/eslint-plugin, the root project)
npm warn
npm warn Could not resolve dependency:
npm warn peer @typescript-eslint/parser@"^6.0.0 || ^6.0.0-alpha" from @typescript-eslint/eslint-plugin@6.21.0
npm warn node_modules/@typescript-eslint/eslint-plugin
npm warn   @typescript-eslint/eslint-plugin@"^6.11.0" from @salesforce/wire-service-jest-util@4.1.4
npm warn   node_modules/@salesforce/wire-service-jest-util
npm warn   1 more (eslint-plugin-jest)

and:

$ npm install @typescript-eslint/eslint-plugin --save-dev

npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: salesforce-app@1.0.0
npm error Found: @typescript-eslint/parser@6.21.0
npm error node_modules/@typescript-eslint/parser
npm error   dev @typescript-eslint/parser@"^6.21.0" from the root project
npm error
npm error Could not resolve dependency:
npm error peer @typescript-eslint/parser@"^8.0.0 || ^8.0.0-alpha.0" from @typescript-eslint/eslint-plugin@8.11.0
npm error node_modules/@typescript-eslint/eslint-plugin
npm error   dev @typescript-eslint/eslint-plugin@"*" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry
npm error this command with --force or --legacy-peer-deps
npm error to accept an incorrect (and potentially broken) dependency resolution.
npm error
npm error
npm error For a full report see:
npm error C:\Users\Scott\AppData\Local\npm-cache\_logs\2024-10-22T19_26_11_914Z-eresolve-report.txt
npm error A complete log of this run can be found in: C:\Users\Scott\AppData\Local\npm-cache\_logs\2024-10-22T19_26_11_914Z-debug-0.log

Let me know if that doesn't provide a sufficient level of detail around this issue.

@wjhsf
Copy link
Contributor

wjhsf commented Oct 23, 2024

Short version: Upgrade to @salesforce/sfdx-lwc-jest to latest (currently 7.0.0) and then you should be able to install as normal. This might break your tests, but only because of dependency bumps.


Long version:

Problem

  • Newly generated projects use @salesforce/sfdx-lwc-jest@5.1.0.
  • @salesforce/sfdx-lwc-jest@5.1.0 uses @salesforce/wire-service-jest-util@4.1.4 (source).
  • @salesforce/wire-service-jest-util@4.1.4 has a dependency on @typescript-eslint/eslint-plugin@^6.11.0 and @typescript-eslint/parser@^6.11.0 (source).
  • @typescript-eslint/eslint-plugin has a peer dependency on @typescript-eslint/parser, but not vice versa.
  • npm install @typescript-eslint/parser seems to identify that we already have the package in our dependencies, and installs a matching version, 6.21.0.
  • npm install @typescript-eslint/eslint-plugin does not identify that we're already using 6.21.0 and tries to install latest, 8.11.0. It has a peer dependency on @typescript-eslint/parser@8.11.0, but we have 6.21.0 installed, so npm complains.

I'm not 100% sure of what's going on with npm in those last two steps, but that's what seems to be happening.

Solution

Any of these actions will fix the issue:

  • npm install @salesforce/sfdx-lwc-jest@latest @typescript-eslint/eslint-plugin @typescript-eslint/parser. This updates @salesforce/wire-service-jest-util to the fixed version.

    • The generated project uses @salesforce/sfdx-lwc-jest@5.1.0, but latest is currently 7.0.0. Be aware that this may break your tests, although per the release notes, it seems like mostly dependency upgrades.
  • Add "@salesforce/wire-service-jest-util": "^4.1.5" to your package.json overrides, run npm install, and then run npm install @typescript-eslint/eslint-plugin @typescript-eslint/parser.

  • npm install @typescript-eslint/eslint-plugin @typescript-eslint/parser --legacy-peer-deps. Legacy peer dep behavior is more lenient.

  • npm install @typescript-eslint/eslint-plugin; npm install @typescript-eslint/parser. I don't know why it works if you install them this way around, but it does!

  • Do nothing! The typescript-eslint packages are already (accidentally) included. So you can use them in your eslint config without installing anything. They're older versions, though, so you may get a warning about unsupported TypeScript versions.

    • Don't actually do this. It'll break as soon as you bump @salesforce/sfdx-lwc-jest or @salesforce/wire-service-jest-util.

I'll also follow up internally to get generated projects using the latest sfdx-lwc-jest.

@SCWells72
Copy link

Thanks so much for your in-depth analysis of this issue and the potential solutions/workaround. With your recommendation plus this config info:

https://typescript-eslint.io/getting-started/

I've been able to get it going nicely. Again, much appreciated, and anything you can do to help make sure this is on the appropriate teams' lists.

@wjhsf
Copy link
Contributor

wjhsf commented Oct 25, 2024

I've verified that you can now install the typescript-eslint packages without issue when using the newest nightly build of the CLI, v2.65.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants