Skip to main content
Version: 6.0.0

Overview

JS Boundaries is a project that provides a set of tools to help you enforce architectural boundaries in your JavaScript and TypeScript projects.


Robert C. Martin's quote

"Software architecture is the art of drawing lines that I call boundaries. Those boundaries separate software elements from one another, and restrict those on one side from knowing about those on the other."

Clean Architecture: A Craftsman's Guide to Software Structure and Design


Purpose

It ensures that your architectural boundaries are respected by the elements in your project by checking the folder and file structure and the dependencies between them. At the moment, it consists of an ESLint plugin: eslint-plugin-boundaries.

How It Works

By default, it analyzes import statements, but it can also evaluate require, exports and dynamic imports (import()). You can further customize it to inspect any other AST node that creates a dependency, such as jest.mock(). See the configuration guide for more details.

Usage

1. Define the Elements in Your Project through Configuration

const elementDescriptors = [
{ type: "controller", pattern: "controllers/*" },
{ type: "model", pattern: "models/*" },
{ type: "view", pattern: "views/*" },
{ type: "shared", pattern: "shared/*" },
];

2. The Plugin Provides Descriptions for Each Dependency

Given this configuration, the plugin will analyze your project in runtime and classify dependencies, providing lots of useful metadata about the files and their relationships. For example:

// When analyzing a dependency in src/controllers/controller-a.js
{
from: {
path: "src/controllers/controller-a.js",
type: "controller",
category: null,
captured: { elementName: "controller-a" },
origin: "local",
},
to: {
path: "src/views/view-a.js",
type: "view",
category: null,
captured: { elementName: "view-a" },
origin: "local",
},
dependency: {
kind: "value",
source: "@views/view-a.js",
specifiers: ["ViewA"],
}
}

3. Define your Rules Based on These Descriptions

Based on these descriptions, you can define rules to allow or disallow dependencies between elements using selectors. For example:

Architecture Boundaries Diagram

const dependencyRules = [
// Allow controllers to depend on models and views
{
from: {
type: "controller",
},
allow: {
to: { type: ["model", "view"] },
},
},
// Allow views to depend on models
{
from: {
type: "view",
},
allow: {
to: { type: "model" },
},
},
// Disallow models to depend on anything other than other models
{
from: {
type: "model",
},
disallow: {
to: { type: "!model" },
},
},
// Allow any file to depend on other files of the same element
{
allow: {
dependency: {
relationship: {
to: "internal",
},
}
},
},
// Allow any file to depend on shared files,
// but only if it's a type dependency (e.g. TypeScript type imports)
{
allow: {
to: { type: "shared" },
dependency: {
kind: "type",
},
},
},
];

4. Get Instant Feedback

When a file violates a dependencies rule, ESLint will report an error:

// In src/models/model-a.js

import View from '../views/view-a';

/* ❌ Error: Importing elements of type 'views'
is not allowed in elements of type 'models'.
Disallowed in rule 3 */

Scope

This plugin focuses on enforcing architectural boundaries by analyzing the relationships between abstract elements. It does not inspect import syntax or enforce coding standards unrelated to module dependencies.

note

This plugin is not a replacement for eslint-plugin-import. In fact, using both together is recommended.

Quick Start

tip

Read the Quick Start Guide for step-by-step instructions on setting up the plugin in your project.