Last active
February 16, 2025 18:16
-
-
Save hyrious/7120a56c593937457c0811443563e017 to your computer and use it in GitHub Desktop.
plugin to get rid of '__require' in esbuild
This file contains hidden or 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 } | |
} | |
}) | |
} | |
} |
The last way is most cheap and seems ok. I'll use that.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
After some tests, I found that I can bundle
export { render } from "react-dom"
correctly inminifySyntax: true
, but notminifySyntax: false
. This is because it has a top levelif
statement:When setting
process.env.NODE_ENV
to"production"
, this file becomes empty and esbuild can not know whether its esm or cjs -- in esm, it exports nothing; in cjs, it exports a default{}
; esbuild will treat it as esm.Then, in the entry file of react-dom, it imports that file:
This import statement will cause that error: esbuild cannot find a default export from that file.
So, a more safer way: we import the namespace:
import * as ns from "some-file"; const default_value = ns["default"]
. But as a prop access statement, esbuild won't perform any tree-shaking on it.Besides, esbuild will also print another warning on it 🤷♂️:
Another way is we always provide a export in cjs modules, but it will includes some verbose code: