Skip to content
Merged

Next #38

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
47 changes: 25 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,31 +101,34 @@ The plugin will now enforce modular architecture patterns in your Vue.js project

## Rules

This plugin provides rules to enforce modular architecture boundaries in Vue.js applications.

If you have suggestions for new rules or improvements, please open an issue or write me directly to <andrew@molyuk.com>. I'm happy to discuss and probably add new rules that can make our Vue projects better!

| Rule | Description |
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
| [app-imports](./docs/rules/app-imports.md) | App folder can import from shared and features with specific exceptions |
| [components-index-required](./docs/rules/components-index-required.md) | All components folders must contain an index.ts file for component exports |
| [cross-imports-absolute](./docs/rules/cross-imports-absolute.md) | Cross-layer imports must use the project root alias instead of absolute paths |
| [feature-imports](./docs/rules/feature-imports.md) | Features should only import from the shared layer or their own internal files |
| [feature-index-required](./docs/rules/feature-index-required.md) | Each feature folder must contain an index.ts file as its public API |
| [file-component-naming](./docs/rules/file-component-naming.md) | All Vue components must use PascalCase naming |
| [file-ts-naming](./docs/rules/file-ts-naming.md) | All TypeScript files must use camelCase naming |
| [folder-kebab-case](./docs/rules/folder-kebab-case.md) | All folders must use kebab-case naming |
| [service-filename-no-suffix](./docs/rules/service-filename-no-suffix.md) | Service files must not have Service suffix |
| [sfc-order](./docs/rules/sfc-order.md) | Enforce SFC block order: script, template, style |
| [sfc-required](./docs/rules/sfc-required.md) | All Vue components should be written as Single File Components |
| [shared-imports](./docs/rules/shared-imports.md) | Shared folder cannot import from features or views |
| [shared-ui-index-required](./docs/rules/shared-ui-index-required.md) | The shared/ui folder must contain an index.ts file for UI component exports |
| [store-filename-no-suffix](./docs/rules/store-filename-no-suffix.md) | Store files must not have Store suffix |
| [views-suffix](./docs/rules/views-suffix.md) | View files must end with View.vue suffix |
This plugin provides rules to enforce modular architecture boundaries in Vue.js applications. Here is a summary of the available rules:

| Rule | Description |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| [app-imports](./docs/rules/app-imports.md) | App folder can import from shared and features with specific exceptions |
| [components-index-required](./docs/rules/components-index-required.md) | All components folders must contain an index.ts file for component exports |
| [cross-imports-alias](./docs/rules/cross-imports-alias.md) | Cross-layer imports must use the project root alias instead of absolute paths |
| [feature-imports](./docs/rules/feature-imports.md) | Features should only import from the shared layer or their own internal files |
| [feature-index-required](./docs/rules/feature-index-required.md) | Each feature folder must contain an index.ts file as its public API |
| [file-component-naming](./docs/rules/file-component-naming.md) | All Vue components must use PascalCase naming |
| [file-ts-naming](./docs/rules/file-ts-naming.md) | All TypeScript files must use camelCase naming |
| [folder-kebab-case](./docs/rules/folder-kebab-case.md) | All folders must use kebab-case naming |
| [internal-imports-relative](./docs/rules/internal-imports-relative.md) | Internal feature/shared/app imports should use relative paths instead of alias |
| [service-filename-no-suffix](./docs/rules/service-filename-no-suffix.md) | Service files must not have Service suffix |
| [sfc-order](./docs/rules/sfc-order.md) | Enforce SFC block order: script, template, style |
| [sfc-required](./docs/rules/sfc-required.md) | All Vue components should be written as Single File Components |
| [shared-imports](./docs/rules/shared-imports.md) | Shared folder cannot import from features or views |
| [shared-ui-index-required](./docs/rules/shared-ui-index-required.md) | The shared/ui folder must contain an index.ts file for UI component exports |
| [store-filename-no-suffix](./docs/rules/store-filename-no-suffix.md) | Store files must not have Store suffix |
| [views-suffix](./docs/rules/views-suffix.md) | View files must end with View.vue suffix |

