Comparing version 0.2.0 to 0.3.0
@@ -1,6 +0,27 @@ | ||
import { Context } from './context'; | ||
export interface Analyze { | ||
import { AcornNode } from './types'; | ||
export declare enum TopLevelType { | ||
ExpressionStatement = "ExpressionStatement", | ||
VariableDeclaration = "VariableDeclaration" | ||
} | ||
export declare function createAnalyze(context: Context): { | ||
analyze: () => void; | ||
}; | ||
export interface RequireStatement { | ||
node: AcornNode; | ||
ancestors: AcornNode[]; | ||
topLevelNode?: AcornNode & { | ||
type: TopLevelType; | ||
}; | ||
functionScope?: AcornNode; | ||
} | ||
export interface ExportsStatement { | ||
node: AcornNode; | ||
token: { | ||
left: string; | ||
right: string; | ||
}; | ||
} | ||
export interface Analyzed { | ||
code: string; | ||
ast: AcornNode; | ||
require: RequireStatement[]; | ||
exports: ExportsStatement[]; | ||
} | ||
export declare function analyzer(code: string): Analyzed; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createAnalyze = void 0; | ||
const acorn_walk_1 = require("acorn-walk"); | ||
function createAnalyze(context) { | ||
return { analyze }; | ||
function analyze() { | ||
acorn_walk_1.ancestor(context.ast, { | ||
CallExpression(node, ancestors) { | ||
if (node.callee.name === 'require') { | ||
analyzeNode(node, ancestors /* 需要浅 copy */.slice(0)); | ||
} | ||
}, | ||
}); | ||
} | ||
function analyzeNode(_node, ancestors) { | ||
let parentIndex; | ||
for (let len = ancestors.length, i = len - 1; i >= 0; i--) { | ||
// Reverse lookup of the first parent element | ||
if (!['CallExpression', 'MemberExpression'].includes(ancestors[i].type)) { | ||
parentIndex = i; | ||
break; | ||
} | ||
exports.analyzer = exports.TopLevelType = void 0; | ||
const acorn_1 = require("acorn"); | ||
// Top-level scope statement types | ||
var TopLevelType; | ||
(function (TopLevelType) { | ||
// require('foo') | ||
TopLevelType["ExpressionStatement"] = "ExpressionStatement"; | ||
// const foo = rquire('foo') | ||
TopLevelType["VariableDeclaration"] = "VariableDeclaration"; | ||
// TODO: others top-level ... | ||
})(TopLevelType = exports.TopLevelType || (exports.TopLevelType = {})); | ||
function analyzer(code) { | ||
const ast = (0, acorn_1.parse)(code, { ecmaVersion: 'latest' }); | ||
const analyzed = { | ||
code, | ||
ast, | ||
require: [], | ||
exports: [], | ||
}; | ||
simpleWalk(ast, { | ||
CallExpression(node, ancestors) { | ||
if (node.callee.name !== 'require') | ||
return; | ||
analyzed.require.push({ | ||
node, | ||
ancestors, | ||
topLevelNode: findTopLevelScope(ancestors), | ||
functionScope: findFunctionScope(ancestors), | ||
}); | ||
}, | ||
AssignmentExpression(node, ancestors) { | ||
if (node.left.type !== 'MemberExpression') | ||
return; | ||
if (!(node.left.object.type === 'Identifier' && ['module', 'exports'].includes(node.left.object.name))) | ||
return; | ||
analyzed.exports.push({ | ||
node, | ||
token: { | ||
left: node.left.object.name, | ||
right: node.left.property.name, | ||
}, | ||
}); | ||
}, | ||
}); | ||
return analyzed; | ||
} | ||
exports.analyzer = analyzer; | ||
// ---------------------------------------------------------------------- | ||
function simpleWalk(ast, visitors, ancestors = []) { | ||
var _a; | ||
if (!ast) | ||
return; | ||
if (Array.isArray(ast)) { | ||
for (const element of ast) { | ||
simpleWalk(element, visitors, ancestors); | ||
} | ||
const parentNode = ancestors[parentIndex]; | ||
const requireNode = ancestors[parentIndex + 1]; | ||
const requireRecord = { | ||
Statement: { | ||
VariableDeclarator: null, | ||
CallExpression: null, | ||
}, | ||
ObjectExpression: null, | ||
ArrayExpression: null, | ||
}; | ||
const CallExpression = analyzeRequireCallExpression(requireNode, ancestors); | ||
switch (parentNode.type) { | ||
// An VariableDeclaration statement | ||
case 'VariableDeclarator': | ||
requireRecord.Statement = { | ||
VariableDeclarator: analyzeVariableDeclarator(parentNode), | ||
CallExpression, | ||
}; | ||
break; | ||
// An ObjectExpression Property | ||
case 'Property': | ||
requireRecord.ObjectExpression = { | ||
Property: parentNode.key.name, | ||
CallExpression, | ||
}; | ||
break; | ||
// An ArrayExpression element | ||
case 'ArrayExpression': | ||
requireRecord.ArrayExpression = { | ||
Index: parentNode.elements.findIndex(elem => elem.start === requireNode.start), | ||
CallExpression, | ||
}; | ||
break; | ||
// Just require statement | ||
default: | ||
requireRecord.Statement = { | ||
VariableDeclarator: null, | ||
CallExpression, | ||
}; | ||
break; | ||
} | ||
context.requires.push(requireRecord); | ||
} | ||
function analyzeVariableDeclarator(node) { | ||
const _node = node.id; | ||
if (_node.type === 'Identifier') { | ||
/* const acorn */ | ||
return { | ||
type: _node.type, | ||
node: _node, | ||
name: _node.name, | ||
}; | ||
else { | ||
ancestors = ancestors.concat(ast); | ||
for (const key of Object.keys(ast)) { | ||
(typeof ast[key] === 'object' && | ||
simpleWalk(ast[key], visitors, ancestors)); | ||
} | ||
if (_node.type === 'ObjectPattern') { | ||
/* const { ancestor, simple } */ | ||
return { | ||
type: _node.type, | ||
node: _node, | ||
names: _node.properties.map(property => property.key.name), | ||
}; | ||
} | ||
} | ||
/** | ||
* @todo 只考虑常量 require 参数,不考虑拼接、模板字符串 21-08-07 | ||
*/ | ||
function analyzeRequireCallExpression(requireNode, ancestors) { | ||
if (requireNode.type === 'CallExpression') { | ||
return { | ||
type: requireNode.type, | ||
node: requireNode, | ||
ancestors, | ||
// Require statement has only one argument | ||
require: requireNode.arguments[0].value, | ||
}; | ||
} | ||
if (requireNode.type === 'MemberExpression') { | ||
return { | ||
type: requireNode.type, | ||
node: requireNode, | ||
ancestors, | ||
property: requireNode.property.name, | ||
// Require statement has only one argument | ||
require: requireNode.object.arguments[0].value, | ||
}; | ||
} | ||
(_a = visitors[ast.type]) === null || _a === void 0 ? void 0 : _a.call(visitors, ast, ancestors); | ||
} | ||
simpleWalk.async = function simpleWalkAsync() { }; | ||
// The function node that wraps it will be returned | ||
function findFunctionScope(ancestors) { | ||
return ancestors.find(an => [ | ||
'FunctionDeclaration', | ||
'ArrowFunctionExpression', | ||
].includes(an.type)); | ||
} | ||
// Will be return nearset ancestor node | ||
function findTopLevelScope(ancestors) { | ||
const ances = ancestors.map(an => an.type).join(); | ||
const arr = [...ancestors].reverse(); | ||
// TODO | ||
// CallExpression,CallExpression | require('foo')() | ||
// CallExpression,MemberExpression,CallExpression | require('foo').bar() | ||
if (/Program,ExpressionStatement,(CallExpression,|MemberExpression,){0,}CallExpression$/.test(ances)) { | ||
// require('foo') | ||
// require('foo').bar | ||
return arr.find(e => e.type === TopLevelType.ExpressionStatement); | ||
} | ||
if (/Program,VariableDeclaration,VariableDeclarator,(CallExpression,|MemberExpression,){0,}CallExpression$/.test(ances)) { | ||
// const foo = require('foo') | ||
// const bar = require('foo').bar | ||
// const { foo, bar: baz } = require('foo') | ||
return arr.find(e => e.type === TopLevelType.VariableDeclaration); | ||
} | ||
} | ||
exports.createAnalyze = createAnalyze; |
@@ -1,20 +0,6 @@ | ||
import { Context } from './context'; | ||
import { TransformExporttOptions, TransformImportOptions } from './transform'; | ||
export interface TransformeOptions { | ||
/** | ||
* @default false | ||
*/ | ||
sourcemap?: boolean; | ||
transformImport?: TransformImportOptions; | ||
transformExport?: TransformExporttOptions; | ||
import { SourceMap } from 'magic-string'; | ||
export interface Result { | ||
code: string; | ||
map?: SourceMap; | ||
} | ||
export interface Transformed { | ||
code: string | null; | ||
sourcemap: string | null; | ||
context: Context; | ||
} | ||
export declare function transform(code: string, options?: TransformeOptions): Transformed; | ||
declare const _default: { | ||
transform: typeof transform; | ||
}; | ||
export default _default; | ||
export default function cjs2esm(code: string): Result; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.transform = void 0; | ||
const context_1 = require("./context"); | ||
const tslib_1 = require("tslib"); | ||
const magic_string_1 = tslib_1.__importDefault(require("magic-string")); | ||
const utils_1 = require("./utils"); | ||
const analyze_1 = require("./analyze"); | ||
const transform_1 = require("./transform"); | ||
function transform(code, options = {}) { | ||
try { | ||
const context = context_1.createContext({ code }); | ||
analyze_1.createAnalyze(context).analyze(); | ||
transform_1.createTransform(context, { | ||
transformImport: options.transformImport, | ||
transformExport: options.transformExport, | ||
}).transform(); | ||
return { | ||
code: context.transformedCode, | ||
sourcemap: context.sourcemap, | ||
context, | ||
}; | ||
const generate_import_1 = require("./generate-import"); | ||
const generate_export_1 = require("./generate-export"); | ||
function cjs2esm(code) { | ||
var _a; | ||
if (!(0, utils_1.isCommonjs)(code)) { | ||
return { code }; | ||
} | ||
catch (error) { | ||
throw error; | ||
const analyzed = (0, analyze_1.analyzer)(code); | ||
const imports = (0, generate_import_1.generateImport)(analyzed); | ||
const exportRuntime = (0, generate_export_1.generateExport)(analyzed); | ||
const promotionImports = []; | ||
const ms = new magic_string_1.default(code); | ||
// Replace require statement | ||
for (const impt of imports) { | ||
const { node, topLevelNode, importee: imptee, declaration, importName, } = impt; | ||
const importee = imptee + ';'; | ||
let importStatement; | ||
if (topLevelNode) { | ||
if (topLevelNode.type === analyze_1.TopLevelType.ExpressionStatement) { | ||
importStatement = importee; | ||
} | ||
else if (topLevelNode.type === analyze_1.TopLevelType.VariableDeclaration) { | ||
importStatement = declaration ? `${importee} ${declaration};` : importee; | ||
} | ||
} | ||
else { | ||
// TODO: Merge duplicated require id | ||
// 🚧-① | ||
promotionImports.push(importee); | ||
importStatement = importName; | ||
} | ||
if (importStatement) { | ||
const start = topLevelNode ? topLevelNode.start : node.start; | ||
const end = topLevelNode ? topLevelNode.end : node.end; | ||
ms.overwrite(start, end, importStatement); | ||
} | ||
} | ||
if (promotionImports.length) { | ||
ms.prepend(['/* import-promotion-S */', ...promotionImports, '/* import-promotion-E */'].join(' ')); | ||
} | ||
// Replace exports statement | ||
if (exportRuntime) { | ||
if (exportRuntime.exportDefault) { | ||
const { start } = exportRuntime.exportDefault.node; | ||
ms.appendRight(start, `const ${exportRuntime.exportDefault.name} = `); | ||
} | ||
const polyfill = ['/* export-runtime-S */', exportRuntime.polyfill, '/* export-runtime-E */'].join(' '); | ||
const _exports = [ | ||
'// --------- export-statement ---------', | ||
(_a = exportRuntime.exportDefault) === null || _a === void 0 ? void 0 : _a.statement, | ||
exportRuntime.exportMembers, | ||
].filter(Boolean).join('\n'); | ||
ms.prepend(polyfill).append(_exports); | ||
} | ||
return { | ||
code: ms.toString(), | ||
map: ms.generateMap({ hires: true }), | ||
}; | ||
} | ||
exports.transform = transform; | ||
exports.default = { transform }; // Compatible tsc transpileModule | ||
exports.default = cjs2esm; |
@@ -1,125 +0,2 @@ | ||
import acorn from 'acorn'; | ||
export declare type KV<V = unknown> = Record<string, V>; | ||
export declare type AcornNode = acorn.Node & KV<any>; | ||
export interface BaseNode { | ||
type: string; | ||
node: AcornNode; | ||
} | ||
export interface VariableDeclaratorNode extends BaseNode { | ||
/** const acorn = require('acorn') */ | ||
name?: string; | ||
/** const { ancestor, simple } = require('acorn-walk') */ | ||
names?: string[]; | ||
} | ||
export interface CallExpressionNode extends BaseNode { | ||
ancestors: AcornNode[]; | ||
require: string; | ||
/** MemberExpression will have */ | ||
property?: string; | ||
} | ||
/** | ||
* VariableDeclarator === null 代表只是 require 引入 (import 'xxxx') | ||
*/ | ||
export interface RequireStatement { | ||
VariableDeclarator: VariableDeclaratorNode | null; | ||
CallExpression: CallExpressionNode | null; | ||
} | ||
/** | ||
* 目前只考虑四种 require 情况 21-08-07 | ||
* 1. 作为赋值表达式 | Statement | const aconr = require('acorn') | ||
* 2. 只有引入 | Statement | require('acorn') | ||
* 3. 作为对象属性值 | ObjectExpression | const obj = { acorn: require('acorn') } | ||
* 4. 作为数组成员 | ArrayExpression | const arr = [require('acorn')] | ||
*/ | ||
export interface RequireRecord { | ||
Statement: RequireStatement; | ||
ObjectExpression: ObjectExpressionNode | null; | ||
ArrayExpression: ArrayExpressionNode | null; | ||
} | ||
export interface ObjectExpressionNode { | ||
Property: string; | ||
CallExpression: CallExpressionNode; | ||
} | ||
export interface ArrayExpressionNode { | ||
Index: number; | ||
CallExpression: CallExpressionNode; | ||
} | ||
export interface ExportsRecord { | ||
} | ||
export declare type ImportName = string | Record</* (name as alias) */ string, string> | Record</* (* as name) */ '*', string>; | ||
export interface ImportRecord { | ||
/** | ||
* const acornDefault = require('acorn').default | ||
* const alias = require('acorn').parse | ||
* const acorn = require('acorn') | ||
*/ | ||
importName?: { | ||
name: ImportName; | ||
code: string; | ||
Statement: RequireStatement; | ||
}; | ||
/** | ||
* const parse = require('acorn').parse | ||
* const { ancestor, simple } = require('acorn-walk') | ||
*/ | ||
importNames?: { | ||
names: string[]; | ||
code: string; | ||
Statement: RequireStatement; | ||
}; | ||
/** require('acorn') */ | ||
importOnly?: { | ||
code: string; | ||
Statement: RequireStatement; | ||
}; | ||
/** const { ancestor, simple } = require('acorn-walk').other */ | ||
importDeconstruct?: { | ||
name: string; | ||
deconstruct: string[]; | ||
codes: [ | ||
string, | ||
string | ||
]; | ||
Statement: RequireStatement; | ||
}; | ||
/** const { ancestor, simple } = require('acorn-walk').default */ | ||
importDefaultDeconstruct?: { | ||
/** 自定义模块名 */ | ||
name: string; | ||
deconstruct: string[]; | ||
codes: [ | ||
string, | ||
string | ||
]; | ||
Statement: RequireStatement; | ||
}; | ||
/** For ArrayExpression, ObjectExpression statement. */ | ||
importExpression?: { | ||
/** 自定义模块名 */ | ||
name: Record<'*', string>; | ||
code: string; | ||
ArrayExpression: ArrayExpressionNode | null; | ||
ObjectExpression: ObjectExpressionNode | null; | ||
}; | ||
importDefaultExpression?: { | ||
/** 自定义模块名 */ | ||
name: string; | ||
code: string; | ||
ArrayExpression: ArrayExpressionNode | null; | ||
ObjectExpression: ObjectExpressionNode | null; | ||
}; | ||
} | ||
export interface CjsEsmRecord { | ||
node: AcornNode; | ||
require: string; | ||
cjs: { | ||
code: string; | ||
}; | ||
importName?: ImportRecord['importName']; | ||
importNames?: ImportRecord['importNames']; | ||
importOnly?: ImportRecord['importOnly']; | ||
importDeconstruct?: ImportRecord['importDeconstruct']; | ||
importDefaultDeconstruct?: ImportRecord['importDefaultDeconstruct']; | ||
importExpression?: ImportRecord['importExpression']; | ||
importDefaultExpression?: ImportRecord['importDefaultExpression']; | ||
} | ||
import type { Node } from 'acorn'; | ||
export declare type AcornNode<T = any> = Node & Record<string, T>; |
{ | ||
"name": "cjs-esm", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "Another CommonJs transform ESModule lib.", | ||
"main": "dist/index.js", | ||
"repository": "https://github.com/caoxiemeihao/cjs-esm", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/caoxiemeihao/cjs-esm.git" | ||
}, | ||
"author": "草鞋没号 <308487730@qq.com>", | ||
@@ -11,11 +14,12 @@ "license": "MIT", | ||
"build": "rm -rf dist && tsc", | ||
"prepublish": "npm run build" | ||
"prepublishOnly": "npm run build", | ||
"test": "node test/index.js" | ||
}, | ||
"dependencies": { | ||
"acorn": "^8.4.1", | ||
"acorn-walk": "^8.1.1" | ||
"acorn": "^8.7.1", | ||
"magic-string": "^0.26.1" | ||
}, | ||
"devDependencies": { | ||
"tslib": "^2.x", | ||
"typescript": "^4.x" | ||
"tslib": "^2.4.0", | ||
"typescript": "^4.6.4" | ||
}, | ||
@@ -25,5 +29,5 @@ "files": [ | ||
"package.json", | ||
"tsconfig.json", | ||
"package-lock.json", | ||
"README.md" | ||
] | ||
} |
# cjs-esm | ||
Another CommonJs transform ESModule lib. | ||
#### 21-08-07 | ||
- Release@0.1.0 | ||
* [x] transform `require` to `import` | ||
* [ ] transform `exports` to `export` | ||
[![NPM version](https://img.shields.io/npm/v/cjs-esm.svg?style=flat)](https://npmjs.org/package/cjs-esm) | ||
[![NPM Downloads](https://img.shields.io/npm/dm/cjs-esm.svg?style=flat)](https://npmjs.org/package/cjs-esm) | ||
English | [简体中文](https://github.com/caoxiemeihao/cjs-esm/blob/main/README.zh-CN.md) | ||
## Usage | ||
```js | ||
import cjs2esm from 'cjs-esm' | ||
// or | ||
// const cjs2esm = require('cjs-esm').default | ||
const { code, map } = cjs2esm(`const fs = require('fs')`) | ||
``` | ||
## TODO | ||
❌ Nested scope(function-scope) | ||
❌ Dynamic require id | ||
✅ require statement | ||
```js | ||
// Top-level scope | ||
const foo = require('foo').default | ||
↓ | ||
import foo from 'foo'; | ||
const foo = require('foo') | ||
↓ | ||
import * as foo from 'foo'; | ||
const foo = require('foo').bar | ||
↓ | ||
import * as __CJS_import__0__ from 'foo'; const { bar: foo } = __CJS_import__0__; | ||
// Non top-level scope | ||
const foo = [{ bar: require('foo').bar }] | ||
↓ | ||
import * as __CJS_import__0__ from 'foo'; const foo = [{ bar: __CJS_import__0__.bar }] | ||
``` | ||
✅ exports statement | ||
```js | ||
module.exports = fn() { }; | ||
↓ | ||
const __CJS__export_default__ = module.exports = fn() { }; | ||
export { __CJS__export_default__ as default } | ||
exports.foo = 'foo'; | ||
↓ | ||
const __CJS__export_foo__ = (module.exports == null ? {} : module.exports).foo; | ||
export { __CJS__export_foo__ as foo } | ||
``` |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
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
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
61
0
17973
14
405
2
+ Addedmagic-string@^0.26.1
+ Addedmagic-string@0.26.7(transitive)
+ Addedsourcemap-codec@1.4.8(transitive)
- Removedacorn-walk@^8.1.1
- Removedacorn-walk@8.3.4(transitive)
Updatedacorn@^8.7.1