Build tools compilation
3.7 Build Tools and Compilation
When to Use This Section
- You're setting up build tools for a new Radix sub-theme
- You need to configure Laravel Mix for SCSS/JS compilation
- You want to optimize production builds with PurgeCSS
- You're setting up live reload with BrowserSync
Laravel Mix Configuration
Decision: Standard Laravel Mix Setup
WHEN TO USE: - Radix sub-themes use Laravel Mix by default for SCSS/JS compilation - You need build process for development and production - You want automatic compilation on file changes
File: webpack.mix.js (located in sub-theme root)
const mix = require('laravel-mix');
// Set public path for output
mix.setPublicPath('.');
// SCSS compilation
mix.sass('src/scss/main.style.scss', 'dist/css')
.options({
processCssUrls: false,
postCss: [
require('autoprefixer')({
grid: true,
}),
],
});
// JavaScript compilation
mix.js('src/js/main.script.js', 'dist/js')
.extract(); // Extract vendor libraries
// Source maps for development
if (!mix.inProduction()) {
mix.sourceMaps();
}
// Production optimizations
if (mix.inProduction()) {
mix.version(); // Cache busting
}
Decision: PurgeCSS Integration
WHEN TO USE: - Production builds where CSS file size matters - You want to remove unused Bootstrap classes - You're concerned about page load performance
WHY: Bootstrap includes many utility classes you may not use. PurgeCSS removes unused CSS, reducing file size by 50-80%.
Configuration:
const mix = require('laravel-mix');
require('laravel-mix-purgecss');
mix.setPublicPath('.');
mix.sass('src/scss/main.style.scss', 'dist/css')
.options({
processCssUrls: false,
postCss: [
require('autoprefixer')({
grid: true,
}),
],
});
// PurgeCSS configuration
if (mix.inProduction()) {
mix.purgeCss({
// Scan these files for used classes
content: [
'./templates/**/*.twig',
'./components/**/*.twig',
'./src/js/**/*.js',
'./THEME_NAME.theme', // Preprocess file
'./THEME_NAME.libraries.yml',
],
// Preserve dynamic classes
safelist: [
/^col-/, // Bootstrap grid
/^btn-/, // Bootstrap buttons
/^alert-/, // Bootstrap alerts
/^dropdown-/, // Bootstrap dropdowns
/^modal-/, // Bootstrap modals
/^carousel-/, // Bootstrap carousels
/^accordion-/, // Bootstrap accordions
/^nav-/, // Bootstrap navigation
/^navbar-/, // Bootstrap navbar
/^card-/, // Bootstrap cards
/^fade/, // Bootstrap transitions
/^show/, // Bootstrap show states
/^active/, // Bootstrap active states
/^disabled/, // Bootstrap disabled states
/^js-/, // JavaScript-added classes
/^ajax-/, // Drupal AJAX classes
/^drupal-/, // Drupal system classes
],
// Preserve classes matching patterns
safelist: {
standard: [/^is-/, /^has-/],
deep: [/^bs-/, /^carousel/, /^modal/],
greedy: [/tooltip$/, /popover$/],
},
});
}
CRITICAL SAFELISTS: - Bootstrap JavaScript Components: Classes added dynamically (fade, show, active) - Drupal System Classes: ajax-processed, contextual-links, etc. - State Classes: is-active, has-error, etc.
WHY SAFELIST NEEDED: PurgeCSS only scans static files. JavaScript-added classes get removed unless safelisted, breaking functionality.
Decision: BrowserSync Configuration
WHEN TO USE: - You want live reload during development - You're testing responsive designs across devices - You want synchronized scrolling/clicks across browsers
WHY: Eliminates manual browser refresh, speeds up development, enables multi-device testing.
Configuration:
const mix = require('laravel-mix');
mix.setPublicPath('.');
mix.sass('src/scss/main.style.scss', 'dist/css')
.options({
processCssUrls: false,
});
// BrowserSync configuration
mix.browserSync({
// Local development URL
proxy: 'https://SITENAME.ddev.local',
// Watch these files for changes
files: [
'dist/css/**/*.css',
'dist/js/**/*.js',
'templates/**/*.twig',
'components/**/*.twig',
'*.theme',
],
// BrowserSync options
open: false, // Don't auto-open browser
notify: false, // Disable notification overlay
reloadDelay: 0, // Immediate reload
injectChanges: true, // Inject CSS without reload
// HTTPS configuration (for DDEV)
https: {
key: '/path/to/.ddev/traefik/certs/SITENAME.ddev.local-key.pem',
cert: '/path/to/.ddev/traefik/certs/SITENAME.ddev.local.pem',
},
});
DDEV SETUP:
For DDEV projects, use this simplified configuration:
mix.browserSync({
proxy: 'https://SITENAME.ddev.local',
files: [
'dist/css/**/*.css',
'templates/**/*.twig',
'components/**/*.twig',
],
open: 'external',
host: 'SITENAME.ddev.local',
notify: false,
});
WHY PROXY: BrowserSync proxies your local site, injecting live reload scripts without modifying your Drupal installation.
Build Commands
Decision Table: Which Command to Use
| Task | Command | Use When |
|---|---|---|
| Active development | npm run watch |
Making frequent SCSS/JS changes |
| Single build (dev) | npm run dev |
One-time development build |
| Production build | npm run production |
Before commit/deploy |
| Initial setup | npm install |
First time or after package.json changes |
Pattern: Development Workflow
# 1. Install dependencies (first time only)
cd themes/custom/THEME_NAME
npm install
# 2. Start watch mode with BrowserSync
npm run watch
# 3. Make changes to SCSS/JS/Twig files
# (BrowserSync auto-reloads on save)
# 4. Before committing, build production
npm run production
git add dist/
git commit -m "Theme updates"
Pattern: Production Build Workflow
# Clean previous builds
rm -rf dist/css/* dist/js/*
# Build optimized production assets
npm run production
# Verify output
ls -lh dist/css/
ls -lh dist/js/
# Commit production assets
git add dist/
git commit -m "Production build: [description]"
Common Mistakes
1. Not installing npm packages in sub-theme
# BAD: No package.json or node_modules in sub-theme
themes/custom/THEME_NAME/
└── webpack.mix.js # Exists but npm install never run
WHY: Laravel Mix and dependencies must be installed locally in sub-theme.
CORRECT:
cd themes/custom/THEME_NAME
npm install # Creates node_modules/ and installs dependencies
2. Committing node_modules/ to git
# BAD: Committing massive node_modules directory
git add node_modules/
git commit
WHY: Bloats repository with thousands of files (100+ MB). Dependencies should be installed via npm install, not committed.
CORRECT:
Add to .gitignore:
node_modules/
npm-debug.log
Commit only package.json and package-lock.json:
git add package.json package-lock.json
3. Using watch mode in production
# BAD: Running watch on production server
npm run watch &
WHY: Watch mode includes source maps, unminified code, and file watchers that consume resources. Never run watch in production.
CORRECT:
Production servers run compiled assets only:
npm run production # Build locally before deploy
git add dist/ # Commit production build
# Deploy to production
4. Not clearing Drupal cache after CSS changes
# Change SCSS → compile → refresh browser → styles don't update
WHY: Drupal aggregates CSS files. Changes may not appear until cache cleared.
CORRECT:
npm run watch # Compile CSS
drush cr # Clear Drupal cache
# OR disable CSS aggregation during development
drush config-set system.performance css.preprocess 0
5. Wrong PurgeCSS safelist (breaks JavaScript components)
// BAD: No safelist for dynamic classes
mix.purgeCss({
content: ['./templates/**/*.twig'],
safelist: [], // Missing Bootstrap JS classes
});
WHY: Bootstrap JavaScript adds classes like .show, .active, .fade. PurgeCSS removes them, breaking modals, dropdowns, carousels.
CORRECT:
mix.purgeCss({
safelist: [
/^show/, /^active/, /^fade/, /^collapse/,
/^modal-/, /^dropdown-/, /^carousel-/,
],
});