Skip to main content
Version: 6.0.0

Migrating from v5.x to v6.x

Overview

Version 6.0.0 introduces object-based selectors as the recommended way to define element selectors. This provides better readability and access to advanced matching features.

It also introduces several new features and changes in configuration to make it more semantic and powerful. But almost every change is designed to be incremental and non-breaking, allowing you to migrate at your own pace. The only breaking change is the new default value for boundaries/dependency-nodes.

Main changes include:

You will receive warnings in your console about how to prepare your configuration for next versions removing support for legacy syntax. This guide will walk you through the migration process step by step.

Backwards Compatibility

Legacy selector formats and other deprecation warnings are still supported in v6.x but will show a warning in your console.

Here is the list of changes and how to migrate your configuration for each one.

Object-based elements selector syntax

The following elements selector formats are now deprecated:

  1. String format: "helper"
  2. Tuple format: ["helper", { family: "data" }]
  3. Array of legacy selectors: ["helper", "component"]

The new object-based syntax provides:

  • Modern format: { type: "helper", captured: { family: "data" } }
  • Clear property names: Self-documenting code
  • Advanced features: Access to new matching properties
  • Better composition: Combine multiple properties easily
  • Future-proof: New capabilities will only be added to object-based syntax
info

Read more about the new object-based selector syntax in the selectors documentation.

Conversion table

Legacy FormatObject-Based Format
"type"{ type: "type" }
["type", { capturedProp: "value" }]{ type: "type", captured: { capturedProp: "value" } }
"*-pattern"{ type: "*-pattern" }
{
rules: {
"boundaries/dependencies": [2, {
default: "disallow",
rules: [
{
from: "helper",
from: { type: "helper" }
},
{
from: [["helper", { family: "data" }]],
from: [{ type: "helper", captured: { family: "data" } }]
}
]
}]
}
};

Dependencies selector syntax

V6 introduces a new format to select dependencies in allow and disallow rules, enabling to match dependencies based on their relationship, the file origin, the target file, and more.

For the migration, the main changes are:

to property in allow/disallow rules is now required

Now allow and disallow must match dependencies using dependency selectors, not just elements: In the practice, this means that allow and disallow rules must be defined with a to property that specifies the dependency selector for proper migration.

info

Read more about the new dependency selector syntax in the selectors documentation.

Conversion table

Legacy FormatObject-Based Format
allow: ["helper"]allow: [{ to: { type: "helper" } }]
{
rules: {
"boundaries/dependencies": [2, {
default: "disallow",
rules: [
{
from: "helper",
from: { type: "helper" },
allow: [
"helper",
{ to: { type: "helper" } }
],
}
]
}]
}
};

Rule element-types renamed to dependencies

The boundaries/element-types rule has been renamed to boundaries/dependencies to better reflect its purpose of defining boundaries between elements based on their dependencies. The new name is more intuitive and consistent.

To migrate, simply change the rule name in your configuration:

