Skip to main content

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:

  1. Webpack - JavaScript bundling and transpilation
  2. 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+ features
  • whatwg-fetch - Fetch API
  • regenerator-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.js
  • theme-bundle.head_async.js
  • theme-bundle.polyfills.js
  • theme-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:

  1. Stencil CLI spawns stencil.conf.js as child process
  2. Sends development message
  3. Webpack compiles in watch mode
  4. On change, Webpack sends reload message
  5. Stencil CLI triggers browser refresh

When running stencil bundle:

  1. Sends production message
  2. Webpack runs production build
  3. Sends done message when complete
  4. 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:

<svg><use xlink:href="#icon-name"></use></svg>

Default Task

npx grunt

Runs all tasks:

  1. ESLint (JavaScript linting)
  2. Jest (tests)
  3. SCSS Lint (style linting)
  4. 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

  1. Stencil CLI watches assets/scss/
  2. On change, compiles SCSS to CSS
  3. Applies Autoprefixer
  4. Outputs to CDN-served location
  5. 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

# 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:

  1. Upgrade to Webpack 5
  2. Enable caching
  3. Limit Babel transpilation to assets/js only
  4. Use thread-loader for 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

  1. Always run production build before bundling

    npm run build
    stencil bundle
  2. Lint before committing

    npx grunt eslint scsslint
  3. Review bundle size regularly

    npm run build
    # Check report.html
  4. Keep dependencies updated

    npm audit
    npm update
  5. Use environment-specific configs

    • Never commit .stencil or secrets
    • Use different API URLs for dev/prod

Further Reading