Library System Deep Dive
apps/docs/src/content/docs/drupal/library-system Click to copy apps/docs/src/content/docs/drupal/library-system Drupal’s asset library system is the foundation for loading and managing HELiX web components in your Drupal site. This deep dive covers the complete architecture, from basic library definitions to advanced optimization strategies and tree-shaking patterns.
Overview: The Drupal Library System
Section titled “Overview: The Drupal Library System”Drupal’s library system provides a declarative way to define, version, and load CSS and JavaScript assets. Libraries are defined in YAML files and attached to pages through themes, modules, or render arrays. This system enables:
- Dependency management — Automatic loading of required libraries
- Version control — Cache-busting and compatibility tracking
- Aggregation — Combining files for production performance
- Conditional loading — Load assets only when needed
- Weight ordering — Control execution order of scripts
For HELiX web components, the library system handles:
- Loading ES modules with proper MIME types
- Managing dependencies between components
- Enabling tree-shaking for optimal bundle sizes
- Providing version control for component updates
- Supporting both CDN and npm distribution strategies
The .libraries.yml File
Section titled “The .libraries.yml File”Every Drupal theme and module can define libraries in a THEMENAME.libraries.yml or MODULENAME.libraries.yml file in the theme/module root directory.
Basic Structure
Section titled “Basic Structure”library-name: version: 1.0.0 js: path/to/file.js: {} css: theme: path/to/file.css: {} dependencies: - core/drupalKey components:
- Library name — Unique identifier within the theme/module namespace
- Version — Used for cache-busting and dependency tracking
- js — JavaScript file definitions
- css — CSS file definitions (categorized by type: base, layout, theme, component, state)
- dependencies — Other libraries that must load first
File Path Resolution
Section titled “File Path Resolution”File paths in .libraries.yml are relative to the theme/module root:
helix-button: js: # Relative to theme root: themes/custom/mytheme/dist/js/hx-button.js dist/js/hx-button.js: {}For external URLs (CDN):
helix-cdn: js: https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/core.js: type: externalLibrary Definition Syntax
Section titled “Library Definition Syntax”JavaScript Assets
Section titled “JavaScript Assets”Basic JavaScript file:
library-name: js: js/script.js: {}With options:
library-name: js: js/script.js: minified: true preprocess: false attributes: defer: trueES Module (required for HELiX components):
helix-component: js: dist/js/hx-button.js: attributes: type: moduleCritical: Web components built with Lit require type: module to load as ES modules. Without this attribute, the browser will fail to parse import statements.
CSS Assets
Section titled “CSS Assets”CSS files are categorized by purpose:
library-name: css: base: css/reset.css: {} layout: css/grid.css: {} component: css/button.css: {} theme: css/colors.css: {} state: css/print.css: { media: print }Categories determine load order:
base— Resets, normalizerslayout— Grid systems, structural CSScomponent— Component-specific stylestheme— Visual styling, colors, typographystate— Print styles, accessibility overrides
External Assets
Section titled “External Assets”For CDN-hosted files:
helix-cdn: js: https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/core.js: type: external attributes: type: module crossorigin: anonymousExternal asset options:
type: external— Required for all external URLsminified: true— Marks file as already minified (skip aggregation)attributes— HTML attributes for the<script>or<link>tag
Dependencies Between Libraries
Section titled “Dependencies Between Libraries”Dependencies ensure libraries load in the correct order.
Basic Dependency
Section titled “Basic Dependency”helix-button: js: dist/js/hx-button.js: attributes: type: module dependencies: - core/once - mytheme/helix-coreSyntax: namespace/library-name
core/*— Drupal core librariesthemename/*— Theme librariesmodulename/*— Module libraries
Dependency Resolution
Section titled “Dependency Resolution”Drupal resolves dependencies recursively:
helix-core: js: dist/js/helix-core.js: attributes: type: module
helix-button: js: dist/js/hx-button.js: attributes: type: module dependencies: - mytheme/helix-core
helix-form: js: dist/js/hx-form.js: attributes: type: module dependencies: - mytheme/helix-button # Transitively loads helix-coreLoad order when helix-form is attached:
helix-core.jshx-button.jshx-form.js
Common Core Dependencies
Section titled “Common Core Dependencies”Useful Drupal core libraries for HELiX integration:
helix-behaviors: js: js/helix-behaviors.js: {} dependencies: - core/drupal # Drupal global object - core/drupalSettings # Settings from PHP - core/once # Run-once utility - core/jquery # jQuery (if needed)Best practice: Minimize dependencies on core/jquery. Modern web components don’t require jQuery.
Version and Cache Control
Section titled “Version and Cache Control”Version Property
Section titled “Version Property”helix-button: version: 3.9.0 js: dist/js/hx-button.js: attributes: type: moduleVersion affects:
- Cache-busting query strings (
hx-button.js?v=3.9.0) - Cache and dependency-graph metadata (the version contributes to cache hashes and library replacement keys; Drupal’s libraries API does not perform semver dependency-compatibility checks)
- Library replacement (modules can replace libraries with specific versions)
Dynamic Versioning
Section titled “Dynamic Versioning”Drupal’s Libraries API substitutes the VERSION placeholder with the Drupal core version
(see drupal.org docs on libraries.yml), not the theme/module version. Use it when the asset’s
cache-bust should track Drupal core; for theme-scoped versioning, write the literal version
string yourself:
helix-button: version: VERSION # Substituted with Drupal core version (e.g. 10.4.0) js: dist/components/hx-button/index.js: attributes: type: module
helix-button-theme-versioned: version: '3.9.0' # Literal — track this library's version explicitly js: dist/components/hx-button/index.js: attributes: type: moduleDisabling Version Query Strings
Section titled “Disabling Version Query Strings”For CDN assets with their own versioning:
helix-cdn: js: https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/core.js: type: external version: -1 # Disable query string attributes: type: moduleWeight and Load Order
Section titled “Weight and Load Order”Control execution order with weight:
helix-init: js: js/helix-init.js: weight: -10 # Load early attributes: type: module
helix-behaviors: js: js/helix-behaviors.js: weight: 0 # Default weight dependencies: - mytheme/helix-initWeight conventions: Drupal accepts any integer (positive or negative). Negative weights load earlier; positive weights load later. The exact band you pick matters less than being consistent with the rest of your theme — Drupal core libraries hover around 0, and theme-level overrides typically land in the ±10 range. Reserve very negative values (e.g. < -50) for polyfills that genuinely must precede everything else.
Best practice: Prefer dependencies over weight for ordering. Dependencies are explicit
and easier to reason about; reach for weight only when you need fine-grained ordering inside a
single dependency cohort.
Attributes and Extra Options
Section titled “Attributes and Extra Options”Script Attributes
Section titled “Script Attributes”helix-component: js: dist/js/hx-button.js: attributes: type: module async: true crossorigin: anonymous integrity: sha384-...Common attributes:
type: module— ES module (required for HELiX)defer: true— Defer execution until DOM readyasync: true— Load asynchronously (use with caution for modules)crossorigin: anonymous— CORS mode for CDN assetsintegrity: ...— Subresource Integrity (SRI) hash
Preprocess Flag
Section titled “Preprocess Flag”helix-component: js: dist/js/hx-button.js: preprocess: false # Do not aggregate attributes: type: moduleWhen to set preprocess: false:
- ES modules (Drupal aggregation doesn’t support ES modules)
- Already-minified files
- CDN assets
- Files that break when concatenated
HELiX rule: Always set preprocess: false for web component files. ES modules cannot be safely aggregated.
Minified Flag
Section titled “Minified Flag”helix-component: js: dist/js/hx-button.min.js: minified: true preprocess: false attributes: type: moduleminified: true tells Drupal the file is already minified, so it skips minification during aggregation.
Tree-Shaking Pattern (Per-Component Libraries)
Section titled “Tree-Shaking Pattern (Per-Component Libraries)”For optimal performance, define one library per component. This enables tree-shaking: only load components actually used on a page.
Full-Bundle Approach (Simple, Larger)
Section titled “Full-Bundle Approach (Simple, Larger)”helix-all: version: 3.9.0 js: https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/core.js: type: external minified: true preprocess: false attributes: type: modulePros:
- Simple single-library setup
- One HTTP request
- All components available globally
Cons:
- Loads all components even if unused
- Larger initial bundle size
- No tree-shaking benefits
Per-Component Approach (Optimized, Recommended)
Section titled “Per-Component Approach (Optimized, Recommended)”helix-button: version: 3.9.0 js: dist/js/hx-button.js: preprocess: false attributes: type: module
helix-card: version: 3.9.0 js: dist/js/hx-card.js: preprocess: false attributes: type: module
helix-text-input: version: 3.9.0 js: dist/js/hx-text-input.js: preprocess: false attributes: type: module dependencies: - mytheme/helix-form-base # Shared form utilitiesPros:
- Load only components used on the page
- Smaller initial bundles
- Better caching (component updates don’t bust all caches)
- Explicit dependencies between components
Cons:
- More HTTP requests (mitigated by HTTP/2)
- More complex library definitions
- Requires build pipeline for per-component bundles
Hybrid Approach (Practical)
Section titled “Hybrid Approach (Practical)”# Core utilities and shared dependencieshelix-core: version: 3.9.0 js: dist/js/helix-core.js: preprocess: false attributes: type: module
# Common component bundle (buttons, badges, alerts)helix-common: version: 3.9.0 js: dist/js/helix-common.js: preprocess: false attributes: type: module dependencies: - mytheme/helix-core
# Heavy components loaded on-demandhelix-data-table: version: 3.9.0 js: dist/js/hx-data-table.js: preprocess: false attributes: type: module dependencies: - mytheme/helix-core
helix-data-table: version: 3.9.0 js: dist/components/hx-data-table/index.js: preprocess: false attributes: type: module dependencies: - mytheme/helix-core(hx-chart is not a shipped HELiX component — substitute whichever heavy component your theme
needs to load on demand, e.g. hx-data-table, hx-date-picker, or hx-combobox.)
Strategy:
helix-core— Shared base (design tokens, utilities)helix-common— Frequently-used components (bundle together)- Individual libraries for heavy/specialized components
Library Loading Strategies
Section titled “Library Loading Strategies”Strategy 1: Global Theme Library
Section titled “Strategy 1: Global Theme Library”Load HELiX on every page via theme attachment:
name: My Themetype: themecore_version_requirement: ^10 || ^11libraries: - mytheme/helix-common # Loaded on every pageUse when:
- Components are used site-wide (headers, footers, navigation)
- Consistent page-to-page experience
- Small bundle size (<50KB)
Strategy 2: Conditional Attachment
Section titled “Strategy 2: Conditional Attachment”Load HELiX only on specific pages/content types:
/** * Implements hook_preprocess_node(). */function mytheme_preprocess_node(&$variables) { // Attach HELiX card library only for article nodes if ($variables['node']->getType() === 'article') { $variables['#attached']['library'][] = 'mytheme/helix-card'; }}Use when:
- Components are page-specific (dashboards, forms)
- Heavy components not needed globally
- Optimizing for page speed
Strategy 3: Component-Driven Attachment
Section titled “Strategy 3: Component-Driven Attachment”Attach libraries via Twig templates:
{# templates/components/card.html.twig #}{{ attach_library('mytheme/helix-card') }}
<hx-card variant="{{ variant }}" elevation="{{ elevation }}"> <span slot="heading">{{ title }}</span> {{ body }}</hx-card>Use when:
- Using Single Directory Components (SDC)
- Component-level encapsulation
- Template-driven architecture
Strategy 4: Render Array Attachment
Section titled “Strategy 4: Render Array Attachment”Attach libraries programmatically in render arrays:
// In a controller or custom block plugin
$build = [ '#type' => 'container', '#attached' => [ 'library' => [ 'mytheme/helix-button', 'mytheme/helix-card', ], ], '#markup' => '<hx-button variant="primary">Click Me</hx-button>',];
return $build;Use when:
- Building render arrays in PHP
- Custom blocks or controllers
- Dynamic library attachment based on conditions
Performance Optimization
Section titled “Performance Optimization”HTTP/2 Considerations
Section titled “HTTP/2 Considerations”With HTTP/2 multiplexing, multiple small files are often faster than one large bundle:
# Optimized for HTTP/2 — many small librarieshelix-button: js: dist/js/hx-button.js: { preprocess: false, attributes: { type: module } }
helix-card: js: dist/js/hx-card.js: { preprocess: false, attributes: { type: module } }
helix-badge: js: dist/js/hx-badge.js: { preprocess: false, attributes: { type: module } }Benefits:
- Parallel downloads
- Better caching granularity
- True tree-shaking
Requirements:
- HTTP/2 enabled on server
- CDN with HTTP/2 support
- Modern browser (all evergreens support HTTP/2)
Preload Critical Components
Section titled “Preload Critical Components”Use resource hints for critical components:
helix-button: js: dist/js/hx-button.js: preprocess: false attributes: type: module header: true # Add to <head> instead of before </body>Or via html.html.twig:
<link rel="modulepreload" href="/themes/custom/mytheme/dist/js/hx-button.js">Cache Strategy
Section titled “Cache Strategy”Drupal’s library system integrates with its cache system:
-
Development — Disable caching (
sites/default/services.yml)parameters:twig.config:debug: trueauto_reload: truecache: false -
Production — Enable aggregation and caching
- Admin → Performance → “Aggregate JavaScript files”
- Set
versionin.libraries.ymlto bust caches on updates
-
CDN — Use versioned URLs for immutable caching
helix-cdn:js:https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/core.js:type: externalminified: true
Bundle Size Monitoring
Section titled “Bundle Size Monitoring”Monitor per-component bundle sizes:
# In your theme build processls -lh dist/js/hx-*.js | awk '{print $5, $9}'HELiX targets:
- <5KB per component (minified + gzipped)
- <50KB total bundle (all components)
Complete Examples
Section titled “Complete Examples”Example 1: hx-button Library (CDN)
Section titled “Example 1: hx-button Library (CDN)”helix-button-cdn: version: 3.9.0 js: https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/components/hx-button.js: type: external minified: true preprocess: false attributes: type: module crossorigin: anonymousUsage in Twig:
{{ attach_library('mytheme/helix-button-cdn') }}
<hx-button variant="primary" hx-size="lg"> Submit Form</hx-button>Example 2: hx-button Library (npm Build)
Section titled “Example 2: hx-button Library (npm Build)”helix-button: version: VERSION # Uses theme version from .info.yml js: dist/js/hx-button.js: minified: true preprocess: false attributes: type: module dependencies: - core/onceTheme build process (package.json):
{ "scripts": { "build": "vite build", "dev": "vite build --watch" }, "dependencies": { "@helixui/library": "^3.9.0" }}Vite config (vite.config.js):
import { defineConfig } from 'vite';
export default defineConfig({ build: { outDir: 'dist/js', lib: { entry: { // The published @helixui/library package only ships the // compiled `dist/` tree — there is no `src/` in the npm // tarball, and the canonical per-component entry uses the // package's `./components/<tag>` subpath export rather than // a node_modules path. 'hx-button': 'node_modules/@helixui/library/dist/components/hx-button/index.js', }, formats: ['es'], }, rollupOptions: { external: ['lit'], }, },});If you’re consuming through the package’s exports map (recommended), prefer:
import '@helixui/library/components/hx-button';Example 3: hx-card Library (Full-Featured)
Section titled “Example 3: hx-card Library (Full-Featured)”helix-card: version: 3.9.0 js: dist/js/hx-card.js: minified: true preprocess: false weight: 0 attributes: type: module dependencies: - mytheme/helix-core - core/onceWith Drupal Behaviors:
helix-card: version: 3.9.0 js: dist/js/hx-card.js: minified: true preprocess: false attributes: type: module js/helix-card-behavior.js: {} # Drupal behavior dependencies: - mytheme/helix-core - core/drupal - core/onceBehavior file (js/helix-card-behavior.js):
(function (Drupal, once) { 'use strict';
Drupal.behaviors.helixCard = { attach(context) { once('helix-card-init', 'hx-card[hx-href]', context).forEach((card) => { card.addEventListener('hx-click', (e) => { const { url } = e.detail;
// Optional: Integrate with Drupal's AJAX system if (card.hasAttribute('data-use-ajax')) { e.preventDefault(); // Trigger AJAX request Drupal.ajax({ url }).execute(); } else { // Normal navigation window.location.href = url; } }); }); }, };})(Drupal, once);Twig template usage:
{# templates/content/node--article--teaser.html.twig #}{{ attach_library('mytheme/helix-card') }}
<hx-card variant="featured" elevation="raised" href="{{ url }}"> <img slot="image" src="{{ content.field_image }}" alt="{{ content.field_image.alt }}"> <span slot="heading">{{ label }}</span> {{ content.body }} <div slot="footer"> <time datetime="{{ node.createdtime }}">{{ node.createdtime|date('M j, Y') }}</time> </div></hx-card>Example 4: Complete Theme Library Set
Section titled “Example 4: Complete Theme Library Set”# Core design tokens and utilitieshelix-core: version: 3.9.0 js: dist/js/helix-core.js: minified: true preprocess: false attributes: type: module
# Common UI components (bundled for performance)helix-common: version: 3.9.0 js: dist/js/helix-common.js: minified: true preprocess: false attributes: type: module dependencies: - mytheme/helix-core - core/once
# Individual form componentshelix-text-input: version: 3.9.0 js: dist/js/hx-text-input.js: minified: true preprocess: false attributes: type: module dependencies: - mytheme/helix-core
helix-select: version: 3.9.0 js: dist/js/hx-select.js: minified: true preprocess: false attributes: type: module dependencies: - mytheme/helix-core
helix-checkbox: version: 3.9.0 js: dist/js/hx-checkbox.js: minified: true preprocess: false attributes: type: module dependencies: - mytheme/helix-core
# Form bundle (all form components)helix-forms: dependencies: - mytheme/helix-text-input - mytheme/helix-select - mytheme/helix-checkbox - mytheme/helix-radio-group - mytheme/helix-textarea - mytheme/helix-switch
# Heavy components (load on-demand)helix-data-table: version: 3.9.0 js: dist/js/hx-data-table.js: minified: true preprocess: false attributes: type: module dependencies: - mytheme/helix-coreConditional attachment in theme:
function mytheme_preprocess_page(&$variables) { // Always load common components $variables['#attached']['library'][] = 'mytheme/helix-common';
// Load form components on specific paths $route_match = \Drupal::routeMatch(); if ($route_match->getRouteName() === 'node.add' || $route_match->getRouteName() === 'node.edit') { $variables['#attached']['library'][] = 'mytheme/helix-forms'; }
// Load data table on views pages if ($route_match->getRouteName() === 'view.patients.page_1') { $variables['#attached']['library'][] = 'mytheme/helix-data-table'; }}Troubleshooting
Section titled “Troubleshooting”Components Not Loading
Section titled “Components Not Loading”Symptom: <hx-button> renders as plain text, no styling
Check:
- Library attached?
{{ attach_library('mytheme/helix-button') }} type: moduleattribute present in.libraries.yml?- File path correct? Check browser DevTools Network tab
- Drupal cache cleared?
drush cr
Module Loading Errors
Section titled “Module Loading Errors”Symptom: Console error: Uncaught SyntaxError: Cannot use import statement outside a module
Fix: Add type: module attribute:
helix-button: js: dist/js/hx-button.js: attributes: type: module # Required!Dependency Load Order Issues
Section titled “Dependency Load Order Issues”Symptom: ReferenceError: Drupal is not defined
Fix: Add core/drupal dependency:
helix-behavior: js: js/helix-behavior.js: {} dependencies: - core/drupal # Ensures Drupal object is availableCache Issues
Section titled “Cache Issues”Symptom: Changes to .libraries.yml not reflected on site
Fix: Clear Drupal caches:
drush cr# Or via UI: Admin → Configuration → Performance → "Clear all caches"Aggregation Breaking Modules
Section titled “Aggregation Breaking Modules”Symptom: Works in development, breaks in production with aggregation enabled
Fix: Set preprocess: false for ES modules:
helix-button: js: dist/js/hx-button.js: preprocess: false # Do not aggregate attributes: type: moduleBest Practices Summary
Section titled “Best Practices Summary”- Always use
type: modulefor HELiX component libraries - Set
preprocess: falsefor all ES modules (Drupal can’t aggregate them safely) - Use semantic versioning — Increment
versionon updates to bust caches - Prefer dependencies over weight — Explicit dependencies are clearer
- Tree-shake with per-component libraries — Load only what’s needed
- Use HTTP/2 — Multiple small files outperform large bundles
- Monitor bundle sizes — Keep components <5KB, total bundle <50KB
- Cache aggressively — Version your assets for long-term caching
- Test with aggregation enabled — Catch production issues early
- Document custom libraries — Comment complex dependency chains
Additional Resources
Section titled “Additional Resources”- Drupal.org: Adding Stylesheets (CSS) and JavaScript (JS) to a Drupal Module
- Drupal.org: Attaching Libraries
Related Documentation: