Add detailed plan for migrating to moduleResolution node16/nodenext with file extensions in imports. This plan addresses: - #154 - Upgrade moduleResolution from node to node16/nodenext - #110 - Published ESM code has imports without file extensions - #64 - expressions: ERR_MODULE_NOT_FOUND attempting to run example demo script - #146 - Can not import @actions/workflow-parser The migration will: 1. Upgrade TypeScript to 5.7+ 2. Enable rewriteRelativeImportExtensions 3. Add .ts extensions to all relative imports 4. Update JSON imports to use import attributes Implementation will begin after pending PRs are merged to avoid conflicts.
272 lines
7.8 KiB
Markdown
272 lines
7.8 KiB
Markdown
# ESM Migration Plan: Add File Extensions to Imports
|
|
|
|
## Overview
|
|
|
|
This document outlines the plan to migrate from TypeScript's deprecated `"moduleResolution": "node"` (node10) to `"moduleResolution": "node16"` or `"nodenext"`. This change is necessary because the published ESM packages have extensionless imports that don't work correctly in modern ESM environments.
|
|
|
|
## Issues Fixed
|
|
|
|
This migration will resolve the following issues:
|
|
|
|
- **#154** - Upgrade `moduleResolution` from `node` to `node16` or `nodenext` in tsconfig
|
|
- **#110** - Published ESM code has imports without file extensions
|
|
- **#64** - expressions: ERR_MODULE_NOT_FOUND attempting to run example demo script
|
|
- **#146** - Can not import `@actions/workflow-parser`
|
|
|
|
## Problem Statement
|
|
|
|
### Current State
|
|
|
|
All packages use `"moduleResolution": "node"`:
|
|
|
|
| Package | moduleResolution | TypeScript |
|
|
|---------|------------------|------------|
|
|
| expressions | `"node"` | ^4.7.4 |
|
|
| workflow-parser | `"node"` | ^4.8.4 |
|
|
| languageservice | `"node"` | ^4.8.4 |
|
|
| languageserver | `"node"` | ^4.8.4 |
|
|
| browser-playground | `"Node16"` ✅ | ^4.9.4 |
|
|
|
|
This causes TypeScript to emit code like:
|
|
```javascript
|
|
// Published to npm - INVALID ESM
|
|
export { Expr } from "./ast"; // Missing .js extension!
|
|
```
|
|
|
|
### Why This Fails
|
|
|
|
ESM in Node.js 12+ **requires** explicit file extensions. When users try to import these packages:
|
|
|
|
```javascript
|
|
// User's code
|
|
import { Expr } from "@actions/expressions";
|
|
```
|
|
|
|
Node.js fails with:
|
|
```
|
|
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../node_modules/@actions/expressions/dist/ast'
|
|
```
|
|
|
|
## Migration Strategy
|
|
|
|
### Option A: TypeScript 5.7+ with `rewriteRelativeImportExtensions` (Recommended)
|
|
|
|
TypeScript 5.7 introduced a new compiler option that automatically rewrites `.ts` extensions to `.js` in output:
|
|
|
|
```jsonc
|
|
{
|
|
"compilerOptions": {
|
|
"moduleResolution": "node16", // or "nodenext"
|
|
"rewriteRelativeImportExtensions": true
|
|
}
|
|
}
|
|
```
|
|
|
|
**Source code:**
|
|
```typescript
|
|
import { Expr } from "./ast.ts";
|
|
```
|
|
|
|
**Compiled output:**
|
|
```javascript
|
|
export { Expr } from "./ast.js";
|
|
```
|
|
|
|
**Pros:**
|
|
- Source uses `.ts` extensions (matches actual files)
|
|
- Works with Deno (which requires `.ts` extensions)
|
|
- TypeScript automatically transforms to `.js`
|
|
- Modern, forward-looking approach
|
|
|
|
**Cons:**
|
|
- Requires TypeScript 5.7+
|
|
- Relatively new feature
|
|
|
|
### Option B: Manual `.js` Extensions
|
|
|
|
Use `.js` extensions in source TypeScript files:
|
|
|
|
```typescript
|
|
import { Expr } from "./ast.js"; // Points to .ts file, but use .js extension
|
|
```
|
|
|
|
**Pros:**
|
|
- Works with TypeScript 4.7+ (with node16 moduleResolution)
|
|
- Well-established pattern
|
|
|
|
**Cons:**
|
|
- Confusing - `.js` files don't exist at write time
|
|
- Doesn't work with Deno out of the box
|
|
|
|
### Recommendation
|
|
|
|
**Use Option A** - TypeScript 5.7+ with `rewriteRelativeImportExtensions`:
|
|
- Cleaner developer experience (`.ts` imports match actual files)
|
|
- Better Deno compatibility
|
|
- TypeScript 5.x upgrade is already on the roadmap (see Dependabot PRs #208-212)
|
|
|
|
## Scope of Changes
|
|
|
|
### Statistics
|
|
|
|
- **~73 source files** need import updates
|
|
- **~176 relative imports** need `.ts` extensions added
|
|
- **5 packages** need tsconfig updates (browser-playground already uses node16)
|
|
- **6 JSON imports** need attention
|
|
|
|
### Package-by-Package Breakdown
|
|
|
|
#### expressions/
|
|
- tsconfig.json: Update `moduleResolution`
|
|
- Add `.ts` extensions to all relative imports
|
|
- Files: ~15 source files
|
|
|
|
#### workflow-parser/
|
|
- tsconfig.json: Update `moduleResolution`
|
|
- Add `.ts` extensions to all relative imports
|
|
- JSON import: `workflow-v1.0.min.json` - needs `with { type: "json" }` or type assertion
|
|
- Files: ~25 source files
|
|
|
|
#### languageservice/
|
|
- tsconfig.json: Update `moduleResolution`
|
|
- Add `.ts` extensions to all relative imports
|
|
- JSON imports: webhooks, schedule, workflow_call, descriptions - need handling
|
|
- Files: ~30 source files
|
|
|
|
#### languageserver/
|
|
- tsconfig.json: Update `moduleResolution`
|
|
- Add `.ts` extensions to all relative imports
|
|
- Files: ~10 source files
|
|
|
|
#### browser-playground/
|
|
- Already uses `"moduleResolution": "Node16"` ✅
|
|
- May need import extension updates
|
|
- Bundled via webpack, so may have different requirements
|
|
|
|
### JSON Import Handling
|
|
|
|
JSON imports require special handling in node16/nodenext. Options:
|
|
|
|
1. **Import Attributes (Node 20.10+)**
|
|
```typescript
|
|
import schema from "./workflow-v1.0.json" with { type: "json" };
|
|
```
|
|
Requires: TypeScript 5.3+, Node 20.10+
|
|
|
|
2. **fs.readFileSync at runtime**
|
|
```typescript
|
|
const schema = JSON.parse(fs.readFileSync(new URL("./schema.json", import.meta.url), "utf8"));
|
|
```
|
|
Works with any Node version but requires runtime file access
|
|
|
|
3. **Keep resolveJsonModule with assertion**
|
|
```typescript
|
|
import schema from "./schema.json" assert { type: "json" };
|
|
```
|
|
Note: `assert` is deprecated in favor of `with`
|
|
|
|
**Recommendation:** Use Import Attributes (`with { type: "json" }`) since we already require Node >= 18 and will bump TypeScript to 5.x.
|
|
|
|
## Implementation Steps
|
|
|
|
### Phase 1: Prerequisites (Wait for merge)
|
|
|
|
- [ ] Merge all pending PRs to avoid conflicts
|
|
- PR #242 (activity types)
|
|
- Any other pending PRs
|
|
|
|
### Phase 2: TypeScript Upgrade
|
|
|
|
- [ ] Upgrade TypeScript to 5.7+ in all packages
|
|
- [ ] Merge Dependabot PRs #208-212 or create unified upgrade PR
|
|
- [ ] Verify all tests pass after upgrade
|
|
|
|
### Phase 3: tsconfig Updates
|
|
|
|
Update each package's `tsconfig.json`:
|
|
|
|
```jsonc
|
|
{
|
|
"compilerOptions": {
|
|
"module": "node16", // or "nodenext"
|
|
"moduleResolution": "node16", // or "nodenext"
|
|
"rewriteRelativeImportExtensions": true
|
|
}
|
|
}
|
|
```
|
|
|
|
### Phase 4: Add Extensions to Imports
|
|
|
|
For each package, update all relative imports:
|
|
|
|
```typescript
|
|
// Before
|
|
import { Expr } from "./ast";
|
|
import { Parser } from "./parser";
|
|
|
|
// After
|
|
import { Expr } from "./ast.ts";
|
|
import { Parser } from "./parser.ts";
|
|
```
|
|
|
|
**Automation approach:**
|
|
```bash
|
|
# Can use sed/perl or a codemod tool
|
|
find src -name "*.ts" -exec sed -i "s/from '\.\//from '.\\//g" {} \;
|
|
# More sophisticated regex needed - consider using ts-morph or jscodeshift
|
|
```
|
|
|
|
### Phase 5: JSON Import Updates
|
|
|
|
Update JSON imports to use import attributes:
|
|
|
|
```typescript
|
|
// Before
|
|
import schema from "./workflow-v1.0.min.json";
|
|
|
|
// After
|
|
import schema from "./workflow-v1.0.min.json" with { type: "json" };
|
|
```
|
|
|
|
### Phase 6: Verification
|
|
|
|
- [ ] Run `npm run build` in all packages
|
|
- [ ] Run `npm test` in all packages
|
|
- [ ] Test importing published packages in:
|
|
- [ ] Node.js ESM mode (`"type": "module"`)
|
|
- [ ] Vite project
|
|
- [ ] Deno (bonus)
|
|
- [ ] Verify browser-playground still works
|
|
|
|
### Phase 7: Documentation
|
|
|
|
- [ ] Update READMEs with any new requirements
|
|
- [ ] Add migration notes to CHANGELOG
|
|
|
|
## Risks and Mitigations
|
|
|
|
| Risk | Impact | Mitigation |
|
|
|------|--------|------------|
|
|
| Breaking change for consumers | High | Semver major version bump |
|
|
| Import attributes not supported in older bundlers | Medium | Test with webpack, vite, rollup |
|
|
| Some edge cases with re-exports | Low | Careful testing |
|
|
| browser-playground uses webpack | Low | Webpack handles bundling differently |
|
|
|
|
## Timeline
|
|
|
|
1. **Wait for pending PRs** - ~1 week
|
|
2. **TypeScript upgrade** - 1 day
|
|
3. **Import migration** - 2-3 days
|
|
4. **Testing & verification** - 1 day
|
|
5. **Documentation** - 0.5 day
|
|
|
|
**Total: ~1-2 weeks** after dependencies are ready
|
|
|
|
## References
|
|
|
|
- [TypeScript moduleResolution reference](https://www.typescriptlang.org/docs/handbook/modules/reference.html)
|
|
- [TypeScript 5.7 rewriteRelativeImportExtensions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#path-rewriting-for-relative-paths)
|
|
- [Node.js ESM mandatory extensions](https://nodejs.org/api/esm.html#mandatory-file-extensions)
|
|
- [Import Attributes proposal](https://github.com/tc39/proposal-import-attributes)
|
|
- [Community fork that works](https://github.com/boxbuild-io/actions-languageservices/commit/077fb2b58dfd2cca3d6e3df1fdf9e26e75db24ae)
|