Repository: Subwaytime/vite-aliases Branch: main Commit: 8da1b2113ab5 Files: 29 Total size: 26.1 KB Directory structure: gitextract_2w2y_h5y/ ├── .gitattributes ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── example/ │ ├── index.html │ ├── package.json │ ├── src/ │ │ ├── app.vue │ │ ├── components/ │ │ │ └── navigation.vue │ │ ├── logs/ │ │ │ └── vite-aliases.json │ │ ├── main.ts │ │ ├── pages/ │ │ │ └── home.vue │ │ └── utils/ │ │ └── random.ts │ ├── tsconfig.json │ ├── vite-aliases.json │ └── vite.config.ts ├── package.json ├── src/ │ ├── constants.ts │ ├── fs/ │ │ ├── config.ts │ │ ├── glob.ts │ │ ├── index.ts │ │ └── log.ts │ ├── generator.ts │ ├── index.ts │ ├── types.ts │ └── utils.ts ├── tsconfig.json └── tsup.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto ================================================ FILE: .github/FUNDING.yml ================================================ github: subwaytime ================================================ FILE: .gitignore ================================================ node_modules .DS_Store dist .prettierignore .vscode package-lock.json example/jsconfig.json exmaple/tsconfig.json ================================================ FILE: .prettierrc ================================================ { "endOfLine": "auto", "jsxBracketSameLine": true, "jsxSingleQuote": true, "printWidth": 120, "singleQuote": true, "tabWidth": 4, "trailingComma": "all", "useTabs": true, "vueIndentScriptAndStyle": false } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020-PRESENT Leon Langer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

vite-aliases

Alias auto generation for Vite 6

## Usage Install ```bash npm i vite-aliases -D ``` Add it to `vite.config.js` ```ts // vite.config.js import { ViteAliases } from 'vite-aliases' export default { plugins: [ ViteAliases() ] }; ``` Add this to `package.json` (Plugin is only available for ESM) ```json { "type": "module" } ``` That's it!


Based on your Folderstructure, it will now automatically generate all needed aliases.
You can configure it to any desired Depth, but it is recommend to stay on the first Level!
Furthermore it will use your native OS filesystem, so it works on Linux, Mac, Windows and other OS.


