experience-lwc-typescript-migrate
Use when converting an existing JavaScript Lightning Web Component (.js, .html, .css) to TypeScript with full type annotations and a matching `.d.ts…
它会碰到什么
这一栏是扫描器报的事实,不是结论。命中多不等于有毒(安全工具、规则库、示例脚本本来就会包含危险写法),命中少也不等于干净。它和你手上的凭据、文件、网络有什么关系,需要你自己看。
技能内容
<!-- adk-managed-skill -->
Converting LWC to TypeScript
Convert a Lightning Web Component bundle from JavaScript to TypeScript. The
deliverable is a fully-typed .ts implementation plus a .d.ts file
that only exposes @api members (the public surface other LWCs consume).
When to Use This Skill
- User wants to migrate a single component or a folder of components from
.js to .ts.
- User needs a
.d.tsfor an existing LWC so other components (or an
external TypeScript host) can import it safely.
- User is adding type annotations to an already-renamed
.tsLWC that
hasn't been properly typed yet.
- User wants JSDoc-style type hints upgraded to real TypeScript types.
Prerequisites
- The component builds and runs correctly in JavaScript today.
gitis available (the rename must preserve history viagit mv).- A TypeScript compiler is wired into the build (either the SFDX TS
pipeline or a standalone tsc step).
Workflow
Step 1 — Read the component
Open every file in the bundle:
componentName/
├── componentName.js
├── componentName.html
├── componentName.css
└── (possibly) __tests__/, __utam__/, existing .d.ts
Understand:
- What extends
LightningElement? What is the class name? - Which fields and methods carry the
@apidecorator? - Which properties/methods have existing JSDoc (use as a type hint
starting point, but validate against actual usage — JSDoc lies).
- Which parameters / return types can you infer from how the code is
called internally?
Step 2 — Rename .js → .ts using git mv
git mv componentName/componentName.js componentName/componentName.ts
Repeat for any helper .js files in the bundle (unless they're already
.ts). Never plain mv — that loses the history link TypeScript
reviewers rely on.
Step 3 — Add type annotations in the .ts
Apply types in this priority order so you stop as soon as the public
contract is solid:
@apiproperties and methods first. Generate JSDoc if it's
missing, then translate JSDoc types to TS syntax (string, number,
boolean, Promise<T>). Validate each JSDoc claim against the code
before trusting it.
- Complex shapes become
interfaceortypealiases — not inline
shapes repeated everywhere.
- Optional members use
?only when the value is genuinely allowed
to be undefined. Do not sprinkle ? defensively.
- Private/internal state — still type it, but don't export the
types. Use private for members that must never be touched by
consumers.
- Event handlers — prefer precise DOM event types:
MouseEventforonclick(and other click-like handlers).click
is dispatched as a MouseEvent — including keyboard-activated
clicks — so typing it as PointerEvent would let handlers rely on
pointer-only fields (pointerType, pressure, etc.) that are
undefined in those cases.
PointerEventforonpointerdown/onpointerup/onpointermove
and other pointer* handlers where pointer-specific fields are
actually meaningful.
CustomEvent<{ detail: ... }>for LWC custom events.Eventis the last resort; document why when using it.
- Async methods always return
Promise<T>— never bareT. - Avoid
any. If you genuinely can't type something, useunknown
and narrow with a type guard.
Reference patterns
Load [[assets/type-patterns.ts|assets/type-patterns.ts]] as an inline example
covering property types, method types, and event handler types.
Step 4 — Generate the .d.ts
Create componentName.d.ts next to the .ts. It must:
- Contain only
@apimembers — no private state, no internal
methods, no lifecycle hooks unless they are themselves @api.
- Preserve
@apiJSDoc verbatim (including@type,@required,
@default, @param, @returns tags) directly above each declaration.
- Declare the LWC module namespace
c/componentName(or the org's
namespace if different).
Template: load [[assets/dts-template.ts|assets/dts-template.ts]] as the
starting .d.ts shape.
If the component has no @api members, still produce the module
declaration with a comment explaining there's no public surface — don't
skip the file.
Step 5 — Compile and test
- Run the TypeScript compiler (
tsc --noEmitor the build's equivalent).
Resolve every error before calling it done; no @ts-ignore patches.
- Run the component's existing Jest tests. The behavior should be
identical.
- Run the bundled consumer-finder unconditionally — empty output is a
valid result, not a reason to skip. The script resolves the search
paths from sfdx-project.json's packageDirectories (or falls back
to <project-root>), rejects any entry that escapes the project root,
and performs the LWC-import search internally so the invocation is
fully deterministic:
"<skill_dir>/scripts/find-consumers.sh" "<project-root>" "<componentName>"
For each match, confirm the consumer's expected types still align with
the new .d.ts public surface.
Step 6 — Expected final bundle shape
componentName/
├── componentName.ts # Main TypeScript implementation
├── componentName.html # Template (unchanged)
├── componentName.css # Styles (unchanged)
└── componentName.d.ts # Type definitions (new)
Verification Checklist
Before conversion:
- [ ] Component is valid JS and all tests pass.
- [ ] You've identified every
@apimember and its intended type.
After conversion:
- [ ]
git mvwas used so history is preserved. - [ ] Every variable and parameter in the
.tshas a concrete type
(no implicit any).
- [ ] Complex object shapes live in
interface/typealiases, not
inline repeats.
- [ ] Optional
?is only on genuinely optional fields. - [ ]
.d.tsexists, declaresc/componentName, extends
LightningElement, includes only @api members.
- [ ] Every
@apiJSDoc is preserved verbatim in the.d.ts. - [ ]
tscpasses with zero errors; no@ts-ignoreoranyused as a
workaround.
- [ ] Jest tests still pass.
Common Pitfalls
- Using
anyto silence errors. Solve the actual type instead.
If the value is truly unknown, use unknown + a type guard.
- Including private members in the
.d.ts. The.d.tsis the
public contract. Internal lifecycle and helpers must not leak.
- Losing JSDoc during the rename. Scan before and after — JSDoc
comments on @api members must appear in both the .ts and .d.ts.
- Skipping
git mv. Makes review miserable and confuses blame. - Forgetting async return types.
foo()with anasynckeyword
always returns a Promise. Declare it.
- Typing
onclickasPointerEvent.clickis aMouseEvent
(keyboard-triggered clicks included), so PointerEvent fields like
pointerType are undefined for those events. Type onclick as
MouseEvent; reserve PointerEvent for onpointer* handlers. Use
MouseEvent | TouchEvent only when the code branches on TouchEvent
distinctly.
Support Resources
想直接用这个技能?
本站把开放许可(MIT / Apache 等)的技能按仓库打包整理到网盘,点一下转存到你自己的网盘,不用一个个从 GitHub 拉。许可未声明的技能只给原始仓库链接,不打包。
它属于哪个仓库
plugins/builder/experience-lwc/skills/experience-lwc-typescript-migrate/SKILL.md同一个仓库里的其他技能
- commerce-b2b-open-code-components-integrate
- commerce-b2b-open-code-components-replace
- dx-devops-conflict-resolve
- dx-devops-pipeline-manage
- dx-devops-test-failures-analyze
- dx-devops-test-pipeline-configure
- dx-devops-test-suite-assignments-configure
- dx-devops-test-suite-run
- dx-devops-work-item-manage
- dx-app-analytics-query
- platform-agentexchange-partner-offers-configure
- automation-sandbox-post-copy-config-generate
同名技能的其他版本
有 2 个不同仓库或目录里都有叫 experience-lwc-typescript-migrate 的技能。它们内容并不相同,别混用:
- forcedotcom/sf-skills — Use when converting an existing JavaScript Lightning Web Component (.js, .html, .css) to T