Overview
JS Boundaries is a project that provides a set of tools to help you enforce architectural boundaries in your JavaScript and TypeScript projects.
"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:
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.
This plugin is not a replacement for eslint-plugin-import. In fact, using both together is recommended.
Quick Start
Read the Quick Start Guide for step-by-step instructions on setting up the plugin in your project.