Build Process
Documentation of the Webpack and Grunt build system for the Cold Steel BigCommerce theme.
Build System Overview
The theme uses a dual build system:
- Webpack - JavaScript bundling and transpilation
- Grunt - Linting, SVG processing, and task orchestration
Build Flow
Source Files
↓
Webpack (via stencil.conf.js)
↓
Babel Transpilation
↓
Bundle Generation
↓
assets/dist/
↓
Stencil CLI
↓
BigCommerce CDN
Webpack Configuration
Entry Points
Three main entry points defined in webpack.common.js:
entry: {
main: './assets/js/app.js', // Main app bundle
head_async: ['lazysizes'], // Critical async scripts
polyfills: './assets/js/polyfills.js', // Browser polyfills
}
Main Bundle (theme-bundle.main.js)
Primary application bundle including:
- Page-specific modules (product, category, cart, etc.)
- Global modules (header, footer, navigation)
- Custom Cold Steel features
- Elevate theme functionality
- Engraving tool
Code-split by page type:
// app.js
const pageClasses = {
product: () => import('./theme/product'),
category: () => import('./theme/category'),
cart: () => import('./theme/cart'),
// ... only loads what's needed per page
};
Head Async Bundle (theme-bundle.head_async.js)
Loaded in <head> for critical functionality:
['lazysizes'] // Lazy loading images
Polyfills Bundle (theme-bundle.polyfills.js)
Browser compatibility shims:
core-js- ES6+ featureswhatwg-fetch- Fetch APIregenerator-runtime- async/await- Focus-within polyfill
- Object-fit polyfill
Module Rules
JavaScript Transpilation
{
test: /\.js$/,
include: /(assets\/js|assets\\js|stencil-utils)/,
use: {
loader: 'babel-loader',
options: {
plugins: [
'@babel/plugin-syntax-dynamic-import',
'lodash', // Tree-shake lodash
'@babel/plugin-syntax-class-properties'
],
presets: [
['@babel/preset-env', {
loose: true,
modules: false, // Preserve ES6 modules for tree-shaking
useBuiltIns: 'usage',
targets: '> 1%, last 2 versions, Firefox ESR',
corejs: '^3.4.1',
}],
],
},
},
}
Target Browsers:
- Browser usage > 1%
- Last 2 versions
- Firefox ESR
jQuery Exposure
Makes jQuery available globally:
{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
options: '$',
}],
}
Allows legacy code to use $ without importing.
CSS Processing
{
test: /\.css$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" }
]
}
Output Configuration
output: {
chunkFilename: 'theme-bundle.chunk.[name].js',
filename: 'theme-bundle.[name].js',
path: path.resolve(__dirname, 'assets/dist'),
}
Generates:
theme-bundle.main.jstheme-bundle.head_async.jstheme-bundle.polyfills.jstheme-bundle.chunk.[name].js(for code-split modules)
Performance Budgets
performance: {
hints: 'warning',
maxAssetSize: 1024 * 300, // 300 KB
maxEntrypointSize: 1024 * 300, // 300 KB
}
Warns if bundles exceed 300 KB.
Webpack Plugins
1. CleanPlugin
new CleanPlugin(['assets/dist'], {
verbose: false,
watch: false,
})
Clears assets/dist/ before each build.
2. LodashPlugin
new LodashPlugin()
Further optimizes Lodash by removing unused features.
3. ProvidePlugin
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
})
Auto-imports jQuery for legacy code.
4. BundleAnalyzerPlugin
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
})
Generates report.html with bundle visualization.
Module Resolution
Custom aliases for common imports:
resolve: {
alias: {
jquery: path.resolve(__dirname, 'node_modules/jquery/dist/jquery.min.js'),
jstree: path.resolve(__dirname, 'node_modules/jstree/dist/jstree.min.js'),
lazysizes: path.resolve(__dirname, 'node_modules/lazysizes/lazysizes.min.js'),
nanobar: path.resolve(__dirname, 'node_modules/nanobar/nanobar.min.js'),
'slick-carousel': path.resolve(__dirname, 'node_modules/slick-carousel/slick/slick.min.js'),
'svg-injector': path.resolve(__dirname, 'node_modules/svg-injector/dist/svg-injector.min.js'),
sweetalert2: path.resolve(__dirname, 'node_modules/sweetalert2/dist/sweetalert2.min.js'),
},
}
Ensures consistent versions and uses pre-built min files where available.
Environment-Specific Configs
Development (webpack.dev.js)
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-source-map',
optimization: {
minimize: false
}
});
Features:
- Source maps for debugging
- Non-minified code
- Faster compilation
- Detailed error messages
Production (webpack.prod.js)
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
devtool: false,
optimization: {
minimize: true,
concatenateModules: true,
usedExports: true
}
});
Features:
- Minified code
- Tree-shaking
- Scope hoisting
- Dead code elimination
- No source maps
Stencil Integration
stencil.conf.js
Bridges Webpack with Stencil CLI:
var webpack = require('webpack');
var watchOptions = {
files: ['/templates', '/lang'],
ignored: ['/assets/scss', '/assets/less', '/assets/css', '/assets/dist']
};
function development() {
var devConfig = require('./webpack.dev.js');
webpack(devConfig).watch({}, (err, stats) => {
if (err) {
console.error(err.message, err.details);
}
if (stats.hasErrors()) {
console.error(stats.toString({ all: false, errors: true, colors: true }));
}
process.send('reload');
});
}
function production() {
var prodConfig = require('./webpack.prod.js');
webpack(prodConfig).run((err, stats) => {
if (err) {
console.error(err.message, err.details);
process.exit(1);
return;
}
process.send('done');
});
}
Build Hooks
When running stencil start:
- Stencil CLI spawns
stencil.conf.jsas child process - Sends
developmentmessage - Webpack compiles in watch mode
- On change, Webpack sends
reloadmessage - Stencil CLI triggers browser refresh
When running stencil bundle:
- Sends
productionmessage - Webpack runs production build
- Sends
donemessage when complete - Stencil CLI packages theme
Grunt Configuration
Gruntfile.js
module.exports = function(grunt) {
require('time-grunt')(grunt);
require('load-grunt-config')(grunt, {
jitGrunt: {
staticMappings: {
scsslint: 'grunt-scss-lint'
}
}
});
grunt.loadNpmTasks('grunt-run');
grunt.registerTask('default', [
'eslint',
'jest',
'scsslint',
'svgstore'
]);
};
Grunt Tasks
ESLint
npx grunt eslint
Lints JavaScript files against .eslintrc:
- Airbnb style guide
- JSX support
- ES6+ syntax
Config: .eslintrc
{
"extends": "airbnb",
"parser": "babel-eslint",
"env": {
"browser": true,
"jquery": true
}
}
SCSS Lint
npx grunt scsslint
Checks SCSS files for:
- Style violations
- Best practices
- Consistency
Config: .scss-lint.yml (if exists)
Jest Tests
npx grunt jest
Runs Jest test suite for JavaScript.
Config: jest.config.js or package.json
SVG Store
npx grunt svgstore
Combines individual SVG icons into sprite sheet:
assets/icons/*.svg → static/img/icon-sprite.svg
Used in templates:
Default Task
npx grunt
Runs all tasks:
- ESLint (JavaScript linting)
- Jest (tests)
- SCSS Lint (style linting)
- SVG Store (icon sprite generation)
NPM Scripts
Defined in package.json
{
"scripts": {
"build": "npx webpack --config webpack.prod.js",
"buildDev": "npx webpack --config webpack.dev.js",
"lighthouse": "npx lighthouse --config-path=lighthouse-config.js ...",
"test": "npx jest"
}
}
Build Script
npm run build
Runs production Webpack build:
- Uses
webpack.prod.js - Minifies output
- Generates bundles in
assets/dist/
BuildDev Script
npm run buildDev
Runs development Webpack build:
- Uses
webpack.dev.js - Includes source maps
- Non-minified output
Test Script
npm test
Runs Jest test suite.
Lighthouse Script
npm run lighthouse
Runs Lighthouse performance audit:
npx lighthouse
--config-path=lighthouse-config.js
--quiet
--output json
--chrome-flags="--headless"
$URL | jq '.audits | map_values(.rawValue)'
SCSS Compilation
Theme Entry Point
assets/scss/theme.scss:
@import "settings/global";
@import "tools/mixins";
@import "utilities/helpers";
@import "components/buttons";
// ... more imports
Compilation Process
- Stencil CLI watches
assets/scss/ - On change, compiles SCSS to CSS
- Applies Autoprefixer
- Outputs to CDN-served location
- Browser refreshes
Autoprefixer Config
From config.json:
{
"autoprefixer_cascade": true,
"autoprefixer_browsers": [
"> 1%",
"last 2 versions",
"Firefox ESR"
]
}
Automatically adds vendor prefixes:
// Input
display: flex;
// Output
display: -webkit-box;
display: -ms-flexbox;
display: flex;
Asset Pipeline
Development Flow
Source File Change
↓
Stencil CLI Detects Change
↓
[Templates] → Direct reload
[SCSS] → Compile → Reload
[JS] → Webpack (watch mode) → Reload
↓
Browser Refresh
Production Flow
npm run build
↓
Webpack Production Build
↓
assets/dist/ populated
↓
stencil bundle
↓
ZIP file created
↓
Upload to BigCommerce
↓
Assets served via CDN
Optimization Strategies
Code Splitting
Implemented via dynamic imports:
const getProduct = () => import('./theme/product');
Only loads product module on product pages.
Tree Shaking
Webpack removes unused exports:
import { debounce } from 'lodash'; // Only debounce included
Minification
Production builds minified with:
- Terser (JavaScript)
- CSSNano (CSS via Stencil)
Caching
Output filenames include content hash:
theme-bundle.main.[hash].js
Ensures cache busting on changes.
Build Artifacts
Generated Files
assets/dist/
├── theme-bundle.main.js # Main application
├── theme-bundle.head_async.js # Critical async
├── theme-bundle.polyfills.js # Polyfills
├── theme-bundle.chunk.*.js # Code-split chunks
└── report.html # Bundle analysis
ZIP Package
stencil bundle creates:
Cold Steel - 1.9.21.zip
├── assets/
├── templates/
├── lang/
├── config.json
├── manifest.json
├── schema.json
└── ...
Performance Monitoring
Webpack Stats
After build, view stats:
npm run build
# Open assets/dist/report.html
Shows:
- Bundle sizes
- Module breakdown
- Dependency tree
- Duplicate modules
Lighthouse Audits
npm run lighthouse
Measures:
- Performance score
- First Contentful Paint
- Time to Interactive
- Total Blocking Time
- Cumulative Layout Shift
Continuous Integration
Recommended CI Pipeline
# Example GitHub Actions
name: Build and Test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm install
- run: npm run build
- run: npx grunt eslint
- run: npx grunt scsslint
- run: npm test
Troubleshooting
Build Errors
Syntax Error in JS
Module parse failed: Unexpected token
Solution: Check JavaScript syntax, ensure Babel is configured
Missing Module
Cannot resolve module 'lodash'
Solution: npm install
Out of Memory
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed
Solution: Increase Node memory
$env:NODE_OPTIONS="--max-old-space-size=4096"
npm run build
Slow Builds
Optimization strategies:
- Upgrade to Webpack 5
- Enable caching
- Limit Babel transpilation to
assets/jsonly - Use
thread-loaderfor parallel processing
Watch Mode Issues
If changes not detected:
# Increase watch limit (Linux/Mac)
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# Windows: usually not an issue
Best Practices
-
Always run production build before bundling
npm run build
stencil bundle -
Lint before committing
npx grunt eslint scsslint -
Review bundle size regularly
npm run build
# Check report.html -
Keep dependencies updated
npm audit
npm update -
Use environment-specific configs
- Never commit
.stencilor secrets - Use different API URLs for dev/prod
- Never commit