Sass: From Legacy @import to Modern Modular Architecture with @use and @forward
In the world of frontend development, where projects grow increasingly complex and codebases expand, efficient style management becomes critically important. Sass, as one of the leading CSS preprocessors, has undergone significant changes aimed at enhancing code modularity and maintainability. A pivotal step in this evolution was the move away from the deprecated @import directive in favor of the powerful and flexible @use and @forward.
For a long time, @import was the sole method for including Sass files, but it came with significant drawbacks: global namespace pollution, a high risk of conflicts, and code duplication. With the impending release of Dart Sass 3.0.0, @import has been officially deprecated and will be entirely removed, making the adoption of the new modular system essential for every modern developer.
The @use Directive: Isolation and Namespacing
@use is a fundamental element of Sass's new modular system. It loads a Sass file (module) and makes its contents (variables, mixins, functions) available in the current file, but isolates them within their own namespace. This is a key distinction from @import, which prevents global conflicts and makes dependencies explicit.
Basic Usage:
@use 'path/to/file';
By default, the namespace name is derived from the last part of the file path, without underscores or extensions. For example, when using @use '../helpers/colors';, variables and functions from _colors.scss will be accessible via the colors. prefix:
// helpers/_colors.scss
$primary: blue;
$secondary: gray;
// components/_button.scss
@use '../helpers/colors';
.button {
color: colors.$primary; // Accessed via the "colors" namespace
background-color: colors.$secondary;
}
Changing the Namespace with as:
For convenience or clarity, you can assign a custom name to a module using the as keyword:
@use 'helpers/colors' as myColors;
.button {
color: myColors.$primary; // Accessed via "myColors"
}
When naming namespaces, you should adhere to Sass identifier rules:
- Allowed: letters (a-z, A-Z), digits (0-9, but not at the beginning),
camelCase,PascalCase. - Disallowed: hyphens (
-), spaces, special characters, starting with a digit.
Utilizing Sass's Built-in Modules
Sass provides a set of built-in modules for common tasks such as mathematical operations, working with lists, maps, colors, and strings. These modules are imported with the sass: prefix:
@use 'sass:math';
@use 'sass:list';
@use 'sass:map';
@use 'sass:color';
@use 'sass:string';
Examples of using functions from built-in modules:
$width: math.div(100%, 3); // Division
$len: list.length((10px, 20px, 30px)); // List length
$color: color.adjust(#336699, $lightness: 20%); // Color adjustment
$index: string.index('hello world', 'world'); // Substring search
It's crucial to remember that built-in modules, like regular ones, must be explicitly imported in every file where their functions are used. They are not inherited through other imports.
Code Organization and Module Access
The Sass modular system encourages an approach where each SCSS file explicitly declares its dependencies at the beginning. This ensures transparency and simplifies refactoring. Mixins and functions defined in custom files are also available through namespaces.
Example of a mixin definition:
// helpers/_mixins.scss
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
And its usage:
// components/_card.scss
@use '../helpers/mixins';
.card {
@include mixins.flex-center;
}
Recommended Folder Structure:
A well-structured Sass project might look like this:
scss/
helpers/
_colors.scss
_variables.scss
_mixins.scss
components/
_button.scss
_card.scss
pages/
_about-page.scss
template.scss // Main build file
Example of a component file using multiple modules:
// components/_button.scss
@use '../helpers/colors' as myColors;
@use '../helpers/variables';
@use '../helpers/mixins';
.button {
background-color: myColors.$primary;
padding: variables.$spacing-md;
@include mixins.border-radius;
}
Advantages of the Modular Approach with Namespaces:
- Clarity: It's immediately clear which module a variable or mixin comes from, improving code readability.
- No Conflicts: Isolated namespaces ensure that variables and mixins with the same names in different modules will not conflict.
- Easier Refactoring: Changes in one module do not affect others, as long as their API is maintained, simplifying scaling and modification.
- IDE Support: Modern development environments work better with explicit namespaces, offering accurate autocompletion.
- Scalability: The codebase remains understandable and manageable even in very large projects.
Configuring Modules with @use ... with()
The @use ... with() directive provides a powerful mechanism for configuring modules upon import. It allows you to override variables marked as !default by passing new values directly when importing the module.
How !default Works:
A variable declared with !default uses its value only if it hasn't been set previously. If the value is already established (e.g., in another file or via @use ... with()), !default is ignored.
// helpers/_theme.scss
$bg-color: #ffffff !default; // Default value
$text-color: #333333 !default; // Can be overridden
Overriding Values with with():
You can pass new values for !default variables within the with() parentheses when importing a module:
// pages/dark-theme.scss
@use '../helpers/theme' with (
$bg-color: #1a1a1a,
$text-color: #f0f0f0
);
body {
background: theme.$bg-color; // Will be #1a1a1a
color: theme.$text-color; // Will be #f0f0f0
}
Important Notes on with():
@use ... with()only works on the first import of a specific module within a given file. All subsequent attempts to import the same module with differentwith()values will be ignored.- Many third-party Sass libraries use
!defaultfor their settings, allowing easy customization of their behavior viawith().
The @forward Directive: Crafting a Public API
@forward is a directive that allows you to re-export the contents of one or more modules, making them available to other files that import an intermediary file. It's used to create a single entry point (public API) for a set of modules, without making their contents directly accessible within the file where @forward is used.
Example of using _core.scss as a single entry point:
// _core.scss
@forward 'helpers/colors';
@forward 'helpers/variables';
@forward 'base/fonts';
@forward 'helpers/mixins';
Now, instead of importing each file individually, you can simply import _core.scss:
// pages/about-page.scss
@use '../core.scss';
.button {
color: core.$blue; // Variable from helpers/colors
border-radius: core.$border-radius-medium; // Variable from helpers/variables
@include core.flex-center; // Mixin from helpers/mixins
}
Pros and Cons of the _core.scss Approach:
- Pro: Simplifies imports in consuming files, creating a clear API for the entire project.
- Con: All forwarded elements use the
corenamespace (or your specified one), which can make it harder to determine the original file for a specific variable or mixin without inspecting_core.scss.
Internal Use of Forwarded Modules:
If the _core.scss file itself needs to use variables or mixins from the forwarded modules, it must explicitly import them using @use:
// _core.scss
@forward 'helpers/colors'; // Forwarding outwards
@use 'helpers/colors' as c; // For internal use within _core.scss
body {
background: c.$bg-color; // Works thanks to @use
}
Advanced @forward Capabilities:
- Selective Forwarding (
show/hide): Allows you to explicitly specify which elements of a module should be forwarded.
```scss
@forward 'colors' show $primary, $secondary; // Forward only these variables
@forward 'mixins' hide private-mixin; // Forward all except private-mixin
```
- Configuration via
with()when Forwarding: Similar to@use ... with(), you can override!defaultvariables in the forwarded module.
```scss
// helpers/_theme.scss
$bg-color: #fff !default;
// _index.scss (file that will forward _theme.scss)
@forward 'theme' with (
$bg-color: #f0f0f0
);
```
Managing Module Dependencies
In the Sass modular system, each file must explicitly declare its dependencies. This applies even when one of your modules (e.g., _variables.scss) uses variables from another module (_colors.scss). Even if _variables.scss and _colors.scss are both forwarded through _core.scss, _variables.scss must still explicitly import _colors.scss if it uses its contents.
// _variables.scss
@use 'colors'; // Explicit dependency on _colors.scss
$button-bg: colors.$primary;
Why This is Important:
- Clarity: It's immediately clear which modules each file depends on, simplifying understanding of the structure.
- Reliability: When files are moved or reorganized, dependencies remain intact and understandable.
- IDE Support: Static analysis tools and autocompletion work more accurately.
- Refactoring: It's easy to track where specific modules and variables are used.
Key Takeaways
- @use and @forward replace the deprecated
@import, offering true modularity in Sass. - Namespaces prevent conflicts and make dependencies explicit, improving scalability.
- @use ... with() allows flexible module configuration by overriding variables marked
!default. - @forward is used to create single entry points (public APIs), simplifying the management of a large number of modules.
- Explicitly declaring dependencies in each file is critical for the maintainability and predictability of Sass code.
— Editorial Team
No comments yet.