@hyrious/esbuild-plugin-commonjs
Advanced tools
Comparing version 0.2.2 to 0.2.3
@@ -76,2 +76,2 @@ import { Plugin } from 'esbuild'; | ||
export { CommonJSOptions, TransformConfig, commonjs, commonjs as default }; | ||
export { type CommonJSOptions, type TransformConfig, commonjs, commonjs as default }; |
37
index.js
@@ -21,2 +21,6 @@ "use strict"; | ||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||
// If the importer is in node compatibility mode or this is not an ESM | ||
// file that has been converted to a CommonJS file using a Babel- | ||
// compatible transform (i.e. "__esModule" has not been set), then set | ||
// "default" to the CommonJS "module.exports" for node compatibility. | ||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||
@@ -213,8 +217,9 @@ mod | ||
transform = false, | ||
transformConfig | ||
transformConfig, | ||
requireReturnsDefault = true, | ||
ignore | ||
} = {}) { | ||
let init_cjs_module_lexer; | ||
if (transform) { | ||
init_cjs_module_lexer = import("cjs-module-lexer"); | ||
} | ||
const init_cjs_module_lexer = transform ? import("cjs-module-lexer") : void 0; | ||
const use_default_export = typeof requireReturnsDefault === "function" ? requireReturnsDefault : (_path) => requireReturnsDefault; | ||
const is_ignored = typeof ignore === "function" ? ignore : Array.isArray(ignore) ? (path) => ignore.includes(path) : () => false; | ||
return { | ||
@@ -255,8 +260,8 @@ name: "commonjs", | ||
cjsExports = parseCJS(contents); | ||
let { behavior, exports, sideEffects } = typeof willTransform === "object" ? willTransform : {}; | ||
let { behavior, exports: exports2, sideEffects } = typeof willTransform === "object" ? willTransform : {}; | ||
behavior ?? (behavior = (transformConfig == null ? void 0 : transformConfig.behavior) ?? "node"); | ||
exports = orderedUniq(cjsExports.exports.concat(exports ?? [])); | ||
exports2 = orderedUniq(cjsExports.exports.concat(exports2 ?? [])); | ||
sideEffects ?? (sideEffects = (transformConfig == null ? void 0 : transformConfig.sideEffects) ?? true); | ||
let exportDefault = behavior === "node" ? "export default exports;" : "export default exports.__esModule ? exports.default : exports;"; | ||
let exportsMap = exports.map((e) => [e, makeLegalIdentifier(e)]); | ||
let exportsMap = exports2.map((e) => [e, makeLegalIdentifier(e)]); | ||
if (exportsMap.some(([e]) => e === "default")) { | ||
@@ -273,2 +278,3 @@ if (behavior === "node") { | ||
transformed = [ | ||
// make sure we don't manipulate the first line so that sourcemap is fine | ||
reexports + "var mod, exports = /* @__PURE__ */ ((exports, module) => {" + contents, | ||
@@ -303,5 +309,5 @@ "return module.exports})((mod = { exports: {} }).exports, mod); " + exportDefault | ||
let suffix = 2; | ||
while (contents.includes(`${name}_${suffix}`)) | ||
while (contents.includes(`${name}${suffix}`)) | ||
suffix++; | ||
name = `${name}_${suffix}`; | ||
name = `${name}${suffix}`; | ||
} | ||
@@ -312,4 +318,6 @@ return name; | ||
try { | ||
; | ||
({ warnings } = await require_esbuild().transform(contents, { format: "esm", logLevel: "silent" })); | ||
} catch (err) { | ||
; | ||
({ warnings } = err); | ||
@@ -328,7 +336,12 @@ } | ||
const path = lexer.readString(lineText, leftBrace); | ||
if (path === null) | ||
if (path === null || is_ignored(path)) | ||
continue; | ||
const rightBrace = lineText.indexOf(")", leftBrace + 2 + path.length) + 1; | ||
let name = makeName(path); | ||
let import_statement = `import ${name} from ${JSON.stringify(path)};`; | ||
let import_statement; | ||
if (use_default_export(path)) { | ||
import_statement = `import ${name} from ${JSON.stringify(path)};`; | ||
} else { | ||
import_statement = `import * as ${name} from ${JSON.stringify(path)};`; | ||
} | ||
let offset2 = getOffset(line - 1); | ||
@@ -335,0 +348,0 @@ edits.push([offset2 + column, offset2 + rightBrace, name]); |
245
index.ts
@@ -1,5 +0,5 @@ | ||
import type { Message, Plugin } from "esbuild"; | ||
import { promises } from "fs"; | ||
import { Lexer } from "./lexer"; | ||
import { cachedReduce, makeLegalIdentifier, orderedUniq } from "./utils"; | ||
import type { Message, Plugin } from 'esbuild' | ||
import { promises } from 'fs' | ||
import { Lexer } from './lexer' | ||
import { cachedReduce, makeLegalIdentifier, orderedUniq } from './utils' | ||
@@ -12,3 +12,3 @@ export interface CommonJSOptions { | ||
*/ | ||
filter?: RegExp; | ||
filter?: RegExp | ||
@@ -35,3 +35,3 @@ /** | ||
*/ | ||
transform?: boolean | ((path: string) => TransformConfig | null | void); | ||
transform?: boolean | ((path: string) => boolean | TransformConfig | null | void) | ||
@@ -41,3 +41,34 @@ /** | ||
*/ | ||
transformConfig?: Pick<TransformConfig, "behavior" | "sideEffects">; | ||
transformConfig?: Pick<TransformConfig, 'behavior' | 'sideEffects'> | ||
/** | ||
* Controls which style of import should be used. By default, it transforms: | ||
* | ||
* ```js | ||
* // input | ||
* const foo = require("foo") | ||
* // output | ||
* import foo from "foo" | ||
* ``` | ||
* | ||
* The above case is often correct when 'foo' is also a commonjs module. | ||
* But if 'foo' has es module exports, it is better to use: | ||
* | ||
* ```js | ||
* // output | ||
* import * as foo from "foo" | ||
* ``` | ||
* | ||
* In which case you can set `requireReturnsDefault` to `false` to get the above output. | ||
* Or use the callback style to control the behavior for each module. | ||
* | ||
* @default true | ||
*/ | ||
requireReturnsDefault?: boolean | ((path: string) => boolean) | ||
/** | ||
* Don't replace require("ignored-modules"). Note that this will cause | ||
* esbuild generates the __require() wrapper which throw error at runtime. | ||
*/ | ||
ignore?: string[] | ((path: string) => boolean) | ||
} | ||
@@ -56,3 +87,3 @@ | ||
*/ | ||
behavior?: "babel" | "node"; | ||
behavior?: 'babel' | 'node' | ||
@@ -64,3 +95,3 @@ /** | ||
*/ | ||
exports?: string[]; | ||
exports?: string[] | ||
@@ -73,3 +104,3 @@ /** | ||
* var mod; | ||
* var exports = /+ @__PURE__ +/ ((exports, module) => { | ||
* var exports = /\*#__PURE__*\/ ((exports, module) => { | ||
* // ... original content ... | ||
@@ -79,10 +110,8 @@ * return module.exports; | ||
* export default exports; | ||
* var a_b = /+ @__PURE__ +/ (() => exports['a-b'])(); | ||
* var something = /+ @__PURE__ +/ (() => exports.something)(); | ||
* var a_b = /\*#__PURE__*\/ (() => exports['a-b'])(); | ||
* var something = /\*#__PURE__*\/ (() => exports.something)(); | ||
* export { a_b as "a-b", something }; | ||
* ``` | ||
* | ||
* Note: the `/+ @__PURE__ +/` above is actually `'/' + '* @__PURE__ *' + '/'`. | ||
*/ | ||
sideEffects?: boolean; | ||
sideEffects?: boolean | ||
} | ||
@@ -94,78 +123,89 @@ | ||
transformConfig, | ||
requireReturnsDefault = true, | ||
ignore, | ||
}: CommonJSOptions = {}): Plugin { | ||
let init_cjs_module_lexer: Promise<typeof import("cjs-module-lexer")> | undefined; | ||
if (transform) { | ||
init_cjs_module_lexer = import("cjs-module-lexer"); | ||
} | ||
const init_cjs_module_lexer = transform ? import('cjs-module-lexer') : undefined | ||
const use_default_export = | ||
typeof requireReturnsDefault === 'function' | ||
? requireReturnsDefault | ||
: (_path: string) => requireReturnsDefault | ||
const is_ignored = | ||
typeof ignore === 'function' | ||
? ignore | ||
: Array.isArray(ignore) | ||
? (path: string) => ignore.includes(path) | ||
: () => false | ||
return { | ||
name: "commonjs", | ||
name: 'commonjs', | ||
setup({ onLoad, esbuild }) { | ||
let esbuild_shim: typeof import("esbuild") | undefined; | ||
const require_esbuild = () => esbuild || (esbuild_shim ||= require("esbuild")); | ||
const read = promises.readFile; | ||
const lexer = new Lexer(); | ||
let esbuild_shim: typeof import('esbuild') | undefined | ||
const require_esbuild = () => esbuild || (esbuild_shim ||= require('esbuild')) | ||
const read = promises.readFile | ||
const lexer = new Lexer() | ||
onLoad({ filter }, async args => { | ||
let parseCJS: typeof import("cjs-module-lexer").parse | undefined; | ||
let parseCJS: typeof import('cjs-module-lexer').parse | undefined | ||
if (init_cjs_module_lexer) { | ||
const { init, parse } = await init_cjs_module_lexer; | ||
await init(); | ||
parseCJS = parse; | ||
const { init, parse } = await init_cjs_module_lexer | ||
await init() | ||
parseCJS = parse | ||
} | ||
let contents: string; | ||
let contents: string | ||
try { | ||
contents = await read(args.path, "utf8"); | ||
contents = await read(args.path, 'utf8') | ||
} catch { | ||
return null; | ||
return null | ||
} | ||
const willTransform = transform === true || (typeof transform === "function" && transform(args.path)); | ||
const willTransform = transform === true || (typeof transform === 'function' && transform(args.path)) | ||
let cjsExports: ReturnType<NonNullable<typeof parseCJS>> | undefined; | ||
let cjsExports: ReturnType<NonNullable<typeof parseCJS>> | undefined | ||
if (parseCJS && willTransform) { | ||
// move sourcemap to the end of the transformed file | ||
let sourcemapIndex = contents.lastIndexOf("//# sourceMappingURL="); | ||
let sourcemap: string | undefined; | ||
let sourcemapIndex = contents.lastIndexOf('//# sourceMappingURL=') | ||
let sourcemap: string | undefined | ||
if (sourcemapIndex !== -1) { | ||
sourcemap = contents.slice(sourcemapIndex); | ||
let sourcemapEnd = sourcemap.indexOf("\n"); | ||
sourcemap = contents.slice(sourcemapIndex) | ||
let sourcemapEnd = sourcemap.indexOf('\n') | ||
if (sourcemapEnd !== -1 && sourcemap.slice(sourcemapEnd + 1).trimStart().length > 0) { | ||
// if there's code after sourcemap, it is invalid, don't do this. | ||
sourcemap = undefined; | ||
sourcemap = undefined | ||
} else { | ||
contents = contents.slice(0, sourcemapIndex); | ||
contents = contents.slice(0, sourcemapIndex) | ||
} | ||
} | ||
// transform commonjs to es modules, easy mode | ||
cjsExports = parseCJS(contents); | ||
cjsExports = parseCJS(contents) | ||
let { behavior, exports, sideEffects } = | ||
typeof willTransform === "object" ? willTransform : ({} as TransformConfig); | ||
behavior ??= transformConfig?.behavior ?? "node"; | ||
exports = orderedUniq(cjsExports.exports.concat(exports ?? [])); | ||
sideEffects ??= transformConfig?.sideEffects ?? true; | ||
typeof willTransform === 'object' ? willTransform : ({} as TransformConfig) | ||
behavior ??= transformConfig?.behavior ?? 'node' | ||
exports = orderedUniq(cjsExports.exports.concat(exports ?? [])) | ||
sideEffects ??= transformConfig?.sideEffects ?? true | ||
let exportDefault = | ||
behavior === "node" | ||
? "export default exports;" | ||
: "export default exports.__esModule ? exports.default : exports;"; | ||
let exportsMap = exports.map(e => [e, makeLegalIdentifier(e)]); | ||
if (exportsMap.some(([e]) => e === "default")) { | ||
if (behavior === "node") { | ||
exportsMap = exportsMap.filter(([e]) => e !== "default"); | ||
behavior === 'node' | ||
? 'export default exports;' | ||
: 'export default exports.__esModule ? exports.default : exports;' | ||
let exportsMap = exports.map(e => [e, makeLegalIdentifier(e)]) | ||
if (exportsMap.some(([e]) => e === 'default')) { | ||
if (behavior === 'node') { | ||
exportsMap = exportsMap.filter(([e]) => e !== 'default') | ||
} else { | ||
exportDefault = ""; | ||
exportDefault = '' | ||
} | ||
} | ||
let reexports = cjsExports.reexports.map(e => `export * from ${JSON.stringify(e)};`).join(""); | ||
let transformed: string[]; | ||
let reexports = cjsExports.reexports.map(e => `export * from ${JSON.stringify(e)};`).join('') | ||
let transformed: string[] | ||
if (sideEffects === false) { | ||
transformed = [ | ||
// make sure we don't manipulate the first line so that sourcemap is fine | ||
reexports + "var mod, exports = /* @__PURE__ */ ((exports, module) => {" + contents, | ||
"return module.exports})((mod = { exports: {} }).exports, mod); " + exportDefault, | ||
]; | ||
reexports + 'var mod, exports = /* @__PURE__ */ ((exports, module) => {' + contents, | ||
'return module.exports})((mod = { exports: {} }).exports, mod); ' + exportDefault, | ||
] | ||
if (exportsMap.length > 0) { | ||
for (const [e, name] of exportsMap) { | ||
transformed.push(`var ${name} = /* @__PURE__ */ (() => exports[${JSON.stringify(e)}])();`); | ||
transformed.push(`var ${name} = /* @__PURE__ */ (() => exports[${JSON.stringify(e)}])();`) | ||
} | ||
@@ -175,10 +215,10 @@ transformed.push( | ||
.map(([e, name]) => (e === name ? e : `${name} as ${JSON.stringify(e)}`)) | ||
.join(", ")} };` | ||
); | ||
.join(', ')} };` | ||
) | ||
} | ||
} else { | ||
transformed = [ | ||
reexports + "var exports = {}, module = { exports }; {" + contents, | ||
"}; exports = module.exports; " + exportDefault, | ||
]; | ||
reexports + 'var exports = {}, module = { exports }; {' + contents, | ||
'}; exports = module.exports; ' + exportDefault, | ||
] | ||
if (exportsMap.length > 0) { | ||
@@ -188,76 +228,81 @@ transformed.push( | ||
.map(([e, name]) => (e === name ? e : `${JSON.stringify(e)}: ${name}`)) | ||
.join(", ")} } = exports;`, | ||
.join(', ')} } = exports;`, | ||
`export { ${exportsMap | ||
.map(([e, name]) => (e === name ? e : `${name} as ${JSON.stringify(e)}`)) | ||
.join(", ")} };` | ||
); | ||
.join(', ')} };` | ||
) | ||
} | ||
} | ||
contents = transformed.join("\n") + (sourcemap ? "\n" + sourcemap : ""); | ||
contents = transformed.join('\n') + (sourcemap ? '\n' + sourcemap : '') | ||
} | ||
function makeName(path: string) { | ||
let name = `__import_${makeLegalIdentifier(path)}`; | ||
let name = `__import_${makeLegalIdentifier(path)}` | ||
if (contents.includes(name)) { | ||
let suffix = 2; | ||
while (contents.includes(`${name}_${suffix}`)) suffix++; | ||
name = `${name}_${suffix}`; | ||
let suffix = 2 | ||
while (contents.includes(`${name}${suffix}`)) suffix++ | ||
name = `${name}${suffix}` | ||
} | ||
return name; | ||
return name | ||
} | ||
let warnings: Message[]; | ||
let warnings: Message[] | ||
try { | ||
({ warnings } = await require_esbuild().transform(contents, { format: "esm", logLevel: "silent" })); | ||
;({ warnings } = await require_esbuild().transform(contents, { format: 'esm', logLevel: 'silent' })) | ||
} catch (err) { | ||
({ warnings } = err as any); | ||
;({ warnings } = err as any) | ||
} | ||
let lines = contents.split("\n"); | ||
let getOffset = cachedReduce(lines, (a, b) => a + 1 + b.length, 0); | ||
let lines = contents.split('\n') | ||
let getOffset = cachedReduce(lines, (a, b) => a + 1 + b.length, 0) | ||
if (warnings && (warnings = warnings.filter(e => e.text.includes('"require" to "esm"'))).length) { | ||
let edits: [start: number, end: number, replace: string][] = []; | ||
let imports: string[] = []; | ||
let edits: [start: number, end: number, replace: string][] = [] | ||
let imports: string[] = [] | ||
for (const { location } of warnings) { | ||
if (location === null) continue; | ||
if (location === null) continue | ||
const { line, lineText, column, length } = location; | ||
const { line, lineText, column, length } = location | ||
const leftBrace = column + length + 1; | ||
const path = lexer.readString(lineText, leftBrace); | ||
if (path === null) continue; | ||
const rightBrace = lineText.indexOf(")", leftBrace + 2 + path.length) + 1; | ||
const leftBrace = column + length + 1 | ||
const path = lexer.readString(lineText, leftBrace) | ||
if (path === null || is_ignored(path)) continue | ||
const rightBrace = lineText.indexOf(')', leftBrace + 2 + path.length) + 1 | ||
let name = makeName(path); | ||
let import_statement = `import ${name} from ${JSON.stringify(path)};`; | ||
let name = makeName(path) | ||
let import_statement: string | ||
if (use_default_export(path)) { | ||
import_statement = `import ${name} from ${JSON.stringify(path)};` | ||
} else { | ||
import_statement = `import * as ${name} from ${JSON.stringify(path)};` | ||
} | ||
let offset = getOffset(line - 1); | ||
edits.push([offset + column, offset + rightBrace, name]); | ||
imports.push(import_statement); | ||
let offset = getOffset(line - 1) | ||
edits.push([offset + column, offset + rightBrace, name]) | ||
imports.push(import_statement) | ||
} | ||
if (imports.length === 0) return null; | ||
if (imports.length === 0) return null | ||
imports = orderedUniq(imports); | ||
imports = orderedUniq(imports) | ||
let offset = 0; | ||
let offset = 0 | ||
for (const [start, end, name] of edits) { | ||
contents = contents.slice(0, start + offset) + name + contents.slice(end + offset); | ||
offset += name.length - (end - start); | ||
contents = contents.slice(0, start + offset) + name + contents.slice(end + offset) | ||
offset += name.length - (end - start) | ||
} | ||
// if we have transformed this module (i.e. having `cjsExports`), don't make the file commonjs | ||
contents = [...imports, cjsExports ? "exports;" : "", contents].join(""); | ||
contents = [...imports, cjsExports ? 'exports;' : '', contents].join('') | ||
return { contents }; | ||
return { contents } | ||
} | ||
}); | ||
}) | ||
}, | ||
}; | ||
} | ||
} | ||
export default commonjs; | ||
export default commonjs |
{ | ||
"name": "@hyrious/esbuild-plugin-commonjs", | ||
"version": "0.2.2", | ||
"version": "0.2.3", | ||
"description": "Bundle commonjs externals in es module in esbuild.", | ||
"author": "hyrious <hyrious@outlook.com>", | ||
"license": "MIT", | ||
"keywords": [ | ||
"esbuild", | ||
"plugin", | ||
"commonjs", | ||
"modules", | ||
"require" | ||
], | ||
"main": "index.js", | ||
"types": "index.d.ts", | ||
"files": [ | ||
"index.d.ts", | ||
"index.js", | ||
@@ -12,14 +22,7 @@ "index.js.map", | ||
"lexer.ts", | ||
"utils.ts", | ||
"index.d.ts" | ||
"utils.ts" | ||
], | ||
"keywords": [ | ||
"esbuild", | ||
"plugin", | ||
"commonjs", | ||
"modules", | ||
"require" | ||
], | ||
"author": "hyrious <hyrious@outlook.com>", | ||
"license": "MIT", | ||
"engines": { | ||
"node": ">=14" | ||
}, | ||
"peerDependencies": { | ||
@@ -35,19 +38,14 @@ "cjs-module-lexer": "*", | ||
"devDependencies": { | ||
"@hyrious/esbuild-dev": "^0.8.6", | ||
"@types/node": "^14", | ||
"cjs-module-lexer": "^1.2.2", | ||
"esbuild": "^0.15.14", | ||
"rollup": "^3.3.0", | ||
"rollup-plugin-dts": "^5.0.0", | ||
"typescript": "^4.9.3" | ||
"@hyrious/dts": "^0.2.0", | ||
"@hyrious/esbuild-dev": "^0.10.5", | ||
"@types/node": "^20.10.6", | ||
"cjs-module-lexer": "^1.2.3", | ||
"esbuild": "^0.19.11" | ||
}, | ||
"engines": { | ||
"node": ">=14" | ||
}, | ||
"scripts": { | ||
"build": "npm run build:js && npm run build:type", | ||
"build:js": "esbuild index.ts --bundle --external:esbuild --external:cjs-module-lexer --sourcemap --sources-content=false --outfile=index.js --platform=node --target=node14", | ||
"build:type": "esbuild-dev build-type.ts", | ||
"build:js": "esbuild index.ts --bundle --packages=external --sourcemap --sources-content=false --outfile=index.js --platform=node --target=node14", | ||
"build:type": "dts index.ts -o index.d.ts", | ||
"test": "esbuild-dev index.test.ts" | ||
} | ||
} |
@@ -12,17 +12,17 @@ # @hyrious/esbuild-plugin-commonjs | ||
// some commonjs library, like react-dom | ||
var React = require("react"); | ||
var React = require('react') | ||
// your esm code | ||
export { render } from "react-dom"; | ||
export { render } from 'react-dom' | ||
// after esbuild --bundle | ||
var React = __require("react"); // <- you dislike this | ||
("..."); | ||
export { render }; | ||
var React = __require('react') // <- you dislike this | ||
// ... | ||
export { render } | ||
// with this plugin | ||
import __import_react from "react"; // <- you want this | ||
var React = __import_react; | ||
("..."); | ||
export { render }; | ||
import __import_react from 'react' // <- you want this | ||
var React = __import_react | ||
// ... | ||
export { render } | ||
``` | ||
@@ -58,3 +58,3 @@ | ||
```js | ||
commonjs({ filter: /\.c?js$/, transform: false }); | ||
commonjs({ filter: /\.c?js$/, transform: false }) | ||
``` | ||
@@ -68,2 +68,30 @@ | ||
**requireReturnsDefault** (default: `true`) | ||
```ts | ||
requireReturnsDefault: boolean | ((path: string) => boolean) | ||
``` | ||
Controls which style of import statement to use replacing require calls in commonjs modules. | ||
```js | ||
// input | ||
const foo = require('foo') | ||
// output if requireReturnsDefault is true (default behavior) | ||
import foo from 'foo' | ||
// output if requireReturnsDefault is false | ||
import * as foo from 'foo' | ||
``` | ||
**ignore** | ||
Do not convert require calls to these modules. Note that this will cause esbuild | ||
to generate `__require()` wrappers and throw errors at runtime. | ||
```ts | ||
ignore: string[] | ((path: string) => boolean) | ||
``` | ||
**transform** (default: `false`) | ||
@@ -76,4 +104,2 @@ | ||
Type: | ||
```ts | ||
@@ -88,5 +114,5 @@ transform: boolean | ((path: string) => { | ||
```js | ||
exports.__esModule = true; | ||
exports.default = {}; | ||
exports.foo = 42; | ||
exports.__esModule = true | ||
exports.default = {} | ||
exports.foo = 42 | ||
``` | ||
@@ -128,2 +154,6 @@ | ||
### 0.2.3 | ||
Add options `requireReturnsDefault` and `ignore`. | ||
## License | ||
@@ -130,0 +160,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
46548
5
858
162