README
Indexit is a small cli app for managing javascript and/or typescript index files.
Install
npm install indexit
# or
yarn add indexit
Usage
Anywhere you want an index file to be managed you add the comment /* Autogenerated Index */
and from there to the end of the file, the program can do it's thing (exports will be exported alphabetically by their "name").
Then you can run indexit test
(this is the equivalent of running indexit test src/** src/
) to see a preview of the indexes it would write to and what would be written.
You can list as many globs as you want: indexit test glob1/** glob2/** glob3/**
.
Notes
- If you get no results note that you must use forward slashes
/
for file paths in the glob. Backslashes are only for escaping characters. - If you want the indexes at, for example,
glob1
(glob1/index.js
) to be allowed to be written to you must also addglob1/
to the globs, otherwise it won't work. This is why the default issrc/** src/
. - The
node_modules
directory is always ignored.
To actually write the changes run indexit update
with the options you tested.
IMPORTANT!: git add/commit your changes before running or double check the test output thoroughly.
Prerequisites
This program makes several assumptions:
- All non-index files export exactly one export.
- If you manually manage a folder's index file it is only ever exporting one type of export, that is, it should not have, for example, both named and default exports, or both typescript types and named exports, etc.
In either case, only the first found will be used.
You can find a good example of how things get exported in the tests/fixtures folder, the index.expected.ts
files are what would be written.
The Start/End Tags
The start and end tags delimit the editable parts of the index files, so you can still have comments before/after them.
The default start tag is /* Autogenerated Index [OPTIONS] */
, and there is no default end tag, the editable area extends until the end of the file.
The options bit in the start tag allows you to specify how that index file will be exported.
By default we take /* Autogenerated Index */
to mean /* Autogenerated Index [Named]*/
since if we took it to mean the options were empty, nothing would be generated.
To force pass no options you would do: /* Autogenerated Index [] */
.
To pass multiple options, just separate them with a coma: /* Autogenerated Index [Named, Ignore] */
Note: Options are case insensitive.
Modifying the Tags
The section of index files the app is allowed to modify can be managed with the flags --tag.start
and --tag.end
. The tag start must include the [OPTIONS]
part(e.g. /* Autogenerated Index [OPTIONS] */
) so we can extract any options from the index files.
Note: It is assumed you will want /* Autogenerated Index */
to match as well even though it's missing the extra space where we remove the [OPTIONS]
part, so spaces after that bit are allowed.
Options
Options: Named
, Named-Unwrapped
, Default
, Types
, Types-Namespaced
, Ignore
You cannot have both Named/Named-Unwrapped
and Default
exports, but all other combinations are supported.
Named, Named-Unwrapped, and Default
These two options determine how regular exports are generated and how that files is re-exported if it is.
Named
Files
For files, it produces exports like this:
export { functionName } from "fileName"
Folders
For folders it depends whether it contains named or default exports.
Folders - Named
If the folder contains named exports, by default we use export ns from
syntax:
export * as folderName from "./folderName"
...but this might not be supported by your compiler. In that case you can pass --no-wildcard-exports
to get exports like this:
import * as _folderName from "./folderName"
export const folderName = _folderName
Folders - Default
If the folder contains a default export it depends on whether the default export is named or not, in which case the name detected will be used instead of the folder's name.
export { default as folderName } from "./folderName"
// or if the default export is named:
// export default = function defaultName () {...}
export { default as defaultName } from "fileName"
The --no-default-named
flag can be passed to always set the name to the folder name.
Named-Unwrapped
export * from "./folderName"
Default
import { named } from "./named"
import { default as defaultName } from ".defaultName"
import * as folderNamed from ".folderNamed"
import folderDefault from ".folderDefault"
export default {
defaultName,
folderDefault,
folderNamed,
named,
// ...
}
Ignore
Occasionally you might not want a nested folder to get re-exported in it's parent:
For example, say we divided our helpers into different folders. You wouldn't want to import a function from helper
through helpers
, since it can't be tree shaken because helpers
re-exports the entirety of the type
folder (regardless of it's export type).
root/src
└──helpers
└── type
└── helper
If type
uses the Ignore
option, helpers won't re-export it, that way the helper folder can exist purely for organizational purposes without re-export anything.
Types
export * from ".typeFile"
Types-Namespaced
export * as typeFile from ".typeFile"
CLI Options
The program also provides a few other useful options to control the output (e.g. --sort
, --spaces
, --newlines
, etc). Run indexit test --help
for details.
How it Works
After an index file with a header is found, it looks at all sibling files:
If the sibling contains a string like export function/const/let
, it is assumed you want a named export, e.g. export {name} from ".fileName"
.
If the sibling contains the string export default
, we try to look for the name of the export (e.g. export default someVar
) to export it like export someVar from "./fileName"
, otherwise if there's an anonymous function (e.g.export default function() {}
) or an object we use the file name: export fileName from "./fileName"
If the sibling is a folder with an index file and it is assumed you want to re-export it using the folder's name as the name. If the index is autogenerated the options in the start tag are used to determine how it's exported, otherwise if it's manually generated we look at the first export type we find.
Note how almost everything has a "name" one way or another.
Other
I have taken care that async
, typescript generic function names, and any weird characters in function names are correctly identified, though there might still be more cases that don't work.
Currently the following regex is used to extract the portion with the name: https://regexr.com/4s9ol (you can see the names by using the list and inputting $2\n
).
Todos
- Error Handling if we can't write file.
- Warn when an index file matches a glob but no tag was detected.
- Option when using test to only print out the parts between the start/end tags.
- Tests for sort/order and formatting options.
- Tests for start/end tag handling.