Skip to content

Commit

Permalink
feat: Add support for CJS.
Browse files Browse the repository at this point in the history
  • Loading branch information
coreyfarrell committed Jan 25, 2019
1 parent bf71a3c commit 59a2ac1
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 31 deletions.
40 changes: 21 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ Add `remove-ungap` to your babelrc plugins if the ponyfills are not needed by yo
browser target. This could be due to only targeting modern browsers or because
your project already polyfills the browser.

This plugin currently only works on ES modules before bundling. It can be run by
`rollup-plugin-babel` before import statements are altered.
This plugin works with CJS and ES modules before bundling. It can be run by
`rollup-plugin-babel` before import or require statements are altered.

### `exclude` option

Expand All @@ -42,23 +42,25 @@ This config will cause any import of `@ungap/essential-map` to be preserved.

### Modules that are removed

* [@ungap/assign](https://github.com/ungap/assign)
* [@ungap/array-iterator](https://github.com/ungap/array-iterator)
* [@ungap/custom-event](https://github.com/ungap/custom-event)
* [@ungap/essential-map](https://github.com/ungap/essential-map)
* [@ungap/essential-set](https://github.com/ungap/essential-set)
* [@ungap/essential-symbol](https://github.com/ungap/essential-symbol)
* [@ungap/essential-weakset](https://github.com/ungap/essential-weakset)
* [@ungap/event](https://github.com/ungap/event)
* [@ungap/event-target](https://github.com/ungap/event-target)
* [@ungap/import-node](https://github.com/ungap/import-node)
* [@ungap/is-array](https://github.com/ungap/is-array)
* [@ungap/map](https://github.com/ungap/map)
* [@ungap/set](https://github.com/ungap/set)
* [@ungap/template-literal](https://github.com/ungap/template-literal)
* [@ungap/trim](https://github.com/ungap/trim)
* [@ungap/weakmap](https://github.com/ungap/weakmap)
* [@ungap/weakset](https://github.com/ungap/weakset)
Module|Target|Declares variable
-|-|-
[@ungap/assign](https://github.com/ungap/assign)|Object.assign|Yes
[@ungap/array-iterator](https://github.com/ungap/array-iterator)|Array.prototype[Symbol.iterator]|Yes
[@ungap/custom-event](https://github.com/ungap/custom-event)|CustomEvent
[@ungap/essential-map](https://github.com/ungap/essential-map)|Map
[@ungap/essential-set](https://github.com/ungap/essential-set)|Set
[@ungap/essential-symbol](https://github.com/ungap/essential-symbol)|Symbol
[@ungap/essential-weakset](https://github.com/ungap/essential-weakset)|WeakSet
[@ungap/event](https://github.com/ungap/event)|Event
[@ungap/event-target](https://github.com/ungap/event-target)|EventTarget
[@ungap/import-node](https://github.com/ungap/import-node)|document.importNode|Yes
[@ungap/is-array](https://github.com/ungap/is-array)|Array.isArray|Yes
[@ungap/map](https://github.com/ungap/map)|Map
[@ungap/set](https://github.com/ungap/set)|Set
[@ungap/template-literal](https://github.com/ungap/template-literal)|val => val|Yes
[@ungap/trim](https://github.com/ungap/trim)|String.prototype.trim|Yes
[@ungap/weakmap](https://github.com/ungap/weakmap)|WeakMap
[@ungap/weakset](https://github.com/ungap/weakset)|WeakSet

### @ungap/create-content is altered

Expand Down
56 changes: 50 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
'use strict';
const path = require('path');

const createContentSource = path.join('node_modules', '@ungap', 'create-content', 'esm', 'index.js');

const replacements = {
'@ungap/assign': 'Object.assign',
'@ungap/array-iterator': 'Array.prototype[Symbol.iterator]',
Expand All @@ -23,6 +21,21 @@ const replacements = {
'@ungap/weakset': 'WeakSet'
};

function getModuleName(filename) {
if (!filename) {
return;
}

const parts = filename.split(path.sep);
if (!parts.includes('node_modules')) {
return;
}

return parts
.join(path.posix.sep)
.replace(/.*node_modules\//, '');
}

module.exports = ({types: t, template}) => ({
visitor: {
Program: {
Expand Down Expand Up @@ -50,6 +63,38 @@ module.exports = ({types: t, template}) => ({
});
}
},
CallExpression(path) {
if (!path.parentPath.isVariableDeclarator() || !path.get('callee').isIdentifier({name: 'require'})) {
return;
}

const moduleName = path.get('arguments.0');
const idPath = path.parentPath.get('id');
if (!moduleName.isStringLiteral() || !idPath.isIdentifier()) {
return;
}

const globalName = this.ungapReplacements[moduleName.node.value];
if (!globalName) {
return;
}

const localName = idPath.node.name;
const variableDeclarationPath = path.parentPath.parentPath;
if (localName === globalName) {
variableDeclarationPath.remove();
} else if (/^[a-zA-Z]*$/.test(globalName)) {
this.bindings.push({
binding: variableDeclarationPath.scope.getBinding(localName),
globalName,
path: variableDeclarationPath
});
// Defer removal until Program.exit, the import needs to exist for the
// binding lookup to work.
} else {
path.replaceWith(template(globalName)().expression);
}
},
ImportDeclaration(path) {
const source = path.get('source');
const specifiers = path.get('specifiers');
Expand All @@ -71,10 +116,9 @@ module.exports = ({types: t, template}) => ({
globalName,
path
});
// Defer removal until Program.exit, it needs to exist for the
// Defer removal until Program.exit, the import needs to exist for the
// binding lookup to work.
} else {
// BUGBUG: figure out how to directly replace usage of localName
path.replaceWith(t.variableDeclaration('var', [
t.variableDeclarator(t.identifier(localName), template(globalName)().expression)
]));
Expand All @@ -91,13 +135,13 @@ module.exports = ({types: t, template}) => ({
});
},
VariableDeclarator(path) {
const {sourceFileName} = path.hub.file.opts.parserOpts;
const sourceFileName = getModuleName(path.hub.file.opts.parserOpts.sourceFileName);
if (!sourceFileName) {
return;
}

const {name} = path.node.id;
if (this.specialHandlers['@ungap/create-content'] && sourceFileName.endsWith(createContentSource) && name === 'HAS_CONTENT') {
if (this.specialHandlers['@ungap/create-content'] && sourceFileName.startsWith('@ungap/create-content') && name === 'HAS_CONTENT') {
path.get('init').replaceWith(t.booleanLiteral(true));
}
}
Expand Down
75 changes: 69 additions & 6 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@ function babelTest(t, {source, result, opts, filename}) {
t.is(code, actual);
}

function genSource(module, importDest) {
function genSource(module, importDest, require = false) {
if (require) {
return `
var ${importDest} = require("${module}");
console.log(typeof ${importDest});
${importDest}();
`;
}

return `
import ${importDest} from "${module}";
console.log(typeof ${importDest});
Expand All @@ -37,33 +45,60 @@ Object.entries(replacements).forEach(([module, statement]) => {
const isBasic = /^[a-zA-Z]*$/.test(statement);

if (isBasic) {
test(`removes default ${module}`, babelTest, {
test(`removes default import ${module}`, babelTest, {
source: genSource(module, statement),
result: genResult(statement)
});

test(`replaces non-default ${module}`, babelTest, {
test(`replaces non-default import ${module}`, babelTest, {
source: genSource(module, 'AlternativeName'),
result: genResult(statement)
});

test(`removes default require ${module}`, babelTest, {
source: genSource(module, statement, true),
result: genResult(statement)
});

test(`removes non-default require ${module}`, babelTest, {
source: genSource(module, 'AlternativeName', true),
result: genResult(statement)
});
} else {
test(`replaces ${module}`, babelTest, {
test(`replaces import ${module}`, babelTest, {
source: genSource(module, 'testImportName'),
result: `
var testImportName = ${statement};
console.log(typeof testImportName);
testImportName();
`
});

test(`replaces require ${module}`, babelTest, {
source: genSource(module, 'testImportName', true),
result: `
var testImportName = ${statement};
console.log(typeof testImportName);
testImportName();
`
});
}

test(`exclude ${module}`, babelTest, {
test(`exclude import ${module}`, babelTest, {
source: genSource(module, 'testImportName'),
result: genSource(module, 'testImportName'),
opts: {
exclude: [module]
}
});

test(`exclude require ${module}`, babelTest, {
source: genSource(module, 'testImportName', true),
result: genSource(module, 'testImportName', true),
opts: {
exclude: [module]
}
});
});

test('ignores imports without local name', babelTest, {
Expand All @@ -81,7 +116,29 @@ test('ignores named imports', babelTest, {
result: 'import {WeakMap} from "@ungap/weakmap";'
});

test('rewrites HAS_CONTENT in @ungap/create-content', babelTest, {
test('ignores unassigned require', babelTest, {
source: 'require("@ungap/weakmap");',
result: 'require("@ungap/weakmap");'
});

test('ignores named require', babelTest, {
source: 'const {WeakMap} = require("@ungap/weakmap");',
result: 'const {WeakMap} = require("@ungap/weakmap");'
});

test('rewrites HAS_CONTENT in @ungap/create-content/index.js', babelTest, {
source: 'var HAS_CONTENT = \'content\' in document;',
result: 'var HAS_CONTENT = true;',
filename: path.join('node_modules', '@ungap', 'create-content', 'index.js')
});

test('rewrites HAS_CONTENT in @ungap/create-content/cjs/index.js', babelTest, {
source: 'var HAS_CONTENT = \'content\' in document;',
result: 'var HAS_CONTENT = true;',
filename: path.join('node_modules', '@ungap', 'create-content', 'cjs', 'index.js')
});

test('rewrites HAS_CONTENT in @ungap/create-content/esm/index.js', babelTest, {
source: 'var HAS_CONTENT = \'content\' in document;',
result: 'var HAS_CONTENT = true;',
filename: path.join('node_modules', '@ungap', 'create-content', 'esm', 'index.js')
Expand All @@ -102,6 +159,12 @@ test('ignores HAS_CONTENT outside of @ungap/create-content', babelTest, {
filename: path.join('node_modules', '@ungap', 'something-else', 'esm', 'index.js')
});

test('ignores HAS_CONTENT outside of node_modules', babelTest, {
source: 'var HAS_CONTENT = \'content\' in document;',
result: 'var HAS_CONTENT = \'content\' in document;',
filename: path.join('@ungap', 'create-content', 'esm', 'index.js')
});

test('tolerates HAS_CONTENT in source without filename', babelTest, {
source: 'var HAS_CONTENT = \'content\' in document;',
result: 'var HAS_CONTENT = \'content\' in document;'
Expand Down

0 comments on commit 59a2ac1

Please sign in to comment.