Last active
March 23, 2024 02:33
-
-
Save hyrious/7120a56c593937457c0811443563e017 to your computer and use it in GitHub Desktop.
plugin to get rid of '__require' in esbuild
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var RequireToImportPlugin = { | |
name: 'require-to-import', | |
setup({ onResolve, onLoad, esbuild }) { | |
function matchBrace(text, from) { | |
if (!(text[from] === '(')) return -1; | |
let i, k = 1; | |
for (i = from + 1; i < text.length && k > 0; ++i) { | |
if (text[i] === '(') k++; | |
if (text[i] === ')') k--; | |
} | |
let to = i - 1; | |
if (!(text[to] === ')') || k !== 0) return -1; | |
return to; | |
} | |
function makeName(path) { | |
return path.replace(/-(\w)/g, (_, x) => x.toUpperCase()) | |
.replace(/[^$_a-zA-Z0-9]/g, '_'); | |
} | |
onLoad({ filter: /\.c?js/ }, async args => { | |
let contents = await fs.readFile(args.path, 'utf8') | |
let warnings | |
try { | |
({ warnings } = await esbuild.transform(contents, { format: 'esm', logLevel: 'silent' })) | |
} catch (err) { | |
({ warnings } = err) | |
} | |
let lines = contents.split('\n') | |
if (warnings && warnings.some(e => e.text.includes('"require" to "esm"'))) { | |
let modifications = [], imports = [] | |
for (const { location: { line, lineText, column, length } } of warnings) { | |
// "require|here|(" | |
let left = column + length | |
// "require('a'|here|)" | |
let right = matchBrace(lineText, left) | |
if (right === -1) continue; | |
// "'a'" | |
let raw = lineText.slice(left + 1, right) | |
let path | |
try { | |
// 'a' | |
path = eval(raw) // or, write a real js lexer to parse that | |
if (typeof path !== 'string') continue; // print warnings about dynamic require | |
} catch (e) { | |
continue | |
} | |
let name = `__import_${makeName(path)}` | |
// "import __import_a from 'a'" | |
let import_statement = `import ${name} from ${raw};` | |
// rewrite "require('a')" -> "__import_a" | |
let offset = lines.slice(0, line - 1).map(line => line.length).reduce((a, b) => a + 1 + b, 0) | |
modifications.push([offset + column, offset + right + 1, name]) | |
imports.push(import_statement) | |
} | |
if (imports.length === 0) return null; | |
imports = [...new Set(imports)] | |
let offset = 0 | |
for (const [start, end, name] of modifications) { | |
contents = contents.slice(0, start + offset) + name + contents.slice(end + offset) | |
offset += name.length - (end - start) | |
} | |
contents = [...imports, 'module.exports', contents].join(';') // put imports at the first line, so sourcemaps will be ok | |
return { contents } | |
} | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The last way is most cheap and seems ok. I'll use that.