This structure: ``` src assets components pages store utils ``` will generate the following: ```ts [ { find: '~', replacement: '${your_project_path}/src' }, { find: '~assets', replacement: '${your_project_path}/src/assets' }, { find: '~components', replacement: '${your_project_path}/src/components' }, { find: '~pages', replacement: '${your_project_path}/src/pages' }, { find: '~store', replacement: '${your_project_path}/src/store' }, { find: '~utils', replacement: '${your_project_path}/src/utils' }, ] ``` ## Best Practice `vite-aliases` is meant to simply take the first Layer of your folders and turn it into useful Shortcuts.
Therefore i advise you to use the default Configuration and not use folders with the same name, otherwise it will create an Error. If however you need duplicate Foldernames, enable `adjustDuplicates`.
This will turn the entire Filepath of said duplicate into the alias itself, like shown in the Example below. Example: ``` `src/components` -> `~components` `src/pages/components` -> `~pagesComponents` `src/test/new/partials/components` -> `~testNewPartialsComponents` ``` and so on.. ## Configuration Current available options: ```ts ViteAliases({ /** * Relative path to the project directory */ dir: 'src', /** * Prefix symbol for the aliases */ prefix: '~', /** * Allow searching for subdirectories */ deep: true, /** * Search depthlevel for subdirectories */ depth: 1, /** * Creates a Logfile * use `logPath` to change the location */ createLog: false, /** * Path for Logfile */ logPath: 'src/logs', /** * Create global project directory alias */ createGlobalAlias: true, /** * Turns duplicates into camelCased path aliases */ adjustDuplicates: false, /** * Used paths in JS/TS configs will now be relative to baseUrl */ useAbsolute: false, /** * Adds seperate index paths * approach created by @davidohlin */ useIndexes: false, /** * Generates paths in IDE config file * works with JS or TS */ useConfig: true, /** * Override config paths */ ovrConfig: false, /** * Will generate Paths in tsconfig * used in combination with `useConfig` * Typescript will be auto detected */ dts: false, /** * Disables any terminal output */ silent: true, /** * Root path of Vite project */ root: process.cwd() }); ``` ## Thanks Thanks to [@brattonross](https://github.com/brattonross) and [@antfu](https://github.com/antfu), due to this tiny library beeing inspired by both projects: [vite-plugin-voie](https://github.com/vamplate/vite-plugin-voie) [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components). ## License MIT License © 2020-PRESENT [Leon Langer](https://github.com/subwaytime) ================================================ FILE: example/index.html ================================================ Vite App
================================================ FILE: example/package.json ================================================ { "name": "@vite-aliases/example", "description": "Example: Alias auto generation for Vite", "version": "0.1.0", "private": true, "license": "MIT", "author": "Subwaytime ", "type": "module", "repository": { "type": "git", "url": "https://github.com/subwaytime/vite-aliases" }, "homepage": "https://github.com/subwaytime/vite-aliases#readme", "bugs": "https://github.com/subwaytime/vite-aliases/issues", "scripts": { "update:packages": "npm update --save-dev && npm update --save", "dev": "vite", "build": "vite build" }, "dependencies": { "@vitejs/plugin-vue": "^4.3.3", "@vue/compiler-sfc": "^3.3.4", "vite": "^4.4.9", "vitest": "^0.34.3", "vue": "^3.3.4" }, "devDependencies": { "typescript": "^5.2.2" } } ================================================ FILE: example/src/app.vue ================================================ ================================================ FILE: example/src/components/navigation.vue ================================================ ================================================ FILE: example/src/logs/vite-aliases.json ================================================ [ { "find": "@assets", "replacement": "D:/work/vite-aliases/example/src/assets" }, { "find": "@components", "replacement": "D:/work/vite-aliases/example/src/components" }, { "find": "@logs", "replacement": "D:/work/vite-aliases/example/src/logs" }, { "find": "@pages", "replacement": "D:/work/vite-aliases/example/src/pages" }, { "find": "@utils", "replacement": "D:/work/vite-aliases/example/src/utils" }, { "find": "@", "replacement": "D:/work/vite-aliases/example/src" } ] ================================================ FILE: example/src/main.ts ================================================ import App from './app.vue' import { createApp } from 'vue' createApp(App).mount('#app'); ================================================ FILE: example/src/pages/home.vue ================================================ ================================================ FILE: example/src/utils/random.ts ================================================ /** * Return a Random Hexcode */ export function random() { let n = (Math.random() * 0xfffff * 1000000).toString(16); return '#' + n.slice(0, 6); }; ================================================ FILE: example/tsconfig.json ================================================ { "compilerOptions": { "baseUrl": ".", "paths": { "~components/*": [ "src/components/*" ], "~logs/*": [ "src/logs/*" ], "~pages/*": [ "src/pages/*" ], "~utils/*": [ "src/utils/*" ], "~/*": [ "src/*" ] } } } ================================================ FILE: example/vite-aliases.json ================================================ [ { "find": "~components", "replacement": "/home/subway/work/vite-aliases/example/src/components" }, { "find": "~logs", "replacement": "/home/subway/work/vite-aliases/example/src/logs" }, { "find": "~pages", "replacement": "/home/subway/work/vite-aliases/example/src/pages" }, { "find": "~utils", "replacement": "/home/subway/work/vite-aliases/example/src/utils" }, { "find": "~", "replacement": "/home/subway/work/vite-aliases/example/src" } ] ================================================ FILE: example/vite.config.ts ================================================ import vue from '@vitejs/plugin-vue'; import { defineConfig } from 'vitest/config'; import { ViteAliases } from '../dist/index.js'; export default defineConfig({ plugins: [ vue(), ViteAliases({ createLog: true, logPath: './', adjustDuplicates: true, }), ], server: { port: 8080, }, logLevel: 'silent', }); ================================================ FILE: package.json ================================================ { "name": "vite-aliases", "description": "Alias auto generation for Vite", "version": "0.11.8", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", "module": "./dist/index.js", "import": "./dist/index.js" } }, "private": false, "license": "MIT", "author": "Subwaytime ", "repository": { "type": "git", "url": "https://github.com/subwaytime/vite-aliases" }, "homepage": "https://github.com/subwaytime/vite-aliases#readme", "bugs": "https://github.com/subwaytime/vite-aliases/issues", "files": [ "dist", "*.d.ts" ], "keywords": [ "vite", "vue", "alias", "aliases", "auto", "generation" ], "scripts": { "build": "tsup", "dev": "npm run build -- --watch src", "release": "npx git-ensure -a && npx bumpp --commit --tag --push && npm publish", "prepublishOnly": "npm run build" }, "devDependencies": { "@types/node": "^22.10.5", "terser": "^5.37.0", "tsup": "^8.3.5", "typescript": "^5.7.2" }, "dependencies": { "chokidar": "^4.0.3", "comment-json": "^4.2.5", "consola": "^3.3.3", "fast-glob": "^3.3.3", "local-pkg": "^0.5.1" }, "peerDependencies": { "vite": "6.x" }, "packageManager": "pnpm@9.15.2", "engines": { "node": ">=22.x.x" } } ================================================ FILE: src/constants.ts ================================================ import { isPackageExists } from 'local-pkg'; import type { Options } from './types'; export const MODULE_NAME = 'vite-aliases'; export const config: Required = { dir: 'src', prefix: '~', deep: true, depth: 1, createGlobalAlias: true, createLog: false, logPath: 'src/logs', adjustDuplicates: false, useAbsolute: false, useConfig: true, ovrConfig: false, useIndexes: false, dts: false, silent: true, root: process.cwd(), }; export const IDEConfig = { compilerOptions: { baseUrl: '.', paths: {}, }, }; ================================================ FILE: src/fs/config.ts ================================================ import { abort, readJSON, interpretFileIndentation, writeJSON } from '../utils'; import { IDEConfig } from '../constants'; import type { Generator } from '../generator'; import type { Process } from '../types'; import type { Indentation } from '../utils'; import { normalizePath } from 'vite'; import { isEmpty } from '../utils'; /** * Creates a JS or TS Configfile */ export async function writeConfig(gen: Generator, process: Process = 'default') { const { root, dir, dts, useConfig, ovrConfig } = gen.options; if (!useConfig) { return; } const name = dts ? 'tsconfig' : 'jsconfig'; const file = normalizePath(`${root}/${name}.json`); try { let [indentation, json]: [Indentation, any] = await Promise.all([ interpretFileIndentation(file), readJSON(file), ]); if (isEmpty(json) || isEmpty(json.compilerOptions)) { json = Object.assign({}, IDEConfig); } let paths = json.compilerOptions.paths || {}; if (process === 'remove') { paths = Object.fromEntries( Object.entries(paths).filter((p: any) => { if (Object.values(gen.paths).flat().includes(p[1][0]) && p[1][0].includes(dir)) { return p; } else if (!p[1][0].includes(dir)) { return p; } }), ); } json.compilerOptions.paths = ovrConfig ? gen.paths : { ...paths, ...gen.paths }; await writeJSON(file, json, process, indentation); } catch (error) { abort(`Cannot write Config: ${file}.`); } } ================================================ FILE: src/fs/glob.ts ================================================ import fg from 'fast-glob'; import type { Generator } from '../generator'; import { logger } from '../utils'; /** * Return all folders from the project directory * @param options */ export async function getDirectories(gen: Generator) { const { dir, root, deep, depth } = gen.options; const directories = await fg.sync(deep ? `${dir}/**/*` : `${dir}/*`, { ignore: ['node_modules'], onlyDirectories: true, cwd: root, deep: depth, absolute: true, }); if (!directories.length) { logger.error(new Error('No Directories could be found!')); } gen.addAlias(directories); } ================================================ FILE: src/fs/index.ts ================================================ export { writeConfig } from './config'; export { getDirectories } from './glob'; export { writeLog } from './log'; ================================================ FILE: src/fs/log.ts ================================================ import { existsSync } from 'node:fs'; import { mkdir } from 'node:fs/promises'; import { normalizePath } from 'vite'; import { MODULE_NAME } from '../constants'; import type { Generator } from '../generator'; import type { Process } from '../types'; import { abort, writeJSON } from '../utils'; /** * Creates a Logfile * If needed it will also create a Logfolder */ export async function writeLog(gen: Generator, process: Process = 'normal') { const { createLog, logPath } = gen.options; if (!createLog) { return; } const folder = normalizePath(logPath); const file = normalizePath(`${folder}/${MODULE_NAME}.json`); const data = gen.aliases; try { if (!existsSync(folder)) { await mkdir(folder, { recursive: true }); } await writeJSON(file, data, process); } catch (error) { abort(`Cannot create Logfolder ${folder}.`); } } ================================================ FILE: src/generator.ts ================================================ import type { Alias, Options, Path } from './types'; import { normalizePath } from 'vite'; import { logger, split, toArray, toCamelCase, toRelative } from './utils'; import { writeLog, writeConfig } from './fs'; import chokidar from 'chokidar'; import { resolve } from 'path'; import { config } from "./constants"; import { getDirectories } from './fs'; import { isPackageExists } from 'local-pkg'; /** * Reads the Projectpath and returns Vite Aliases * @param options * @returns Record */ export class Generator { readonly options: Options; readonly fullPath: string; public aliases: Alias[] = []; public directories = new Set(); public paths: Path = {}; constructor(public readonly servermode: string, options?: Partial) { this.options = Object.assign({}, config, options); logger.level = this.options.silent ? -999 : 3; this.fullPath = normalizePath(resolve(this.options.root, this.options.dir)); // needed for absolute paths in watcher this.detectTypescript(); // only watch on dev not on build if (servermode === 'serve') { this.observe(); } } /** * Add Alias * @param path */ addAlias(path: string | string[]) { toArray(path).forEach((p) => { const correctedPath = normalizePath(p); const folders = split(correctedPath.replace(this.fullPath, this.options.dir), '/').filter(Boolean); const lastDir = folders.slice(-1)[0]; let key = `${this.options.prefix}${lastDir}`; const uniqueFolders = [...new Set(folders)] as string[]; this.checkForDuplicates(correctedPath, folders, uniqueFolders); if(this.aliases.some((a) => a.find === key)) { logger.warn( 'There are duplicate Aliases generated, either fix the folderstructure or enable adjustDuplicates.', ); if (this.options.adjustDuplicates && this.options.depth > 1) { const name = folders.filter((f) => !split(normalizePath(this.options.dir), '/').includes(f)).join('-'); key = `${this.options.prefix}${toCamelCase(name)}`; } } if(lastDir === this.options.dir && this.options.createGlobalAlias) { key = `${this.options.prefix}`; } this.directories.add(p); this.aliases.push({ find: `${key}`, replacement: `${p}` }); const configPath = this.options.useAbsolute ? correctedPath : toRelative(correctedPath, this.options.dir); if(this.options.useIndexes) { this.paths[key] = [configPath]; } else { this.paths[`${key}/*`] = [`${configPath}/*`]; } }); } /** * Remove Alias * @param path */ removeAlias(path: string | string[]) { toArray(path).forEach((p) => { const correctedPath = normalizePath(p); if(this.directories.has(correctedPath)) { this.directories.delete(correctedPath); this.aliases = this.aliases.filter((a) => a.replacement != correctedPath); this.paths = Object.fromEntries( Object.entries(this.paths).filter( (configPath) => configPath[1][0].slice(0, -2) != ( this.options.useIndexes ? correctedPath : `${correctedPath}/*` ) ) ) } }); } /** * Check for duplicates before adding them as aliases * @param initialPath * @param folders * @param uniqueFolders */ checkForDuplicates(initialPath: string, folders: string[], uniqueFolders: string[]) { if (folders.length !== uniqueFolders.length) { const duplicateFolders = [...folders].sort().filter((f, i, self) => { if (self[i + 1] === self[i]) { return f; } }); logger.warn(`Path: '${initialPath}' contains multiple folders with same name: ${duplicateFolders.toString()}.`); } } /** * Check if TypeScript is installed */ detectTypescript() { this.options.dts = this.options.dts || isPackageExists(`${this.options.root}/node_modules/typescript`) || isPackageExists('typescript'); logger.info(this.options.dts ? 'TypeScript got detected.' : 'TypeScript is not installed, fallback to JS only.'); } /** * Glob directories * writes Logfile * writes IDE Config */ private searched: boolean = false; async init() { if (this.searched) { return; } await getDirectories(this); // add global alias if allowed if (this.options.createGlobalAlias) { this.addAlias(this.fullPath); } // start alias logger if allowed writeLog(this); // write js/ts config if allowed writeConfig(this); this.searched = true; } /** * Watch for directory changes */ observe() { const watcher = chokidar.watch(this.fullPath, { ignoreInitial: true, depth: this.options.depth }); watcher .on('addDir', (path) => { this.addAlias(path); writeLog(this, 'add'); writeConfig(this, 'add'); logger.info(`Watcher added new Path: ${path}`); }) .on('unlinkDir', (path) => { this.removeAlias(path); writeLog(this, 'remove'); writeConfig(this, 'remove'); logger.info(`Watcher removed Path: ${path}`); }); } } ================================================ FILE: src/index.ts ================================================ import type { Plugin } from 'vite'; import { Generator } from './generator'; import type { Options } from './types'; import { toArray } from './utils'; export function ViteAliases(options: Partial = {}): Plugin { let gen: Generator; return { name: 'vite-aliases', enforce: 'pre', config(config, { command }) { gen = new Generator(command, options); gen.init(); config.resolve = { alias: config.resolve?.alias ? [...toArray(config.resolve.alias as any), ...gen.aliases] : gen.aliases, }; }, }; }; ================================================ FILE: src/types.ts ================================================ /** * Library options. */ export interface Options { /** * Relative path to the project directory * @default 'src' */ dir: string; /** * Prefix symbol for the aliases * @default '~' */ prefix: string; /** * Allow searching for subdirectories * @default true */ deep: boolean; /** * Search depthlevel for subdirectories * @default 1 */ depth: number; /** * Creates a Logfile * use `logPath` to change the location * @default false */ createLog: boolean; /** * Path for Logfile * @default 'src/logs' */ logPath: string; /** * Create global project directory alias * @default true */ createGlobalAlias: boolean; /** * Turns duplicates into camelCased path aliases * @default false */ adjustDuplicates: boolean; /** * Used paths in JS/TS configs will now be relative to baseUrl * @default false */ useAbsolute: boolean; /** * Adds seperate index paths * @default false */ useIndexes: boolean; /** * Generates paths in IDE config file * works with JS or TS * @default true */ useConfig: boolean; /** * Override config paths * @default false */ ovrConfig: boolean /** * Will generate Paths in tsconfig * used in combination with `useConfig` * Typescript will be auto detected * @default false */ dts: boolean; /** * Disables any terminal output * @default true */ silent: boolean; /** * Root path of Vite project * @default 'process.cwd()' */ root: string; } export interface Alias { find: string; replacement: string; } export interface Path { [key: string]: string[]; } export type Process = 'add' | 'remove' | 'default' | 'normal'; ================================================ FILE: src/utils.ts ================================================ import { createConsola } from 'consola'; import fs from 'node:fs/promises'; import { normalizePath } from 'vite'; import { MODULE_NAME } from './constants'; import type { Process } from './types'; import { parse, stringify } from 'comment-json'; /** * Split String on Seperator into Array * @param string * @param seperator */ export function split(string: string, seperator: string): string[] { return string.split(seperator); } /** * Turns a Value into Array * @param string * @param seperator */ export function toArray(value: T | T[]): T[] { if (Array.isArray(value)) { return value; } else { return [value]; } } /** * Turns a absolute Path into an Relative Path * @param string * @param seperator */ export function toRelative(path: string, dir: string): string { let folders = split(normalizePath(path), '/'); folders = folders.slice( folders.findIndex((f) => f === dir), folders.length, ); return normalizePath(`./${folders.join('/')}`); } /** * Turns any String into Camelcased String * @param string */ export function toCamelCase(string: string): string { return string.trim().replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : '')); } /** * Check if Value is Empty * supports: Array, Object, String * @param value */ export function isEmpty(value: any) { if (value === null || value === undefined || value === '{}' || value === '' || JSON.stringify(value) === '{}') { return true; } if ((Array.isArray(value) && Object.keys(value).length <= 0) || (Array.isArray(value) && value.length === 0)) { return true; } // if (Reflect.ownKeys(value).length === 0 && value.constructor === Object) { // return true; // } return false; } /** * Simple Info/Warn/Error Consola Instance */ export const logger = createConsola({ defaults: { message: `[${MODULE_NAME}] -` } }); export function abort(message: any) { throw logger.error(new Error(message)); } /** * Reads a JSON File */ export async function readJSON(path: string) { try { const file = (await fs.readFile(path, 'utf-8')).toString(); logger.success(`Config: ${path} successfully read!`); return parse(file); } catch (error) { logger.error(`File: ${path} was not found!`); } } /** * Writes a JSON File */ export const DEFAULT_INDENTATION: Indentation = 4; // default to 4 spaces before introducing the intepretation feature export async function writeJSON(path: string, data: any, process: Process, indentation: Indentation = DEFAULT_INDENTATION) { const name = path.replace(/^.*[\\\/]/, ''); const state = process === 'add' || process === 'default' ? 'created' : 'updated'; try { await fs.writeFile(path, stringify(data, null, indentation)); logger.success(`File: ${name} successfully ${state}`); } catch (error) { logger.error(`File: ${name} could not be ${state}.`); abort(error); } } /** * Interprets file indentations */ export type Indentation = number | '\t'; export async function interpretFileIndentation(path: string): Promise { const name = path.replace(/^.*[\\\/]/, ''); try { const content = (await fs.readFile(path, 'utf-8')).toString(); const lines = content.split('\n'); const secondLine = lines[1]; let indentation: Indentation; if (secondLine.startsWith('\t')) { indentation = '\t'; } else { const firstNonSpaceCharacter = split(secondLine, '').findIndex(char => char !== ' '); if (firstNonSpaceCharacter === -1) { logger.error('Failed to interpret indentation from file. (No indentation found)'); } indentation = firstNonSpaceCharacter; } logger.info(`File: Interpreted indentation as (${typeof indentation === 'number' ? `${indentation} spaces` : 'tabs'}) from file ${name} successfully`); return indentation; } catch (error) { logger.error(`File: Failed to interpret indentation from ${name}.`); return DEFAULT_INDENTATION; } } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "module": "ESNext", "target": "ESNext", "lib": [ "ESNext" ], "allowSyntheticDefaultImports": true, "esModuleInterop": false, "strict": true, "strictNullChecks": true, "moduleResolution": "node", "resolveJsonModule": true }, "exclude": [ "**/dist", "**/node_modules", "**/test" ] } ================================================ FILE: tsup.config.ts ================================================ import type { Options } from 'tsup'; export const tsup: Options = { entry: ['./src/index.ts'], format: ['esm'], minify: 'terser', dts: true, splitting: true, clean: true, shims: true, treeshake: 'smallest' };