// The v7 → v8 source transform. Deliberately regex-based and honest about // it: simple top-level CJS patterns are converted mechanically, package // specifiers are remapped, and everything the regexes cannot prove is // reported as a note instead of guessed at. // old specifier → new specifier export const SPECIFIERS = { choo: '@choojs/core', 'choo/html': '@choojs/html', 'choo/html/raw': '@choojs/html/raw', 'choo/component': '@choojs/component', nanohtml: '@choojs/html', 'nanohtml/raw': '@choojs/html/raw', nanomorph: '@choojs/html/morph', nanocomponent: '@choojs/component', 'choo-devtools': '@choojs/devtools' } // old specifier → guidance (no direct replacement package) export const RETIRED = { nanobus: "built into @choojs/core — use app.emitter, or import Nanobus from '@choojs/core' internals is no longer needed", nanorouter: 'built into @choojs/core — app.route covers it', nanohref: 'built into @choojs/core — link handling is automatic', nanotiming: "built into @choojs/core — import from '@choojs/core/timing' if you need it directly", nanoraf: 'retired — use requestAnimationFrame, or rely on choo render batching', nanoquery: 'retired — state.query is built in; use URLSearchParams directly elsewhere', nanoassert: 'retired — use plain checks or node:assert', nanolru: 'retired — the component cache is built into @choojs/core', 'choo-lazy-route': "replaced by lazy() from '@choojs/core': app.route('/x', lazy(() => import('./x.js')))", 'choo-service-worker': 'kept as a concept; bankai v10 will inject the asset manifest — check docs before migrating this one' } export default function transform (source) { const notes = [] let code = source // --- CJS → ESM, simple top-level forms --- // const { a, b } = require('x') code = code.replace( /^(\s*)(?:var|let|const)\s*\{([^}]+)\}\s*=\s*require\((['"])([^'"]+)\3\)\s*;?[^\S\n]*$/gm, (m, indent, names, q, spec) => `${indent}import {${names}} from '${spec}'` ) // const X = require('x').default / .thing code = code.replace( /^(\s*)(?:var|let|const)\s+([A-Za-z_$][\w$]*)\s*=\s*require\((['"])([^'"]+)\3\)\.default\s*;?[^\S\n]*$/gm, (m, indent, name, q, spec) => `${indent}import ${name} from '${spec}'` ) code = code.replace( /^(\s*)(?:var|let|const)\s+([A-Za-z_$][\w$]*)\s*=\s*require\((['"])([^'"]+)\3\)\.([A-Za-z_$][\w$]*)\s*;?[^\S\n]*$/gm, (m, indent, name, q, spec, prop) => `${indent}import { ${prop} as ${name} } from '${spec}'` ) // const X = require('x') code = code.replace( /^(\s*)(?:var|let|const)\s+([A-Za-z_$][\w$]*)\s*=\s*require\((['"])([^'"]+)\3\)\s*;?[^\S\n]*$/gm, (m, indent, name, q, spec) => `${indent}import ${name} from '${spec}'` ) // bare require('x') statements (side-effect imports) code = code.replace( /^(\s*)require\((['"])([^'"]+)\2\)\s*;?[^\S\n]*$/gm, (m, indent, q, spec) => `${indent}import '${spec}'` ) // module.exports = X code = code.replace(/^(\s*)module\.exports\s*=\s*/gm, (m, indent, offset) => { return `${indent}export default ` }) // --- specifier remapping (imports and any requires we couldn't convert) --- code = code.replace( /(from\s*|import\s*\(\s*|import\s+|require\s*\(\s*)(['"])([^'"]+)\2/g, (m, lead, q, spec) => { if (SPECIFIERS[spec]) return `${lead}${q}${SPECIFIERS[spec]}${q}` if (RETIRED[spec]) { notes.push(`'${spec}': ${RETIRED[spec]}`) } return m } ) // --- things we refuse to guess at --- if (/module\.exports\.[A-Za-z_$]/.test(code)) { notes.push('module.exports. assignments found — convert to named `export` statements by hand') } if (/\brequire\s*\(/.test(code)) { notes.push('require() calls remain (non-top-level or dynamic) — convert to import/await import() by hand') } if (/\bexports\.[A-Za-z_$]/.test(code)) { notes.push('bare exports. assignments found — convert to named `export` statements by hand') } return { code, changed: code !== source, notes: [...new Set(notes)] } }