-
-
Notifications
You must be signed in to change notification settings - Fork 61
Fixes #79 Resolved ERR_MODULE_NOT_FOUND during Vitest ESM resolution by adding explicit .js extensions to ESM imports. #106
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
base: main
Are you sure you want to change the base?
Changes from all commits
2b11dc0
3d94563
3f749f6
d344c71
a988636
ff13137
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| #!/usr/bin/env node | ||
|
|
||
| /** | ||
| * Post-build script to fix ESM imports by adding .js extensions | ||
| * | ||
| * This script walks through the dist directory and adds .js extensions | ||
| * to all relative imports to ensure compatibility with strict Node.js ESM. | ||
| * It also handles directory imports by checking if index.js exists. | ||
| * | ||
| * Usage: node fix-imports-script.mjs [dist-directory] | ||
| */ | ||
|
|
||
| import { readdir, readFile, writeFile, access } from 'fs/promises'; | ||
| import { join, dirname } from 'path'; | ||
| import { fileURLToPath } from 'url'; | ||
|
|
||
| const __dirname = dirname(fileURLToPath(import.meta.url)); | ||
|
|
||
| // Get dist directory from command line argument or use default | ||
| const distDir = process.argv[2] || join(__dirname, 'node_modules', 'wallet-svelte-component', 'dist'); | ||
|
|
||
| console.log(`🔧 Fixing ESM imports in: ${distDir}\n`); | ||
|
|
||
| let filesFixed = 0; | ||
| let importsFixed = 0; | ||
|
|
||
| /** | ||
| * Recursively process all .js files in a directory | ||
| */ | ||
| async function fixImportsInDirectory(dir) { | ||
| try { | ||
| const files = await readdir(dir, { withFileTypes: true }); | ||
|
|
||
| for (const file of files) { | ||
| const fullPath = join(dir, file.name); | ||
|
|
||
| if (file.isDirectory()) { | ||
| await fixImportsInDirectory(fullPath); | ||
| } else if (file.name.endsWith('.js')) { | ||
| await fixImportsInFile(fullPath); | ||
| } | ||
| } | ||
| } catch (error) { | ||
| console.error(`❌ Error processing directory ${dir}:`, error.message); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Check if a path exists | ||
| */ | ||
| async function pathExists(path) { | ||
| try { | ||
| await access(path); | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Resolve the correct import path with extension | ||
| */ | ||
| async function resolveImportPath(importPath, fileDir) { | ||
| // Skip if already has extension | ||
| if (importPath.match(/\.(js|svelte|json)$/)) { | ||
| return importPath; | ||
| } | ||
|
|
||
| // Remove leading './' | ||
| const relativePath = importPath.replace(/^\.\//, ''); | ||
| const fullPath = join(fileDir, relativePath); | ||
|
|
||
| // Try path + .js (file) | ||
| if (await pathExists(fullPath + '.js')) { | ||
| return importPath + '.js'; | ||
| } | ||
|
|
||
| // Try path + /index.js (directory with index) | ||
| if (await pathExists(join(fullPath, 'index.js'))) { | ||
| return importPath + '/index.js'; | ||
| } | ||
|
|
||
| // Default to .js if neither exists | ||
| return importPath + '.js'; | ||
| } | ||
|
Comment on lines
+63
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle async function resolveImportPath(importPath, fileDir) {
- // Skip if already has extension
- if (importPath.match(/\.(js|svelte|json)$/)) {
+ // Skip if it already has *any* file extension (e.g. .css, .svg, .wasm, .map, ...)
+ // This script is only meant to fix extensionless ESM specifiers.
+ if (/\.[A-Za-z0-9]+$/.test(importPath)) {
return importPath;
}
- // Remove leading './'
- const relativePath = importPath.replace(/^\.\//, '');
- const fullPath = join(fileDir, relativePath);
+ // Keep './' and '../' semantics intact; `join` handles both.
+ const fullPath = join(fileDir, importPath); const patterns = [
// Pattern 1: export * from './path'
- /export\s+\*\s+from\s+(['"])(\.\/[^'"]+)\1/g,
+ /export\s+\*\s+from\s+(['"])(\.\.?(?:\/[^'"]+)+)\1/g,
// Pattern 2: export { ... } from './path'
- /export\s+{[^}]+}\s+from\s+(['"])(\.\/[^'"]+)\1/g,
+ /export\s+{[^}]+}\s+from\s+(['"])(\.\.?(?:\/[^'"]+)+)\1/g,
// Pattern 3: import ... from './path'
- /import\s+[^'"]+from\s+(['"])(\.\/[^'"]+)\1/g,
+ /import\s+[^'"]+from\s+(['"])(\.\.?(?:\/[^'"]+)+)\1/g,
// Pattern 4: import './path'
- /import\s+(['"])(\.\/[^'"]+)\1/g
+ /import\s+(['"])(\.\.?(?:\/[^'"]+)+)\1/g
];- // Skip if already has extension
- if (!path.match(/\.(js|svelte|json)$/)) {
+ // Skip if it already has any extension
+ if (!/\.[A-Za-z0-9]+$/.test(path)) {
matches.push({
fullMatch: match[0],
quote,
path,
index: match.index
});
}Also applies to: 98-107, 115-117 🤖 Prompt for AI Agents |
||
|
|
||
| /** | ||
| * Fix imports in a single JavaScript file | ||
| */ | ||
| async function fixImportsInFile(filePath) { | ||
| try { | ||
| let content = await readFile(filePath, 'utf-8'); | ||
| const originalContent = content; | ||
| let fileImportCount = 0; | ||
| const fileDir = dirname(filePath); | ||
|
|
||
| // Find all import/export statements that need fixing | ||
| const patterns = [ | ||
| // Pattern 1: export * from './path' | ||
| /export\s+\*\s+from\s+(['"])(\.\/[^'"]+)\1/g, | ||
| // Pattern 2: export { ... } from './path' | ||
| /export\s+{[^}]+}\s+from\s+(['"])(\.\/[^'"]+)\1/g, | ||
| // Pattern 3: import ... from './path' | ||
| /import\s+[^'"]+from\s+(['"])(\.\/[^'"]+)\1/g, | ||
| // Pattern 4: import './path' | ||
| /import\s+(['"])(\.\/[^'"]+)\1/g | ||
| ]; | ||
|
|
||
| let matches = []; | ||
| for (const pattern of patterns) { | ||
| let match; | ||
| while ((match = pattern.exec(content)) !== null) { | ||
| const quote = match[1]; | ||
| const path = match[2]; | ||
| // Skip if already has extension | ||
| if (!path.match(/\.(js|svelte|json)$/)) { | ||
| matches.push({ | ||
| fullMatch: match[0], | ||
| quote, | ||
| path, | ||
| index: match.index | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Sort by index in reverse to replace from end to start | ||
| matches.sort((a, b) => b.index - a.index); | ||
|
|
||
| // Process each match | ||
| for (const { fullMatch, quote, path } of matches) { | ||
| const newPath = await resolveImportPath(path, fileDir); | ||
| const newStatement = fullMatch.replace(path, newPath); | ||
| content = content.replace(fullMatch, newStatement); | ||
| fileImportCount++; | ||
| } | ||
|
Comment on lines
+127
to
+136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: - for (const { fullMatch, quote, path } of matches) {
+ for (const { fullMatch, quote, path, index } of matches) {
const newPath = await resolveImportPath(path, fileDir);
const newStatement = fullMatch.replace(path, newPath);
- content = content.replace(fullMatch, newStatement);
- fileImportCount++;
+ if (newStatement !== fullMatch) {
+ content =
+ content.slice(0, index) +
+ newStatement +
+ content.slice(index + fullMatch.length);
+ fileImportCount++;
+ }
}🤖 Prompt for AI Agents |
||
|
|
||
| // Only write if changes were made | ||
| if (content !== originalContent) { | ||
| await writeFile(filePath, content, 'utf-8'); | ||
| filesFixed++; | ||
| importsFixed += fileImportCount; | ||
| console.log(`✅ Fixed ${fileImportCount} imports in: ${filePath}`); | ||
| } | ||
| } catch (error) { | ||
| console.error(`❌ Error processing file ${filePath}:`, error.message); | ||
| } | ||
| } | ||
|
|
||
| // Run the script | ||
| (async () => { | ||
| try { | ||
| console.log('Starting import fixes...\n'); | ||
| await fixImportsInDirectory(distDir); | ||
| console.log(`\n🎉 Complete! Fixed ${importsFixed} imports across ${filesFixed} files.`); | ||
| } catch (error) { | ||
| console.error('❌ Script failed:', error); | ||
| process.exit(1); | ||
| } | ||
| })(); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Architectural concern: Patching node_modules is not sustainable.
The default path targets
node_modules/wallet-svelte-component/dist, meaning this script modifies an installed dependency. Changes to node_modules are lost on reinstall, requiring this script to run after everynpm install.Consider these more maintainable alternatives:
wallet-svelte-componentpackage to ship with proper ESM imports