Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recursive imports #11

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,38 @@ meaning you will be able to access the items using `Items.A` and `Items.B`.

---

The above works recursively with nested directories as well:

```
|- index.js
|- dir
|- a.js
|- c.js
|- nested
|- b.js
```

the following JS:

```javascript
import * as Items from './dir';
```

will be compiled to:

```javascript
const Items = {};
import _wcImport from "./dir/a";
Items.A = _wcImport;
import _wcImport1 from "./dir/c";
Items.C = _wcImport1;
Items.Nested = {};
import _wcImport3 from "./dir/nested/c";
Items.Nested.C = _wcImport3;
```

---

You can also selectively choose files using:

```javascript
Expand Down Expand Up @@ -116,7 +148,7 @@ import bSpec from './dir/b.spec';

---

Files are automatically camel-cased and in the `import` statements the extensions are clipped unless specified otherwise (see below)
Files and nested directory names are automatically camel-cased and in the `import` statements the extensions are clipped unless specified otherwise (see below)

## Information

Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"build": "babel -d ./lib ./src",
"prepublish": "babel -d ./lib ./src --minified",
"test": "node test"
"test": "node test",
"watch": "nodemon --exec npm run test"
},
"repository": {
"type": "git",
Expand All @@ -24,6 +25,10 @@
"babel-preset-es2015": "^6.18.0",
"chalk": "^1.1.3",
"clear": "0.0.1",
"diff": "^3.0.1"
"diff": "^3.0.1",
"nodemon": "^1.12.1"
},
"dependencies": {
"recursive-readdir-sync": "^1.0.6"
}
}
161 changes: 114 additions & 47 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import _path from 'path';
import _fs from 'fs';
import recursiveReaddirSync from 'recursive-readdir-sync';

export default function (babel) {
const { types: t } = babel;
Expand Down Expand Up @@ -54,6 +55,7 @@ export default function (babel) {
var files = [];
var dir = _path.join(_path.dirname(name), src); // path of the target dir.

const alreadyInitialized = {};
for (var i = node.specifiers.length - 1; i >= 0; i--) {
dec = node.specifiers[i];

Expand Down Expand Up @@ -101,14 +103,13 @@ export default function (babel) {

// Will throw if the path does not point to a dir
try {
let r = _fs.readdirSync(dir);
for (var i = 0; i < r.length; i++) {
// Check extension is of one of the aboves
const {name, ext} = _path.parse(r[i]);
if (exts.indexOf(ext.substring(1)) > -1 && filenameRegex.test(name)) {
files.push(r[i]);
}
}
files = recursiveReaddirSync(dir)
.map(file => file.replace(dir + '/', '')) // Handle shallow dependencies
.map(file => file.replace(dir, ''))
.filter(file => {
const {name, ext} = _path.parse(file);
return exts.indexOf(ext.substring(1)) > -1 && filenameRegex.test(name);
});
} catch(e) {
console.warn(`Wildcard for ${name} points at ${src} which is not a directory.`);
return;
Expand All @@ -122,53 +123,48 @@ export default function (babel) {
let id = path.scope.generateUidIdentifier("wcImport");

var file = files[i];

// Strip extension
var fancyName = file.replace(/(?!^)\.[^.\s]+$/, "");

// Handle dotfiles, remove prefix `.` in that case
if (fancyName[0] === ".") {
fancyName = fancyName.substring(1);
}

// If we're allowed to camel case, which is default, we run it
// through this regex which converts it to a PascalCase variable.
if (state.opts.noCamelCase !== true) {
fancyName = fancyName.match(/[A-Z][a-z]+(?![a-z])|[A-Z]+(?![a-z])|([a-zA-Z\d]+(?=-))|[a-zA-Z\d]+(?=_)|[a-z]+(?=[A-Z])|[A-Za-z0-9]+/g).map(s => s[0].toUpperCase() + s.substring(1)).join("");
}

// Now we're 100% settled on the fancyName, if the user
// has provided a filer, we will check it:
if (filterNames.length > 0) {
// Find a filter name
let res = null;
for (let j = 0; j < filterNames.length; j++) {
if (filterNames[j].original === fancyName) {
res = filterNames[j];
break;
var parts = file.split('/')
// Set the fancy name based on options
.map(part => getFancyName(part, state.opts))
// Now we're 100% settled on the fancyName, if the user
// has provided a filter, we will check it:
.map(part => {
if (filterNames.length > 0) {
// Find a filter name
let res = null;
for (let j = 0; j < filterNames.length; j++) {
if (filterNames[j].original === part) {
res = filterNames[j];
break;
}
}
if (res === null) return null;
return res.local;
}
}
if (res === null) continue;
fancyName = res.local;
}
return part;
})
.filter(part => part !== null);

// If after filtering we have no parts left then continue
if (parts.length === 0) continue;

// This will remove file extensions from the generated `import`.
// This is useful if your src/ files are for example .jsx or
// .es6 but your generated files are of a different extension.
// For situations like webpack you may want to disable this
var name;
if (state.opts.nostrip !== true) {
name = "./" + _path.join(src, _path.basename(file));
name = "./" + _path.join(src, _path.dirname(file), _path.basename(file));
} else {
name = "./" + _path.join(src, file);
}

// Special behavior if 'filterNames'
if (filterNames.length > 0) {
let importDeclaration = t.importDeclaration(
[t.importDefaultSpecifier(
t.identifier(fancyName)
)],
parts.map(part => t.importDefaultSpecifier(
t.identifier(part)
)),
t.stringLiteral(name)
);
path.insertAfter(importDeclaration);
Expand All @@ -183,16 +179,70 @@ export default function (babel) {
t.stringLiteral(name)
);

// Assign it
let thing = t.expressionStatement(
t.assignmentExpression("=", t.memberExpression(
// Initialize the top level directories as an empty objects
const nested = parts.slice(0, -1);
nested.reduce((prev, curr) => {
if (!prev) {
if (alreadyInitialized[curr]) return {
path: curr,
member: alreadyInitialized[curr],
};
const member = t.memberExpression(
t.identifier(wildcardName),
t.stringLiteral(curr),
true
);
const setup = t.expressionStatement(
t.assignmentExpression("=", member, t.objectExpression([]))
);
path.insertBefore(setup);
alreadyInitialized[curr] = member;
return {
path: curr,
member,
};
}
const newPath = `${prev.path}.${curr}`;
if (alreadyInitialized[newPath]) return {
path: newPath,
member: alreadyInitialized[newPath],
};
const member = t.memberExpression(
prev.member,
t.stringLiteral(curr),
true
);
const setup = t.expressionStatement(
t.assignmentExpression("=", member, t.objectExpression([]))
);
path.insertBefore(setup);
alreadyInitialized[newPath] = member;
return {
path: newPath,
member,
};
}, null);

// Chain the parts for access
const accessPoint = parts.reduce((prev, curr) => {
if (!prev) return t.memberExpression(
t.identifier(wildcardName),
t.stringLiteral(fancyName),
t.stringLiteral(curr),
true
), id
));
);
return t.memberExpression(
prev,
t.stringLiteral(curr),
true
);
}, null);

// Assign the file to the parts
let setter = t.expressionStatement(
t.assignmentExpression("=", accessPoint, id)
);

path.insertAfter(thing);
path.insertAfter(setter);
path.insertAfter(importDeclaration);
}

Expand All @@ -204,3 +254,20 @@ export default function (babel) {
}
};
}

function getFancyName(originalName, opts) {
// Strip extension
var fancyName = originalName.replace(/(?!^)\.[^.\s]+$/, "");

// Handle dotfiles, remove prefix `.` in that case
if (fancyName[0] === ".") {
fancyName = fancyName.substring(1);
}

// If we're allowed to camel case, which is default, we run it
// through this regex which converts it to a PascalCase variable.
if (opts.noCamelCase !== true) {
fancyName = fancyName.match(/[A-Z][a-z]+(?![a-z])|[A-Z]+(?![a-z])|([a-zA-Z\d]+(?=-))|[a-zA-Z\d]+(?=_)|[a-z]+(?=[A-Z])|[A-Za-z0-9]+/g).map(s => s[0].toUpperCase() + s.substring(1)).join("");
}
return fancyName;
}
2 changes: 2 additions & 0 deletions test/fixtures/deep/actual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import * as files from './data/';
console.log(files.Deep.Test());
3 changes: 3 additions & 0 deletions test/fixtures/deep/data/deep/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function() {
return "Hello, World!";
}
13 changes: 13 additions & 0 deletions test/fixtures/deep/expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict';

var _test = require('./data/deep/test.js');

var _test2 = _interopRequireDefault(_test);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var files = {};
files['Deep'] = {};
files['Deep']['Test'] = _test2.default;

console.log(files.Deep.Test());
1 change: 1 addition & 0 deletions test/fixtures/deep/text.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello, World!
1 change: 0 additions & 1 deletion test/fixtures/filterTest/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,3 @@ var _A2 = _interopRequireDefault(_A);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

console.log((0, _A2.default)());

7 changes: 7 additions & 0 deletions test/fixtures/recursiveMixedMultiple/actual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as files from './data/';
console.log(files.Test());
console.log(files.Another.Deep.Test());
console.log(files.Another.Second());
console.log(files.Another.Test());
console.log(files.More.Deep.Test());
console.log(files.More.Deep.Second());
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function() {
return "Hello, World!";
}
3 changes: 3 additions & 0 deletions test/fixtures/recursiveMixedMultiple/data/another/second.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function() {
return "Hello, World!";
}
3 changes: 3 additions & 0 deletions test/fixtures/recursiveMixedMultiple/data/another/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function() {
return "Hello, World!";
}
3 changes: 3 additions & 0 deletions test/fixtures/recursiveMixedMultiple/data/more/deep/second.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function() {
return "Hello, World!";
}
3 changes: 3 additions & 0 deletions test/fixtures/recursiveMixedMultiple/data/more/deep/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function() {
return "Hello, World!";
}
3 changes: 3 additions & 0 deletions test/fixtures/recursiveMixedMultiple/data/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function() {
return "Hello, World!";
}
46 changes: 46 additions & 0 deletions test/fixtures/recursiveMixedMultiple/expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';

var _test = require('./data/test.js');

var _test2 = _interopRequireDefault(_test);

var _test3 = require('./data/more/deep/test.js');

var _test4 = _interopRequireDefault(_test3);

var _second = require('./data/more/deep/second.js');

var _second2 = _interopRequireDefault(_second);

var _test5 = require('./data/another/test.js');

var _test6 = _interopRequireDefault(_test5);

var _second3 = require('./data/another/second.js');

var _second4 = _interopRequireDefault(_second3);

var _test7 = require('./data/another/deep/test.js');

var _test8 = _interopRequireDefault(_test7);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var files = {};
files['Another'] = {};
files['Another']['Deep'] = {};
files['More'] = {};
files['More']['Deep'] = {};
files['Test'] = _test2.default;
files['More']['Deep']['Test'] = _test4.default;
files['More']['Deep']['Second'] = _second2.default;
files['Another']['Test'] = _test6.default;
files['Another']['Second'] = _second4.default;
files['Another']['Deep']['Test'] = _test8.default;

console.log(files.Test());
console.log(files.Another.Deep.Test());
console.log(files.Another.Second());
console.log(files.Another.Test());
console.log(files.More.Deep.Test());
console.log(files.More.Deep.Second());
1 change: 1 addition & 0 deletions test/fixtures/recursiveMixedMultiple/text.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello, World!
3 changes: 3 additions & 0 deletions test/fixtures/recursiveMultiple/actual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as files from './data/';
console.log(files.More.Deep.Test());
console.log(files.More.Deep.Second());
Loading