Skip to content

Layer System

Kimesh's layer system allows you to build modular applications by sharing routes, components, and composables across projects.

Overview

Layers are self-contained modules that can extend your application with:

  • Routes (placed in correct directory structure)
  • Components (with auto-prefixing)
  • Composables
  • CSS files

How Layers Work

Routes in layers must be placed in the correct directory structure matching their real URL path. For example, a blog layer with routes at /blog/* should have:

blog-layer/
├── routes/
│   ├── blog.vue           # Layout for /blog
│   └── blog/
│       ├── index.vue      # /blog
│       └── $postId.vue    # /blog/:postId
└── kimesh.config.ts

This ensures routes are explicit and predictable - no magic path prefixing.

Priority System

Layers are resolved with a priority system:

PrioritySourceDescription
0AppYour application (highest priority)
1+extends arrayLayers in kimesh.config.ts (order preserved)
N+layers/ directoryAuto-scanned layers (alphabetical)

Higher priority layers override lower priority ones when conflicts occur.

Configuring Layers

Using extends Array

Add layers in your kimesh.config.ts:

ts
import { defineKmConfig } from '@kimesh/kit'

export default defineKmConfig({
  extends: [
    // Local layer (relative path)
    './layers/cms',

    // NPM package
    '@kimesh-layers/auth',

    // Workspace package
    '@company/admin-layer',
  ],
})

Auto-Scanning layers/ Directory

Kimesh automatically scans the layers/ directory:

my-app/
├── src/
│   └── routes/
├── layers/
│   ├── admin/          # Auto-discovered
│   │   └── routes/
│   │       └── admin/
│   │           └── index.vue  # /admin
│   └── cms/            # Auto-discovered
│       └── routes/
│           └── cms/
│               └── index.vue  # /cms
└── kimesh.config.ts

Layer Structure

A layer can contain any of these directories:

my-layer/
├── routes/             # Route files (directory structure = URL path)
├── components/         # Vue components
├── composables/        # Composable functions
└── kimesh.config.ts    # Layer configuration (optional)

Layer Configuration

Create a kimesh.config.ts in your layer:

ts
import { defineKmConfig } from '@kimesh/kit'

export default defineKmConfig({
  name: 'admin',

  // Route configuration
  routes: {
    folder: 'routes', // Default: 'routes'
  },

  // Component configuration
  components: {
    dirs: ['components'],
    prefix: 'Admin', // AdminButton, AdminCard, etc.
  },

  // Composable configuration
  composables: {
    dirs: ['composables'],
  },

  // CSS files to include
  css: ['css/admin.css'],
})

Source Types

Layers can come from different sources:

Local Layers

Layers within your project:

ts
extends: [
  './layers/admin',
  '../shared-layers/common',
]

NPM Packages

Published layer packages:

ts
extends: [
  '@kimesh-layers/auth',
  '@kimesh-layers/cms',
]

Workspace Packages

In a monorepo:

ts
extends: [
  '@company/layer-auth',
  '@company/layer-admin',
]

Route Structure Convention

Routes must be placed in directories matching their URL path:

# Blog layer → routes at /blog/*
blog-layer/routes/
├── blog.vue              # Layout wrapper for /blog
└── blog/
    ├── index.vue         # /blog
    ├── $slug.vue         # /blog/:slug
    └── new.vue           # /blog/new

# Admin layer → routes at /admin/*
admin-layer/routes/
├── admin.vue             # Layout wrapper for /admin
└── admin/
    ├── index.vue         # /admin
    ├── users.vue         # /admin/users
    └── settings.vue      # /admin/settings

Host Layout Integration

If your host app has a layout route that matches a layer's routes, the layer's routes will be nested under the host layout:

# Host app
host/src/routes/
├── __root.vue            # Root layout
└── blog.vue              # Host's blog layout (wraps layer routes)

# Blog layer
blog-layer/routes/
├── blog.vue              # Layer's blog layout
└── blog/
    └── index.vue         # /blog

# Result: __root.vue → host/blog.vue → layer/blog.vue → layer/blog/index.vue

Component Prefixing

Components from layers are automatically prefixed to avoid conflicts:

ts
// Layer 'admin' components become:
components/
├── Button.vue          # <AdminButton />
├── Card.vue            # <AdminCard />
└── Table.vue           # <AdminTable />

Disable prefixing:

ts
export default defineKmConfig({
  components: {
    prefix: false,
  },
})

Conflict Resolution

When multiple layers define the same route, component, or composable:

  1. Higher priority wins - App (priority 0) always wins
  2. Explicit configuration - extends array order is preserved
  3. Alphabetical fallback - Auto-scanned layers use alphabetical order

Example Conflict

# Host app defines /admin
host/src/routes/
└── admin.vue           # /admin (priority 0 - wins)

# Admin layer also defines /admin
admin-layer/routes/
└── admin/
    └── index.vue       # /admin (priority 1 - nested under host)

Layer Aliases

Each layer gets path aliases for imports:

ts
// In a component
import { useAuth } from '#layers/auth/composables/useAuth'
import AdminLayout from '#layers/admin/components/Layout.vue'

Context-Aware Alias Resolution

Kimesh provides context-aware resolution for ~/ and @/ aliases. When you import using these aliases, they resolve relative to the importing file's layer, not globally to the host app.

How It Works

ts
// In layer: my-layer/src/routes/feature.vue
import Sidebar from '~/components/Sidebar.vue'
// Without context-aware: Resolves to host/src/components/Sidebar.vue ❌
// With context-aware: Resolves to my-layer/src/components/Sidebar.vue ✅

Alias Resolution Rules

AliasResolves To
~/Layer root path
@/Layer source path (src/ if exists, else root)

Benefits

  • Self-contained layers: Layers can use standard aliases without worrying about host conflicts
  • No path rewriting: Keep familiar ~/ and @/ patterns in layer code
  • Automatic fallback: Falls back to app layer if file isn't in any layer

Virtual Module Support

The resolver also handles virtual modules (like route definitions) by detecting which layer the original file belongs to and resolving aliases accordingly.

ts
// In route definition extracted to virtual module
// Still resolves @/api/client relative to the route's layer
import { apiClient } from '@/api/client'

Creating a Layer Package

1. Create Layer Structure

my-layer/
├── routes/
│   └── my-feature/
│       └── index.vue
├── components/
│   └── MyComponent.vue
├── composables/
│   └── useMyFeature.ts
├── kimesh.config.ts
└── package.json

2. Configure package.json

json
{
  "name": "@company/my-layer",
  "version": "1.0.0",
  "exports": {
    ".": "./kimesh.config.ts"
  },
  "files": ["routes", "components", "composables", "kimesh.config.ts"]
}

3. Configure the Layer

ts
// kimesh.config.ts
import { defineKmConfig } from '@kimesh/kit'

export default defineKmConfig({
  name: 'my-layer',
  components: {
    dirs: ['components'],
  },
  composables: {
    dirs: ['composables'],
  },
})

4. Publish

bash
npm publish

API Reference

KimeshLayerConfig

ts
interface KimeshLayerConfig {
  name: string
  path: string
  routes?: {
    folder?: string // Default: 'routes'
    include?: string[]
    exclude?: string[]
  }
  components?: {
    dirs?: string[]
    prefix?: string | false
    global?: boolean
    extensions?: string[]
  }
  composables?: {
    dirs?: string[]
    extensions?: string[]
  }
  css?: string[]
}

ResolvedLayer

ts
interface ResolvedLayer {
  name: string
  path: string
  priority: number
  config: KimeshLayerConfig
  extends: string[]
  isApp: boolean
  source: 'local' | 'npm' | 'workspace'
  aliases: Record<string, string>
}

Released under the MIT License.