## Contributing

Pull requests and issues are welcome! Please follow the code style and add tests for new rules. See [CONTRIBUTING](./CONTRIBUTING.md) for details.
We welcome contributions!

If you have ideas or suggestions for new rules or improvements, please open an issue. Let's discuss and probably add new rules that can make our Vue projects better!

If you want to contribute code, please fork the repository and create a pull request with your changes. Make sure to include tests for any new functionality. See [CONTRIBUTING](./CONTRIBUTING.md) for more details.

## License

Expand Down
4 changes: 3 additions & 1 deletion docs/rules/app-imports.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import from the `shared/` layer or from feature public APIs. This helps keep the
app shell focused on composition and prevents accidental dependencies on feature
internals.

Included in recommended config.

## Rule Details

This rule flags import declarations that originate from files inside the
Expand All @@ -18,7 +20,7 @@ project-relative path or when the filename matches an `ignores` pattern.
The router special-case is respected: `app/router.ts` may import feature route
files so the global router can be composed from feature routes.

How it works
## How it works

- Resolve the current filename to a project-relative path. If the file is not
inside `appPath` do nothing.
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/components-index-required.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Enforce a components folder index file (for example `features/<feature>/components/index.ts`) so component consumers import from the components root instead of deep-importing implementation files.

Included in recommended config.

## Rule Details

This rule scans the project's source tree (starting at the configured project `rootPath`, default: `src`) for `components` directories and verifies they contain a public API file. A stable components index makes imports shorter, centralizes re-exports, and reduces accidental deep imports.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
````markdown
# vue-modular/cross-imports-absolute
# vue-modular/cross-imports-alias

Enforce that cross-layer imports (between features or from features to shared) must use the project root alias (e.g. `@/`) instead of non-aliased absolute paths or relative paths that cross architectural boundaries.

Not included in recommended config.

## Rule Details

This rule flags import declarations that reference other parts of the project using non-aliased absolute paths (like `src/features/...` or `/features/...`) or relative paths that traverse outside the current architectural layer. Cross-layer imports should use the configured root alias to maintain consistency and prevent coupling to internal directory structures.
Expand Down Expand Up @@ -32,12 +34,12 @@ Note: project-level settings control `rootPath`, `rootAlias`, `appPath`, `featur

```js
{
"vue-modular/cross-imports-absolute": ["error"]
"vue-modular/cross-imports-alias": ["error"]
}

