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.
7.8 KiB
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
moduleResolutionfromnodetonode16ornodenextin 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:
// 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:
// 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:
{
"compilerOptions": {
"moduleResolution": "node16", // or "nodenext"
"rewriteRelativeImportExtensions": true
}
}
Source code:
import { Expr } from "./ast.ts";
Compiled output:
export { Expr } from "./ast.js";
Pros:
- Source uses
.tsextensions (matches actual files) - Works with Deno (which requires
.tsextensions) - 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:
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 -
.jsfiles 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 (
.tsimports 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
.tsextensions 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
.tsextensions to all relative imports - Files: ~15 source files
workflow-parser/
- tsconfig.json: Update
moduleResolution - Add
.tsextensions to all relative imports - JSON import:
workflow-v1.0.min.json- needswith { type: "json" }or type assertion - Files: ~25 source files
languageservice/
- tsconfig.json: Update
moduleResolution - Add
.tsextensions to all relative imports - JSON imports: webhooks, schedule, workflow_call, descriptions - need handling
- Files: ~30 source files
languageserver/
- tsconfig.json: Update
moduleResolution - Add
.tsextensions 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:
-
Import Attributes (Node 20.10+)
import schema from "./workflow-v1.0.json" with { type: "json" };Requires: TypeScript 5.3+, Node 20.10+
-
fs.readFileSync at runtime
const schema = JSON.parse(fs.readFileSync(new URL("./schema.json", import.meta.url), "utf8"));Works with any Node version but requires runtime file access
-
Keep resolveJsonModule with assertion
import schema from "./schema.json" assert { type: "json" };Note:
assertis deprecated in favor ofwith
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:
{
"compilerOptions": {
"module": "node16", // or "nodenext"
"moduleResolution": "node16", // or "nodenext"
"rewriteRelativeImportExtensions": true
}
}
Phase 4: Add Extensions to Imports
For each package, update all relative imports:
// Before
import { Expr } from "./ast";
import { Parser } from "./parser";
// After
import { Expr } from "./ast.ts";
import { Parser } from "./parser.ts";
Automation approach:
# 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:
// 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 buildin all packages - Run
npm testin all packages - Test importing published packages in:
- Node.js ESM mode (
"type": "module") - Vite project
- Deno (bonus)
- Node.js ESM mode (
- 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
- Wait for pending PRs - ~1 week
- TypeScript upgrade - 1 day
- Import migration - 2-3 days
- Testing & verification - 1 day
- Documentation - 0.5 day
Total: ~1-2 weeks after dependencies are ready