Skip to content
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
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,70 @@ turn javascript files into folders
### Install
Download the latest version from the Visual Studio Marketplace [here](https://marketplace.visualstudio.com/items?itemName=ee92.folderize)

### Customize
Not everybody uses the same folder structure, that is why Folderize also gives you the possibility to customize the options you see when selecting which optional files to create.

In order to customize this extension, go to Visual Studio Code settings and search for `Folderize`, you will see an option called `optionalFilesToGenerate`. As a value it expects an array of a mix of presets(strings) or custom options(structured objects). These are explained a bit more in depth in the following subsections.

Before that, here is a simple demo of how this setting could look like:

```json
...
"folderize.filesToGenerate": [
"styles",
"spec",
{
"id": "storybook",
"label": "Add stories file",
"description": "{fileName}.stories.{ext}",
"createFile": "{fileName}.stories.{ext}",
"importComponent": true
}
]
...
```

#### Presets
As mentioned before, not everybody uses the same folder structure. That being said, there are some files that are quite common to most structures so to make it easier for you, we created a set of presets that can be of help.

##### Current list of presets:
* test: creates a `{Component}.test.{ext}` file with the Component already imported
* spec: creates a `{Component}.spec.{ext}` file with the Component already imported
* css_module: creates a `{Component}.module.css` file and imports it in the Component
* styles: creates a `{Component}.styles.{ext}` file and imports it in the Component

*Note:* both css_module and styles presets are imported in the Component with the name `styles`. If for whatever reason you need both files to be created for a component, you can override one of them with a custom option and change the import name.

#### Custom Option
If you need a file to be created that is not part of our presets, there is still a way for you to add it.

Custom options should follow the following structure:
```
{
id: string,
label: string,
description: string,
// OPTIONALS:
createFile: string,
importInComponentName: string,
importComponent: boolean
}
````

##### Property breakdown

- *id:* this is used to identify the preset, if you use an id of an existing preset, it will replace it with your custom version.
- *label:* Name of the option that appears in the menu.
- *description:* Description of the option that appears in the menu. You can use `{fileName}` and `{ext}` and it will be replaced with the correct data extracted from the original file. This is useful when trying to display how the file name will end up looking like.
- *createFile:* this is the file name to create. As with description you can write `"{fileName}.example.{ext}"` if you want to make use of those properties.
- *importInComponentName:* If you want the file to be imported in the component, just give it an import name and the extension will know how and where to add it.
- *importComponent:* If you want the file to import the component, set this to true and the extension will take care of it.

##### Overriding presets
In order to override a preset you just need to define a new custom option with the same id as the preset you want to override. You need to fully override it by providing all the necessary parameter.

We recommend going to the [presets file](../blob/master/src/options/presets.ts) and copying the config you want to override to have a working base and go from there.

### Donate
If you you find this extension useful, you can make a donation [here](https://paypal.me/650egor)

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 15 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "folderize",
"displayName": "folderize",
"description": "Easily convert a Javascript file into a folder with an index file without breaking imports / exports. Supports .js, .jsx, .ts, .tsx.",
"version": "0.0.5",
"version": "0.0.6",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you are adding new features, I believe this should be a minor version bump.

Suggested change
"version": "0.0.6",
"version": "0.1.0",

"publisher": "ee92",
"repository": "https://github.com/ee92/folderize",
"icon": "assets/logo.png",
Expand Down Expand Up @@ -43,6 +43,19 @@
"group": "boilerplate@1"
}
]
},
"configuration": {
"title": "Folderize",
"properties": {
"folderize.optionalFilesToGenerate": {
"type": "array",
"default": [
"test",
"css_module"
],
"markdownDescription": "Customize the files you want to generate by selecting from the presets or creating your own templates. To learn more about how to customize this setting go to the [documentation](https://github.com/ee92/folderize)."
}
}
}
},
"scripts": {
Expand All @@ -61,4 +74,4 @@
"webpack-cli": "^3.3.6"
},
"license": "SEE LICENSE IN LICENSE.md"
}
}
54 changes: 32 additions & 22 deletions src/folderize.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import * as vscode from 'vscode';
import {
mkdirSync,
readFileSync,
writeFileSync,
renameSync
mkdirSync,
readFileSync,
writeFileSync,
renameSync
} from 'fs';

import {Context, Option} from './types';
import {getOptions} from './options';
import {stringRegex, importRegex, firstEmptyLineRegex} from './regex';
import {formatIndexFile, formatImportPath, formatCssImport} from './format';
import { Context, Option } from './types';
import { getOptions } from './options';
import { stringRegex, importRegex, firstEmptyLineRegex } from './regex';
import { formatIndexFile, formatImportPath, formatImportInComponent, formatImportComponent } from './format';

const createEmptyFolder = (c: Context) => {
mkdirSync(c.folderPath);
Expand All @@ -31,12 +31,20 @@ const updateLocalImports = (c: Context, o: Option[]) => {
const newPath = match.replace(stringRegex, formatImportPath);
return newPath;
});
const importCss = o.some(option => option.id === 'css_module');
if (importCss) {
updatedText = updatedText.replace(firstEmptyLineRegex, () => {
return formatCssImport(c.fileName);
});
}

o.forEach(option => {
if (option.importInComponentName && option.createFile) {
const lineToAdd = formatImportInComponent(
option.importInComponentName,
option.createFile,
c.fileName,
c.filePath,
!updatedText
);
updatedText = !updatedText ? lineToAdd : updatedText.replace(firstEmptyLineRegex, lineToAdd);
}
});

writeFileSync(c.newPath, updatedText);
};

Expand All @@ -45,7 +53,9 @@ const createOptionalFiles = (c: Context, o: Option[]) => {
if (option.createFile) {
const name = option.createFile;
const path = c.folderPath + '/' + name;
writeFileSync(path, '');

const fileContent = option.importComponent ? formatImportComponent(c.fileName) : '';
writeFileSync(path, fileContent);
}
});
};
Expand All @@ -72,7 +82,7 @@ const showOptions = (context: Context) => {
export const init = (event: vscode.Uri) => {
const path = event.fsPath;
const filePath = path.split('.').pop() || '';
const folderPath = path.split('.').slice(0,-1).join('.');
const folderPath = path.split('.').slice(0, -1).join('.');
const fileName = folderPath.split('/').pop() || '';
const newPath = folderPath + '/' + fileName + '.' + filePath;

Expand All @@ -83,10 +93,10 @@ export const init = (event: vscode.Uri) => {
fileName,
newPath
};
showOptions(context).then((selectedOptions) => {
if (selectedOptions) {
createFolder(context, selectedOptions);
}
});

showOptions(context).then((selectedOptions) => {
if (selectedOptions) {
createFolder(context, selectedOptions);
}
});
};
23 changes: 15 additions & 8 deletions src/format/format.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { parsePath } from './../utils/utils';

export const formatIndexFile = (name: string) => {
return `export { default } from './${name}';\n`;
};

export const formatCssImport = (name: string) => {
return `\nimport styles from './${name}.module.css'\n\n`;
export const formatImportInComponent = (importName: string, path: string, name: string, ext: string, fileIsEmpty: boolean) => {
const importText = `import ${importName} from './${parsePath(path, name, ext)}'\n`;
return fileIsEmpty ? importText : `\n${importText}\n`;
};

export const formatImportComponent = (name: string) => {
return `import ${name} from './${name}';\n`;
};

export const formatImportPath = (s: string) => {
const prefix = s.slice(1,3);
switch(prefix) {
case('./'):
return s.substr(0,1) + '.' + s.substr(1);
case('..'):
return s.substr(0,1) + '../' + s.substr(1);
const prefix = s.slice(1, 3);
switch (prefix) {
case ('./'):
return s.substr(0, 1) + '.' + s.substr(1);
case ('..'):
return s.substr(0, 1) + '../' + s.substr(1);
default:
return s;
}
Expand Down
56 changes: 40 additions & 16 deletions src/options/options.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
import {Context} from '../types';
import { parsePath } from './../utils/utils';
import * as vscode from 'vscode';
import { Context, Option } from '../types';
import { checkOptionType } from '../utils';
import { presets } from './presets';

const parseOption = (option: Option, c: Context) => {
const parsedOption = { ...option };

['description', 'createFile'].forEach(attribute => {
if (!parsedOption[attribute]) return;

parsedOption[attribute] = parsePath(parsedOption[attribute], c.fileName, c.filePath)
})

return parsedOption;
}

export const getOptions = (c: Context) => {
return [
{
'id': 'test',
'label': 'Add test file',
'description': `${c.fileName}.test.${c.filePath}`,
'createFile': `${c.fileName}.test.${c.filePath}`
},
{
'id': 'css_module',
'label': 'Add CSS module',
'description': `${c.fileName}.module.css`,
'createFile': `${c.fileName}.module.css`
const filesToGenerateConfig = <Object[]>vscode.workspace.getConfiguration('folderize')
.get('optionalFilesToGenerate');

console.log(filesToGenerateConfig);

const selectedPresets = filesToGenerateConfig.filter(fileConfig => typeof fileConfig === 'string');
const addedOptions = filesToGenerateConfig.filter(checkOptionType).map(option => parseOption(option, c));

const presetOptions = presets.filter(({ id }) => selectedPresets.includes(id))
.map(preset => parseOption(preset, c));

const options = [...addedOptions];

presetOptions.forEach(presetOption => {
const existingOptionIndex = options.findIndex(option => option.id === presetOption.id);
if (existingOptionIndex !== -1) {
options[existingOptionIndex] = presetOption;
} else {
options.push(presetOption);
}
];
};
})

return options;

// TODO: add optional congif file
};
32 changes: 32 additions & 0 deletions src/options/presets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const presets = [
// TESTS
{
'id': 'test',
'label': 'Add test file',
'description': '{fileName}.test.{ext}',
'createFile': '{fileName}.test.{ext}',
'importComponent': true
},
{
'id': 'spec',
'label': 'Add test file (spec)',
'description': '{fileName}.spec.{ext}',
'createFile': '{fileName}.spec.{ext}',
'importComponent': true
},
// STYLES
{
'id': 'css_module',
'label': 'Add CSS module',
'description': '{fileName}.module.css',
'createFile': '{fileName}.module.css',
'importInComponentName': 'styles'
},
{
'id': 'styles',
'label': 'Add styles file',
'description': '{fileName}.styles.{ext}',
'createFile': '{fileName}.styles.{ext}',
'importInComponentName': 'styles'
},
];
2 changes: 1 addition & 1 deletion src/regex/regex.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const importRegex = /(import[^'"]*|require\()('(.*?)'|"(.*?)")/g;
export const exportRegex = /export /g;
export const stringRegex = /('(.*?)'|"(.*?)")/;
export const firstEmptyLineRegex = /\n\n/;
export const firstEmptyLineRegex = /\n\n/;
2 changes: 2 additions & 0 deletions src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ export type Option = {
label: string,
description: string,
createFile?: string,
importInComponentName?: string,
importComponent?: boolean
};
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './utils';
12 changes: 12 additions & 0 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Option } from './../types/types';

export const checkOptionType = (element: any): element is Option => {
if (!element) return false;

return ['id', 'label', 'description', 'createFile']
.every(name => !!element[name] && typeof (element[name]) === 'string');
}

export const parsePath = (path: string, fileName: string, ext: string) => {
return path.replace('{fileName}', fileName).replace('{ext}', ext);
}