// ignore specific legacy files
{
"vue-modular/cross-imports-absolute": ["error", { "ignores": ["src/legacy/**"] }]
"vue-modular/cross-imports-alias": ["error", { "ignores": ["src/legacy/**"] }]
}
```

Expand Down
2 changes: 2 additions & 0 deletions docs/rules/feature-imports.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Require that feature code imports only from the `shared/` layer (features must not import from other features).

Included in recommended config.

## Rule Details

This rule flags imports originating from files under the configured features root (default: `src/features`) that resolve into a different feature. Keeping cross-feature dependencies routed through the `shared/` layer reduces coupling and prevents consumers from depending on implementation details inside other features.
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/feature-index-required.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Require a public feature entry file (for example `src/features/<feature>/index.ts`) so consumers import from the feature root instead of deep-importing internal implementation files.

Included in recommended config.

## Rule Details

This rule verifies that each feature folder exposes a public API index file. Keeping a small, stable public surface at the feature root helps encapsulate implementation details and makes refactors safer.
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/file-component-naming.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Enforce PascalCase filenames for Vue Single File Components (.vue).

Included in recommended config.

## Rule Details

This rule requires that component filenames use PascalCase (for example `MyButton.vue`) to make component names predictable and consistent across a project.
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/file-ts-naming.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Enforce camelCase filenames for TypeScript files (.ts and .tsx).

Included in recommended config.

## Rule Details

This rule requires that TypeScript filenames use camelCase (for example `useAuth.ts` or `userApi.ts`) to keep filenames consistent across a project.
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/folder-kebab-case.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Enforce kebab-case folder names under the project's source directory.

Included in recommended config.

## Rule Details

This rule requires that folder (directory) names use kebab-case: lowercase letters, numbers, and single hyphens between segments (for example `my-feature` or `api-v1`). The rule helps keep filesystem structure predictable and consistent across a project.
Expand Down
74 changes: 74 additions & 0 deletions docs/rules/internal-imports-relative.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# vue-modular/internal-imports-relative

Enforce that imports between nearby files inside the same feature (or within the shared/app folders) use relative paths instead of the project root alias (e.g. `@/`). This keeps internal feature wiring local and avoids coupling through the root alias for nearby imports.

Not included in recommended config.

## Rule Details

This rule flags import declarations that use the configured project root alias (default `@`) when the import target is within the same feature (same feature segment under `src/features`) or otherwise a nearby file inside the same architectural area (e.g. both files inside `src/shared` or `src/app`). In these cases prefer relative imports (like `./foo` or `../bar`) to make the dependency clearly local and easier to refactor.

How it works

- The rule resolves the current filename to a project-relative path using the plugin project options (for example `src/features/auth/components/Login.vue`). If the filename cannot be resolved (outside project root), the rule does nothing.
- For each import it resolves the import specifier to a normalized project-relative path. If the resolved path cannot be normalized (external packages, unresolved imports), the rule skips that import to avoid false positives.
- The rule determines the architectural areas of both the source file and the target (app, feature, shared). It allows:
- Relative imports within the same feature (same segment under `src/features`).
- Imports within shared or app when both sides are inside the same `shared` or `app` folders.
- If both source and target are within the project areas of interest but the import uses the project root alias (e.g. `@/features/...` or `@/shared/...`), the rule reports and recommends using a relative import instead.

## Options

The rule accepts an options object with the following properties:

- `ignores` (string[], default: `[]`) — array of minimatch glob patterns tested against the resolved project-relative filename. If any pattern matches, the rule will skip that file. Use this to permit exceptions during migration or for generated files.

Note: project-level settings control `rootPath`, `rootAlias`, `appPath`, `featuresPath`, and `sharedPath` used by the rule; those defaults are provided by the plugin and can be overridden via settings.

### Example configuration

```js
{
"vue-modular/internal-imports-relative": ["error"]
}

