Comparing version 0.2.2 to 0.3.0
450
index.js
const {walk} = require("estree-walker"); | ||
const MagicString = require("magic-string"); | ||
const {createTopLevelAnalyzer, createScopeAnalyzer} = require("./lib/util"); | ||
const { | ||
createTopLevelExportTransformer, | ||
createTopLevelImportTransformer | ||
} = require("./lib/top-level"); | ||
const {createDynamicImportTransformer} = require("./lib/dynamic"); | ||
const { | ||
createHoistExportTransformer, | ||
createHoistImportTransformer | ||
} = require("./lib/hoist"); | ||
function getExportInfo(node) { | ||
if (node.left.type === "MemberExpression") { | ||
if (node.left.object.name === "module" && node.left.property.name === "exports") { | ||
return { | ||
type: "default", | ||
left: node.left, | ||
value: node.right | ||
}; | ||
} | ||
if ( | ||
node.left.object.type === "MemberExpression" && | ||
node.left.object.object.name === "module" && | ||
node.left.object.property.name === "exports" | ||
) { | ||
return { | ||
type: "named", | ||
name: node.left.property.name, | ||
left: node.left, | ||
value: node.right | ||
}; | ||
} | ||
if (node.left.object.name === "exports") { | ||
return { | ||
type: "named", | ||
name: node.left.property.name, | ||
left: node.left, | ||
value: node.right | ||
}; | ||
} | ||
} | ||
} | ||
function getDeclareExport(node) { | ||
if (node.declarations.length !== 1) { | ||
return; | ||
} | ||
const dec = node.declarations[0]; | ||
if (dec.id.type !== "Identifier" || dec.init.type !== "AssignmentExpression") { | ||
return; | ||
} | ||
const exported = getExportInfo(dec.init); | ||
if (!exported) { | ||
return; | ||
} | ||
if (exported.name === dec.id.name) { | ||
return { | ||
kind: node.kind, | ||
exported | ||
}; | ||
} | ||
} | ||
function getDeclareImport(node) { | ||
if (node.declarations.length !== 1) { | ||
return; | ||
} | ||
const dec = node.declarations[0]; | ||
if (dec.init.type !== "CallExpression") { | ||
return; | ||
} | ||
const required = getRequireInfo(dec.init); | ||
if (!required) { | ||
return; | ||
} | ||
let object; | ||
if (dec.id.type === "ObjectPattern") { | ||
object = getObjectInfo(dec.id, true); | ||
if (!object) { | ||
return; | ||
} | ||
} else if (dec.id.type !== "Identifier") { | ||
return; | ||
} | ||
return { | ||
object, | ||
left: dec.id, | ||
right: dec.init, | ||
required | ||
}; | ||
} | ||
function getDynamicImport(node) { | ||
if ( | ||
node.callee.type !== "MemberExpression" || | ||
node.callee.object.name !== "Promise" || | ||
node.callee.property.name !== "resolve" | ||
) { | ||
return; | ||
} | ||
if ( | ||
node.arguments.length !== 1 || | ||
node.arguments[0].type !== "CallExpression" | ||
) { | ||
return; | ||
} | ||
const required = getRequireInfo(node.arguments[0]); | ||
if (required) { | ||
return { | ||
start: node.start, | ||
end: node.end, | ||
required | ||
}; | ||
} | ||
} | ||
function getRequireInfo(node) { | ||
if ( | ||
node.callee.name === "require" && | ||
node.arguments.length === 1 && | ||
node.arguments[0].type === "Literal" | ||
) { | ||
return node.arguments[0]; | ||
} | ||
} | ||
function getObjectInfo(node, checkValueType) { | ||
if (!node.properties.length) { | ||
return; | ||
} | ||
const properties = []; | ||
for (const prop of node.properties) { | ||
if (prop.key.type !== "Identifier") { | ||
return; | ||
} | ||
if (checkValueType && prop.value.type !== "Identifier") { | ||
return; | ||
} | ||
if (prop.method) { | ||
properties.push({ | ||
name: prop.key.name, | ||
method: true, | ||
generator: prop.value.generator, | ||
key: prop.key, | ||
value: prop.value | ||
}); | ||
} else { | ||
// note that if prop.shorthand == true then prop.key == prop.value | ||
properties.push({ | ||
name: prop.key.name, | ||
key: prop.key, | ||
value: prop.value | ||
}); | ||
} | ||
} | ||
return { | ||
start: node.start, | ||
end: node.end, | ||
properties | ||
}; | ||
} | ||
function makeCallable(func) { | ||
@@ -162,229 +21,88 @@ if (typeof func === "function") { | ||
function createExportTransformer({s, exportStyle, code}) { | ||
let isTouched = false; | ||
function transform({ | ||
parse, | ||
code, | ||
sourceMap = false, | ||
importStyle = "named", | ||
exportStyle = "named", | ||
hoist = false, | ||
dynamicImport = false | ||
} = {}) { | ||
return { | ||
transformExportAssign, | ||
transformExportDeclare, | ||
isTouched: () => isTouched | ||
}; | ||
importStyle = makeCallable(importStyle); | ||
exportStyle = makeCallable(exportStyle); | ||
function transformExportAssign(node) { | ||
const exported = getExportInfo(node); | ||
if (!exported) { | ||
return; | ||
} | ||
if (exported.type === "named") { | ||
if (exported.value.type === "Identifier") { | ||
// exports.foo = foo | ||
s.overwrite( | ||
node.start, | ||
exported.value.start, | ||
"export {" | ||
); | ||
s.appendLeft( | ||
exported.value.end, | ||
exported.value.name === exported.name ? | ||
"}" : ` as ${exported.name}}` | ||
); | ||
} else { | ||
// exports.foo = "not an identifier" | ||
s.overwrite( | ||
node.start, | ||
exported.left.end, | ||
`const _export_${exported.name}_` | ||
); | ||
s.appendLeft(node.end, `;\nexport {_export_${exported.name}_ as ${exported.name}}`); | ||
} | ||
} else { | ||
const rx = /.*?\/\/.*\bdefault\b/y; | ||
rx.lastIndex = node.start; | ||
if (exported.value.type !== "ObjectExpression" || exportStyle() === "default" || rx.test(code)) { | ||
// module.exports = ... | ||
s.overwrite( | ||
node.start, | ||
exported.value.start, | ||
"export default " | ||
); | ||
} else { | ||
// module.exports = {...} | ||
const objMap = getObjectInfo(exported.value); | ||
if (objMap) { | ||
const overwrite = (start, property, newLine, semi) => { | ||
if (property.value.type === "Identifier") { | ||
// foo: bar | ||
s.overwrite(start, property.value.start, `${newLine ? "\n" : ""}export {`); | ||
s.appendLeft( | ||
property.value.end, | ||
`${ | ||
property.value.name === property.name ? | ||
"" : ` as ${property.name}` | ||
}}${semi ? ";" : ""}` | ||
); | ||
} else { | ||
// foo: "not an identifier" | ||
s.overwrite( | ||
start, | ||
property.value.start, | ||
`${newLine ? "\n" : ""}const _export_${property.name}_ = ${ | ||
property.method ? | ||
`function${property.generator ? "*" : ""} ` : "" | ||
}` | ||
); | ||
s.appendLeft( | ||
property.value.end, | ||
`;\nexport {_export_${property.name}_ as ${property.name}}${semi ? ";" : ""}` | ||
); | ||
} | ||
}; | ||
// module.exports = { ... | ||
let start = node.start; | ||
for (let i = 0; i < objMap.properties.length; i++) { | ||
overwrite( | ||
start, | ||
objMap.properties[i], | ||
i > 0, | ||
i < objMap.properties.length - 1 | ||
); | ||
start = objMap.properties[i].value.end; | ||
} | ||
// , ... } | ||
s.remove(start, node.end); | ||
} | ||
} | ||
} | ||
isTouched = true; | ||
} | ||
function transformExportDeclare(node) { | ||
const declared = getDeclareExport(node); | ||
if (!declared) { | ||
return; | ||
} | ||
// const foo = exports.foo = ... | ||
s.overwrite( | ||
node.start, | ||
declared.exported.left.end, | ||
`export ${declared.kind} ${declared.exported.name}` | ||
); | ||
isTouched = true; | ||
} | ||
} | ||
function createImportTransformer({s, importStyle, code}) { | ||
let isTouched = false; | ||
const s = new MagicString(code); | ||
const ast = parse(code); | ||
const topLevel = createTopLevelAnalyzer(); | ||
const scope = hoist || dynamicImport ? createScopeAnalyzer(ast) : null; | ||
return { | ||
transformImportBare, | ||
transformImportDynamic, | ||
transformImportDeclare, | ||
isTouched: () => isTouched | ||
}; | ||
const topLevelImportTransformer = createTopLevelImportTransformer({code, s, importStyle}); | ||
const topLevelExportTransformer = createTopLevelExportTransformer({code, s, exportStyle}); | ||
function transformImportDeclare(node) { | ||
const declared = getDeclareImport(node); | ||
if (!declared) { | ||
return; | ||
} | ||
if (!declared.object) { | ||
// const foo = require("foo") | ||
const rx = /.*?\/\/.*\bdefault\b/y; | ||
rx.lastIndex = declared.required.end; | ||
if (rx.test(code) || importStyle(declared.required.value) === "default") { | ||
// import default | ||
s.overwrite( | ||
node.start, | ||
declared.left.start, | ||
"import " | ||
); | ||
} else { | ||
// import named | ||
s.overwrite( | ||
node.start, | ||
declared.left.start, | ||
"import * as " | ||
); | ||
const dynamicImportTransformer = dynamicImport ? createDynamicImportTransformer({s, scope}) : null; | ||
const hoistImportTransformer = hoist ? | ||
createHoistImportTransformer({s, topLevel, scope, code}) : null; | ||
const hoistExportTransformer = hoist ? | ||
createHoistExportTransformer({s, topLevel, scope}) : null; | ||
walk(ast, { | ||
enter(node, parent) { | ||
if (node.shouldSkip) { | ||
this.skip(); | ||
} | ||
} else { | ||
// const {foo, bar} | ||
s.overwrite( | ||
node.start, | ||
declared.object.start, | ||
"import " | ||
); | ||
// foo: bar | ||
for (const prop of declared.object.properties) { | ||
if (prop.key.end < prop.value.start) { | ||
s.overwrite( | ||
prop.key.end, | ||
prop.value.start, | ||
" as " | ||
); | ||
topLevel.enter(node, parent); | ||
if (scope) { | ||
scope.enter(node); | ||
} | ||
if (node.type === "VariableDeclaration" && topLevel.isTop()) { | ||
topLevelImportTransformer.transformImportDeclare(node); | ||
topLevelExportTransformer.transformExportDeclare(node); | ||
} else if (node.type === "AssignmentExpression" && topLevel.isTopChild()) { | ||
if (!hoistExportTransformer || !hoistExportTransformer.isModuleDeclared()) { | ||
topLevelExportTransformer.transformExportAssign(node); | ||
} | ||
} else if (node.type === "CallExpression") { | ||
if (dynamicImport) { | ||
dynamicImportTransformer.transform(node); | ||
} | ||
if (topLevel.isTopChild()) { | ||
topLevelImportTransformer.transformImportBare(node); | ||
} | ||
if (hoist) { | ||
hoistImportTransformer.transform(node); | ||
} | ||
} else if (node.type === "Identifier" && hoist) { | ||
hoistExportTransformer.transformExport(node, parent); | ||
hoistExportTransformer.transformModule(node, parent); | ||
} | ||
if (!dynamicImport && !hoist && !topLevel.isTop()) { | ||
this.skip(); | ||
if (scope) { | ||
scope.leave(node); | ||
} | ||
} | ||
}, | ||
leave(node) { | ||
if (scope) { | ||
scope.leave(node); | ||
} | ||
} | ||
s.overwrite( | ||
declared.left.end, | ||
declared.required.start, | ||
" from " | ||
); | ||
s.remove(declared.required.end, declared.right.end); | ||
isTouched = true; | ||
} | ||
function transformImportDynamic(node) { | ||
const imported = getDynamicImport(node); | ||
if (!imported) { | ||
return; | ||
} | ||
s.overwrite( | ||
imported.start, | ||
imported.required.start, | ||
"import(" | ||
); | ||
s.overwrite( | ||
imported.required.end, | ||
imported.end, | ||
")" | ||
); | ||
isTouched = true; | ||
} | ||
}); | ||
function transformImportBare(node) { | ||
const required = getRequireInfo(node); | ||
if (required) { | ||
s.overwrite(node.start, required.start, "import "); | ||
s.remove(required.end, node.end); | ||
isTouched = true; | ||
} | ||
if (hoist) { | ||
hoistExportTransformer.writeDeclare(); | ||
hoistExportTransformer.writeExport(); | ||
} | ||
} | ||
function transform({parse, code, sourceMap = false, importStyle = "named", exportStyle = "named"} = {}) { | ||
importStyle = makeCallable(importStyle); | ||
exportStyle = makeCallable(exportStyle); | ||
const s = new MagicString(code); | ||
const importTransformer = createImportTransformer({code, s, importStyle}); | ||
const exportTransformer = createExportTransformer({code, s, exportStyle}); | ||
const isTouched = | ||
topLevelImportTransformer.isTouched() || | ||
topLevelExportTransformer.isTouched() || | ||
(hoist && ( | ||
hoistImportTransformer.isTouched() || | ||
hoistExportTransformer.isTouched() | ||
)) || | ||
dynamicImport && dynamicImportTransformer.isTouched(); | ||
const ast = parse(code); | ||
walk(ast, {enter(node, parent) { | ||
if (node.type === "VariableDeclaration" && parent.type === "Program") { | ||
importTransformer.transformImportDeclare(node); | ||
exportTransformer.transformExportDeclare(node); | ||
} else if (node.type === "AssignmentExpression" && parent.topLevel) { | ||
exportTransformer.transformExportAssign(node); | ||
} else if (node.type === "ExpressionStatement" && parent.type === "Program") { | ||
node.topLevel = true; | ||
} else if (node.type === "CallExpression") { | ||
importTransformer.transformImportDynamic(node); | ||
if (parent.topLevel) { | ||
importTransformer.transformImportBare(node); | ||
} | ||
} | ||
}}); | ||
const isTouched = importTransformer.isTouched() || exportTransformer.isTouched(); | ||
return { | ||
@@ -391,0 +109,0 @@ code: isTouched ? s.toString() : code, |
{ | ||
"name": "cjs-es", | ||
"version": "0.2.2", | ||
"version": "0.3.0", | ||
"description": "Transform CommonJS module into ES module.", | ||
@@ -29,4 +29,6 @@ "keywords": [ | ||
"estree-walker": "^0.5.1", | ||
"magic-string": "^0.24.0" | ||
"is-reference": "^1.1.0", | ||
"magic-string": "^0.24.0", | ||
"rollup-pluginutils": "^2.0.1" | ||
} | ||
} |
@@ -11,13 +11,14 @@ cjs-es | ||
* Lightweight. | ||
* Prefer named import/export when possible. | ||
* Only support the syntax that is interchangeable between mjs and js. | ||
* Convert in-place. It only converts: | ||
* Support the syntax that is interchangeable between mjs and js. | ||
* Convert in-place. By default, it only converts: | ||
- top-level `require` declaration (`const foo = require("foo")`), | ||
- top-level `module.exports`, `exports` assignment (`module.exports = ...`/`const foo = exports.foo = ...`), | ||
- dynamic-require expression (`Promise.resolve(require("foo"))`). | ||
There are more samples under `test/cases` folder. | ||
* Hoist the `require`, `exports` statements that is not top-level. | ||
* Transform dynamic imports. (`Promise.resolve(require("foo"))`) | ||
There are more samples under `test/cases` folder. | ||
Usage | ||
@@ -117,2 +118,41 @@ ----- | ||
Hoist | ||
----- | ||
If the `require`/`module`/`exports` statement are not at the top level, they would be hoisted: | ||
```js | ||
if (foo) { | ||
require("foo").foo(); | ||
} | ||
``` | ||
Result: | ||
```js | ||
import * as _require_foo_ from "foo"; | ||
if (foo) { | ||
_require_foo_.foo(); | ||
} | ||
``` | ||
Dynamic import | ||
-------------- | ||
ES6 lazy load `import("...")` is async and return a promise. It is interchangeable with `Promise.resolve(require("..."))` in CommonJS: | ||
```js | ||
module.exports = () => { | ||
return Promise.resolve(require("foo")); | ||
}; | ||
``` | ||
Result: | ||
```js | ||
export default () => { | ||
return import("foo"); | ||
}; | ||
``` | ||
API reference | ||
@@ -139,3 +179,7 @@ ------------- | ||
* `exportStyle?`: `string` or `function -> string`. The result must be `"named"` or `"default"`. Default: `"named"` | ||
* `hoist?`: `boolean`. If true then turn on hoist transformer. Default: `false`. | ||
* `dynamicImport?`: `boolean`. If true then turn on dynamic import transformer. Default: `false`. | ||
If `hoist` and `dynamicImport` are both `false`, the transformer would only traverse top-level nodes of the AST. | ||
The result object has following members: | ||
@@ -150,2 +194,8 @@ | ||
* 0.3.0 (Apr 27, 2018) | ||
- Merge cjs-hoist. | ||
- Add: `hoist` option. | ||
- Add: `dynamicImport` option. | ||
* 0.2.2 (Apr 26, 2018) | ||
@@ -152,0 +202,0 @@ |
@@ -7,21 +7,41 @@ /* eslint-env mocha */ | ||
describe("cases", () => { | ||
for (const dir of fs.readdirSync(__dirname + "/cases")) { | ||
it(dir, () => { | ||
const readFile = filename => { | ||
try { | ||
return fs.readFileSync(`${__dirname}/cases/${dir}/${filename}`, "utf8").replace(/\r/g, ""); | ||
} catch (err) { | ||
// pass | ||
} | ||
}; | ||
const options = JSON.parse(readFile("options.json") || "{}"); | ||
const input = readFile("input.js"); | ||
const output = readFile("output.js"); | ||
const result = transform(Object.assign({code: input, parse}, options)); | ||
assert.equal(result.code, output); | ||
assert.equal(result.isTouched, input !== output); | ||
}); | ||
const cases = [ | ||
{ | ||
name: "top-level only", | ||
test: dir => !dir.startsWith("hoist") && !dir.startsWith("dynamic"), | ||
options: {} | ||
}, { | ||
name: "top-level + hoist + dynamic", | ||
test: () => true, | ||
options: {dynamicImport: true, hoist: true} | ||
} | ||
}); | ||
]; | ||
for (const c of cases) { | ||
describe(c.name, () => { | ||
for (const dir of fs.readdirSync(__dirname + "/cases")) { | ||
if (!c.test(dir)) { | ||
continue; | ||
} | ||
it(dir, () => { | ||
const readFile = filename => { | ||
try { | ||
return fs.readFileSync(`${__dirname}/cases/${dir}/${filename}`, "utf8").replace(/\r/g, ""); | ||
} catch (err) { | ||
// pass | ||
} | ||
}; | ||
const options = JSON.parse(readFile("options.json") || "{}"); | ||
const input = readFile("input.js"); | ||
const output = readFile("output.js"); | ||
const result = transform(Object.assign({ | ||
code: input, | ||
parse | ||
}, c.options, options)); | ||
assert.equal(result.code, output); | ||
assert.equal(result.isTouched, input !== output); | ||
}); | ||
} | ||
}); | ||
} |
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
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
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
31247
71
919
215
4
1
+ Addedis-reference@^1.1.0
+ Addedrollup-pluginutils@^2.0.1
+ Added@types/estree@1.0.6(transitive)
+ Addedestree-walker@0.6.1(transitive)
+ Addedis-reference@1.2.1(transitive)
+ Addedrollup-pluginutils@2.8.2(transitive)