{
rules: {
"boundaries/element-types": [2, {
"boundaries/dependencies": [2, {
// ...rule configuration
}]
}
};
info

Read more about this change in the GitHub discussion here

Changed default value for boundaries/dependency-nodes setting

The default value for the boundaries/dependency-nodes setting is now ["import", "export", "require", "dynamicImport"], which means that by default the plugin will analyze all types of dependencies. Previously, only import statements were analyzed by default.

BREAKING CHANGE

This means that if you were relying on the default value of boundaries/dependency-nodes to only analyze import statements, now your rules will also apply to other types of dependencies such as export * from, require(), and import(). This may cause new violations to be reported in your codebase that were not reported before.

If you want to keep the previous behavior and only analyze import statements, you should set boundaries/dependency-nodes to ["import"] in your configuration.

{
// ...
settings: {
"boundaries/dependency-nodes": ["import"] // Only analyze import statements
}
}

Rule-level importKind is deprecated

Rule-level importKind is deprecated: Use selector-level dependency.kind instead. When both are defined, selector-level dependency.kind takes precedence. This allows for more granular control over matching specific dependency kinds within the same rule.

Conversion table

Legacy FormatObject-Based Format
importKind: "value" at rule leveldependency: { kind: "value" } inside the selector
{
rules: {
"boundaries/dependencies": [2, {
default: "disallow",
rules: [
{
from: "helper",
from: { type: "helper" },
importKind: "value"
allow: [
"helper",
{
to: { type: "helper" },
dependency: { kind: "value" }
}
],
}
]
}]
}
};
note

This does not apply to the entry-point rule, because the "allow" and "disallow" properties in that rule are not used to match dependencies but to define the allowed paths to be used as entry points. In that rule, the importKind property is still supported. Anyway, that entire rule is also deprecated and will be removed in oncoming versions, so it is recommended to migrate to the boundaries/dependencies rule with the new selector syntax as soon as possible. You will find a migration example for that rule in its documentation page.

New template syntax

V6 introduces a new Handlebars-style template syntax ({{...}}) for selectors and custom messages, providing clearer syntax and structured access to element descriptions properties. The old ${ } syntax is still supported for backwards compatibility but will be deprecated in future versions.

The modern template syntax uses Handlebars-style double curly braces:

  • {{ from.* }} - References properties from the file being analyzed (the importer)
  • {{ to.* }} - References properties from the dependency being imported
  • {{ dependency.* }} - References properties from the dependency itself, such as the kind of import, the relationship between both elements, etc.

Templates in selectors

The new template syntax can access any property from the dependency description, while the old ${to.capturedProperty} syntax could access only captured properties.

tip

Read more about the description properties in the Runtime Description Properties documentation.

Conversion table

Legacy FormatHandlebars Format
${from.capturedProperty}{{ from.captured.capturedProperty }}
${to.capturedProperty}{{ to.captured.capturedProperty }}
${target.capturedProperty}{{ to.captured.capturedProperty }}
{
rules: {
"boundaries/dependencies": [2, {
default: "disallow",
rules: [
{
from: [["helper", { family: "${ from.family }" }]],
from: [{ type: "helper", captured: { family: "{{ from.captured.family }}" } }]
}
]
}]
}
};
caution

When the new boundaries/legacy-templates setting is enabled (default until next major version), if you are capturing properties with names equal to any of the Runtime Element Description Properties (e.g., path, category, origin, etc.) they will overwrite the corresponding template variables and cause unexpected behavior in the templates.

To avoid this, it is recommended to check your captured properties and set boundaries/legacy-templates to false, which will avoid injecting captured properties at first level object and instead require using the captured namespace to access them.

Templates in custom messages

The new template syntax ({{ }}) can access any property from the dependency description, while the old ${ } syntax could access only:

  • ${file.*} and ${dependency.*}:
    • type - Element type
    • internalPath - Path inside the element
    • source - Import source
    • importKind - Import kind (value, type, typeof)
    • All captured values from the element pattern
  • ${from.*} and ${target.*} - Alternative aliases for ${file.*} and ${dependency.*} respectively.
  • ${file.parent.*}, ${from.parent.*}, ${dependency.parent.*}, ${target.parent.*} - Parent element properties
  • ${report.*} - Rule-specific metadata (when provided by the rule)

For backward compatibility, message rendering runs in two phases:

  1. Legacy replacement (${...}): Processes legacy template syntax with flattened property access
  2. Handlebars rendering ({{...}}): Processes modern template syntax with nested object access

This allows existing templates to continue working while you migrate to the new format.

Conversion table

Legacy FormatHandlebars Format
${file.type} / ${from.type} / ${dependency.type} / ${target.type}{{ from.type }}
${file.internalPath} / ${from.internalPath} / ${dependency.internalPath} / ${target.internalPath}{{ from.internalPath }}
${file.source} / ${from.source} / ${dependency.source} / ${target.source}{{ dependency.source }}
${dependency.importKind} / ${target.importKind}{{ dependency.kind }}
${file.parent.type} / ${from.parent.type}{{ from.parents.0.type }}
${dependency.parent.type} / ${target.parent.type}{{ to.parents.0.type }}
${file.capturedProperty} / ${from.capturedProperty}{{ from.captured.capturedProperty }}
${dependency.capturedProperty} / ${target.capturedProperty}{{ to.captured.capturedProperty }}
${report.specifiers}{{ dependency.specifiers }}
${report.path}{{ to.internalPath }}
"${file.type} cannot import ${dependency.type}"
"{{from.type}} cannot import {{to.type}}"
"${file.type} with name ${file.elementName} cannot import ${dependency.domain}"
"{{from.type}} with name {{from.captured.elementName}} cannot import {{to.captured.domain}}"
"${file.type} in ${file.parent.type} cannot import ${dependency.type}"
"{{from.type}} in {{from.parents.0.type}} cannot import {{to.type}}"
tip

For more details on the template messages migration, see the Legacy Message Templates section in the rules documentation.

Deprecated Rules

entry-point rule

The boundaries set by this rule now can also be achieved with the boundaries/dependencies rule, which allows you to specify allowed entry points directly in the rules by using the new internalPath selector property.

Here you have an example of how to migrate a configuration from boundaries/entry-point to boundaries/dependencies:

{
rules: {
"boundaries/entry-point": [2, {
"boundaries/dependencies": [2, {
default: "disallow",
rules: [
{
target: ["component", "module"],
allow: "index.js",
importKind: "value"
}
{
to: {
type: ["component", "module"],
internalPath: "!index.js"
},
disallow: {
from: {
type: "*",
}
}
},
]
}]
}
}
Bonus: Allowing different entry points based on the importer element

With boundaries/dependencies you can also define rules to allow different entry points depending on the importer element, which was not possible with the entry-point rule. For example, you could allow importing index.js from components but only allow importing main.js from modules.

external rule

The boundaries set by this rule can also be achieved with the boundaries/dependencies rule, which allows you to specify allowed entry points directly in the rules by using the origin, source, module, 'internalPath' and specifiers selector properties.

warning

You need to set the checkAllOrigins option to true in your boundaries/dependencies configuration to make sure that dependencies from external origins are also checked, as by default boundaries/dependencies only checks dependencies between local known elements.

Here you have an example of how to migrate a configuration from boundaries/external to boundaries/dependencies.

{
rules: {
"boundaries/external": [2, {
"boundaries/dependencies": [2, {
checkAllOrigins: true,
default: "disallow",
rules: [
{
from: "helper",
allow: ["moment"],
importKind: "type"
}
{
from: { type: "helper" },
allow: {
to: {
origin: "external",
},
dependency: {
module: "moment",
kind: "type"
},
}
}
{
from: "helper",
allow: [
"@mui/material",
{
path: "Autocomplete",
specifiers: ["useAutocomplete"]
}
]
}
{
from: { type: "helper" },
allow: {
to: {
origin: "external",
internalPath: "Autocomplete",
},
dependency: {
module: "@mui/material",
specifiers: ["useAutocomplete"]
},
}
}
]
}]
}
}

no-private rule

The boundaries set by this rule can also be achieved with the boundaries/dependencies rule, which allows you to specify allowed entry points directly in the rules by using the relationship selector property.

{
rules: {
"boundaries/no-private": [2, { allowUncles: true }]
"boundaries/dependencies": [2, {
default: "allow",
rules: [
// Disallow importing any element with any kind of parent
{
disallow: {
to: {
parent: {
type: "*"
},
},
}
},
// Allow all elements importing their own children, siblings, and uncles
{
allow: {
dependency: {
relationship: {
to: ["child", "sibling", "uncle"],
}
},
}
}
]
}]
}
}
Bonus: More specific rules with boundaries/dependencies

With boundaries/dependencies you can also define more specific rules that were not possible with no-private, such as allowing only certain elements to import their siblings or uncles, while disallowing it for others.

The boundaries/no-private rule is now disabled in the recommended configuration, as its boundaries can be achieved with the boundaries/dependencies rule with more flexibility and better syntax. If you were relying on that rule in your configuration without explicitly enabling it, you can enable it again with the old syntax or, better, migrate to the new boundaries/dependencies rule with the new selector syntax for better readability and more powerful rules.

{
// ...
rules: {
// Old syntax (deprecated)
"boundaries/no-private": [2, { allowUncles: true }],
}
}

Why Migrate?

Benefits of Object-Based Selectors

The modern object-based selector syntax provides several benefits over the legacy formats:

  • Better readability - Object properties are self-documenting
  • Advanced features - Access to properties like origin, path, internalPath, and more
  • Type safety - Better TypeScript support and IDE autocompletion
  • Future-proof - New features will only be added to object-based syntax

These benefits make it easier to write and maintain your configuration, and to take advantage of new features as they are added in future releases. It is much more powerful and flexible, allowing you to define complex matching logic that was not possible with the legacy formats.

Benefits of the new template syntax

The new template syntax also provides clearer and more structured access to all the properties of the elements and dependencies.

  • Access to all properties - Not limited to captured properties, can access any property from the element or dependency description
  • Structured access - Clear distinction between from, to, and dependency properties
  • Future-proof - New properties will only be added to the new template syntax, ensuring you can take advantage of new features as they are added in future releases.

Benefits of stop using legacy rules

Using the boundaries/dependencies rule with the new selector syntax instead of the deprecated entry-point, external, and no-private rules allows you to:

  • Unified configuration - Define all your boundaries in a single rule with consistent syntax
  • More powerful rules - Access to advanced selector features for more granular control over your boundaries

Need Help?