// ignore legacy features while migrating
{
"vue-modular/internal-imports-relative": ["error", { "ignores": ["src/features/legacy-*/**"] }]
}
```

## Examples

Incorrect (using alias inside same feature):

```ts
// File: src/features/auth/components/LoginForm.vue
import helper from '@/features/auth/utils/helper'
```

Correct (use a relative import for local feature files):

```ts
// File: src/features/auth/components/LoginForm.vue
import helper from '../utils/helper'
```

Incorrect (use alias inside shared when the target is nearby in shared):

```ts
// File: src/shared/components/Button.vue
import { theme } from '@/shared/utils/theme'
```

Correct:

```ts
// File: src/shared/components/Button.vue
import { theme } from '../utils/theme'
```

## When Not To Use

- If your team prefers always using the root alias for consistency across the codebase, disable this rule.
- Consider disabling this rule during large refactors or migrations where import style consistency is secondary to completing the change.
2 changes: 2 additions & 0 deletions docs/rules/service-filename-no-suffix.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Disallow the `Service` suffix in service filenames (for example `auth.ts`, not `authService.ts`).

Included in recommended config.

## Rule Details

This rule enforces concise service filenames for files placed under the configured service locations:
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/sfc-order.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Enforce a conventional order for Single File Component (SFC) blocks.

Included in recommended config.

This rule parses Vue component files using `@vue/compiler-sfc` and validates the relative order of SFC blocks (`<script>`, `<template>`, `<style>`). The rule only enforces order among blocks that are present — missing blocks are allowed.

Default order:
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/sfc-required.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Require Vue Single File Component (SFC) files to contain at least a `<template>` or `<script>` block.

Included in recommended config.

This rule enforces that Vue component files contain meaningful content by requiring at least one of the core SFC blocks (`<template>` or `<script>` / `<script setup>`). It only runs on files that are identified as Vue components (`.vue` files in the configured components folder).

## Rule Details
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/shared-imports.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Prevent files inside the `shared/` layer from importing implementation from `features/` (or `views`) folders.

Included in recommended config.

## Rule Details

This rule flags imports and re-exports found in files under the configured shared path (default: `src/shared`) when the resolved import points into a feature implementation or a `views` folder. The goal is to keep `shared/` code layer-agnostic and avoid coupling shared utilities to feature internals.
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/shared-ui-index-required.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Require a `shared/ui` public API file (for example `src/shared/ui/index.ts`) so shared UI components are exported from a single, stable entry point.

Not included in recommended config.

## Rule Details

This rule verifies that the configured `shared` UI folder contains a public API file. A central entry file shortens imports, centralizes re-exports, and prevents consumers from deep-importing component implementation files.
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/store-filename-no-suffix.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Disallow the `Store` suffix in store filenames (for example `auth.ts`, not `authStore.ts`).

Included in recommended config.

## Rule Details

This rule enforces concise store filenames for files placed under the configured store locations:
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/views-suffix.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Require that view files end with `View.vue` suffix.

Included in recommended config.

## Rule Details

This rule checks files located inside a `views/` folder (the exact folder name is configurable via project settings). When a file in a views folder does not end with the configured suffix (default `View.vue`) the rule reports.
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions src/rules.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { componentsIndexRequired } from './rules/components-index-required'
import { featureImports } from './rules/feature-imports'
import { crossImportsAbsolute } from './rules/cross-imports-absolute'
import { crossImportsAbsolute } from './rules/cross-imports-alias'
import { featureIndexRequired } from './rules/feature-index-required'
import { fileComponentNaming } from './rules/file-component-naming'
import { fileTsNaming } from './rules/file-ts-naming'
Expand All @@ -26,7 +26,7 @@ export const rules: Record<string, VueModularRuleModule> = {
'app-imports': appImports,
'service-filename-no-suffix': serviceFilenameNoSuffix,
'store-filename-no-suffix': storeFilenameNoSuffix,
'cross-imports-absolute': crossImportsAbsolute,
'cross-imports-alias': crossImportsAbsolute,
'feature-imports': featureImports,
'views-suffix': viewsSuffix,
}
3 changes: 1 addition & 2 deletions src/rules/app-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,13 @@ export const appImports = createRule<VueModularRuleModule>({
}
},
name: 'app-imports',
recommended: false,
recommended: true,
level: 'error',
meta: {
type: 'problem',
docs: {
description: 'app/ folder can import from shared/ and features/ (exception: app/router.ts may import feature route files)',
category: 'Dependency',
recommended: false,
},
defaultOptions: [defaultOptions],
schema: [
Expand Down
1 change: 0 additions & 1 deletion src/rules/components-index-required.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export const componentsIndexRequired = createRule<VueModularRuleModule>({
docs: {
description: 'Require a components/index.ts to expose the components public API',
category: 'File Organization',
recommended: false,
},
defaultOptions: [defaultOptions],
schema: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,14 @@ export const crossImportsAbsolute = createRule<VueModularRuleModule>({
},
}
},
name: 'cross-imports-absolute',
name: 'cross-imports-alias',
recommended: false,
level: 'error',
meta: {
type: 'problem',
docs: {
description: 'Cross-layer imports must use the project root alias (e.g. @/) instead of non-aliased absolute paths',
category: 'Dependency',
recommended: false,
},
defaultOptions: [defaultOptions],
schema: [
Expand Down
1 change: 0 additions & 1 deletion src/rules/feature-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export const featureImports = createRule<VueModularRuleModule>({
docs: {
description: 'Features should only import from shared layer or their own internal files',
category: 'Dependency',
recommended: false,
},
defaultOptions: [defaultOptions],
schema: [
Expand Down
1 change: 0 additions & 1 deletion src/rules/feature-index-required.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export const featureIndexRequired = createRule<VueModularRuleModule>({
docs: {
description: 'Require a feature public API file features/{feature}/index.ts when a feature contains files',
category: 'File Organization',
recommended: false,
},
defaultOptions: [defaultOptions],
schema: [
Expand Down
Loading