Skip to main content

Build Process

The Baits theme uses a modern build pipeline with Webpack 5, Babel, and Grunt to compile, optimize, and bundle assets for production.

Overview

Build Tools

  • Webpack 5: Module bundler for JavaScript
  • Babel: ES6+ to ES5 transpilation
  • Grunt: Task runner for auxiliary tasks (SVG sprites, linting)
  • Sass: CSS preprocessing

Build Commands

# Development build (source maps, no minification)
npm run buildDev

# Production build (minified, optimized)
npm run build

# Bundle theme for BigCommerce upload
npm run bundle

# Code quality
npm run stylelint # Lint Sass
npm run stylelint:fix # Fix Sass issues
grunt eslint # Lint JavaScript

Webpack Configuration

File Structure

webpack.common.js    # Shared base configuration
webpack.dev.js # Development overrides
webpack.prod.js # Production overrides

Webpack Common Config

Base configuration in webpack.common.js:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin,
{CleanWebpackPlugin} = require('clean-webpack-plugin'),
LodashPlugin = require('lodash-webpack-plugin'),
path = require('path'),
webpack = require('webpack');

module.exports = {
bail: true,
context: __dirname,
entry: {
main: './assets/js/app.js',
head_async: ['lazysizes'],
font: './assets/js/theme/common/font.js',
polyfills: './assets/js/polyfills.js',
},
module: {
rules: [
{
test: /\.js$/,
include: /(assets\/js|assets\\js|stencil-utils)/,
use: {
loader: 'babel-loader',
options: {
plugins: [
'@babel/plugin-syntax-dynamic-import',
'lodash',
],
presets: [
['@babel/preset-env', {
loose: true,
modules: false,
useBuiltIns: 'entry',
corejs: '^3.6.5',
}],
],
},
},
},
{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
options: {
exposes: ['$'],
},
}],
},
],
},
output: {
chunkFilename: 'theme-bundle.chunk.[name].js',
filename: 'theme-bundle.[name].js',
path: path.resolve(__dirname, 'assets/dist'),
},
performance: {
hints: 'warning',
maxAssetSize: 1024 * 300, // 300 KB
maxEntrypointSize: 1024 * 300,
},
plugins: [
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: ['assets/dist'],
verbose: false,
watch: false,
}),
new LodashPlugin(),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
}),
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
}),
],
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'),
},
},
};

Entry Points

Four entry points for optimized loading:

  1. main (app.js): Main application bundle

    • Page-specific modules (dynamically imported)
    • Global functionality
    • BigCommerce Stencil Utils
  2. head_async (lazysizes): Critical async scripts

    • Lazy image loading library
    • Loaded in <head> with async attribute
  3. font (font.js): Web font loader

    • WebFontLoader for Google Fonts
    • Prevents FOUT (Flash of Unstyled Text)
  4. polyfills (polyfills.js): Browser compatibility

    • Core-js polyfills for older browsers
    • Fetch API polyfill
    • FormData polyfill
    • Object-fit polyfill
    • Focus-within polyfill

Module Rules

Babel Loader

Transpiles modern JavaScript to ES5:

{
test: /\.js$/,
include: /(assets\/js|assets\\js|stencil-utils)/,
use: {
loader: 'babel-loader',
options: {
plugins: [
'@babel/plugin-syntax-dynamic-import', // Support import()
'lodash', // Tree-shake Lodash
],
presets: [
['@babel/preset-env', {
loose: true, // Smaller output
modules: false, // Keep ES6 modules for tree-shaking
useBuiltIns: 'entry', // Polyfill based on usage
corejs: '^3.6.5', // Core-js version
}],
],
},
},
}

Babel Preset Env:

  • Targets browsers based on package.json browserslist
  • Automatically includes necessary polyfills
  • Smaller bundles by only including needed transforms

Expose Loader

Exposes jQuery globally for legacy scripts:

{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
options: {
exposes: ['$'],
},
}],
}

Makes $ and jQuery available on window object.

Output Configuration

output: {
chunkFilename: 'theme-bundle.chunk.[name].js',
filename: 'theme-bundle.[name].js',
path: path.resolve(__dirname, 'assets/dist'),
}

Output Files:

  • theme-bundle.main.js - Main application
  • theme-bundle.head_async.js - Lazy image loader
  • theme-bundle.font.js - Font loader
  • theme-bundle.polyfills.js - Browser polyfills
  • theme-bundle.chunk.[name].js - Dynamically imported modules

Performance Budgets

performance: {
hints: 'warning',
maxAssetSize: 1024 * 300, // 300 KB per file
maxEntrypointSize: 1024 * 300, // 300 KB per entry point
}

Webpack warns when bundles exceed 300 KB, indicating need for optimization.

Plugins

CleanWebpackPlugin

Removes old build artifacts:

new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: ['assets/dist'],
verbose: false,
watch: false,
})

LodashPlugin

Optimizes Lodash imports:

new LodashPlugin()

Before:

import _ from 'lodash';
_.map([1, 2, 3], n => n * 2);

After (with babel-plugin-lodash):

import _map from 'lodash/map';
_map([1, 2, 3], n => n * 2);

Reduces bundle size by 90%+ when using Lodash.

ProvidePlugin

Auto-imports jQuery:

new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
})

No need to import jQuery in every file.

BundleAnalyzerPlugin

Generates bundle size visualization:

new BundleAnalyzerPlugin({
analyzerMode: 'static', // Generates HTML file
openAnalyzer: false, // Don't auto-open browser
})

Creates assets/dist/report.html with interactive treemap of bundle contents.

Use Cases:

  • Identify large dependencies
  • Find duplicate modules
  • Optimize bundle splitting

Resolve Aliases

Shortcuts 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'),
},
}

Benefits:

  • Use pre-minified versions
  • Faster builds (skip transpilation)
  • Smaller bundles

Development Build

Configuration in webpack.dev.js:

const webpack = require('webpack'),
{merge} = require('webpack-merge'),
commonConfig = require('./webpack.common.js');

module.exports = merge(commonConfig, {
devtool: 'eval-cheap-module-source-map',
mode: 'development',
});

Features:

  • Source Maps: eval-cheap-module-source-map for fast rebuilds
  • No Minification: Readable code for debugging
  • Fast Builds: Optimized for development speed

Run: npm run buildDev

Production Build

Configuration in webpack.prod.js:

const webpack = require('webpack'),
{merge} = require('webpack-merge'),
commonConfig = require('./webpack.common.js');

module.exports = merge(commonConfig, {
devtool: 'source-map',
mode: 'production',
optimization: {
noEmitOnErrors: true,
},
});

Features:

  • Minification: Uglified, minified code
  • Source Maps: Separate .map files for debugging production
  • Tree Shaking: Removes unused code
  • Dead Code Elimination: Removes unreachable code
  • Optimization: Webpack production optimizations

Run: npm run build

Production Optimizations

Webpack's production mode automatically:

  • Minifies JavaScript with Terser
  • Enables scope hoisting (smaller bundles)
  • Sets process.env.NODE_ENV = 'production'
  • Removes development-only code
  • Optimizes module IDs
  • Enables deterministic chunk hashing

Babel Configuration

Configured inline in webpack.common.js (can be moved to .babelrc).

Presets

presets: [
['@babel/preset-env', {
loose: true,
modules: false,
useBuiltIns: 'entry',
corejs: '^3.6.5',
}],
]

@babel/preset-env

Transforms ES6+ to ES5 based on target browsers.

Options:

  • loose: true

    • Generates simpler, faster code
    • May not be fully spec-compliant
    • Good for production
  • modules: false

    • Keep ES6 module syntax
    • Enables tree-shaking
    • Webpack handles bundling
  • useBuiltIns: 'entry'

    • Import polyfills based on usage
    • Requires import 'core-js' in entry
    • Smaller bundles than 'usage' (includes only needed polyfills)
  • corejs: '^3.6.5'

    • Core-js version for polyfills
    • Provides ES6+ features (Promise, Array.includes, etc.)

Plugins

plugins: [
'@babel/plugin-syntax-dynamic-import',
'lodash',
]

@babel/plugin-syntax-dynamic-import

Enables dynamic imports:

// Dynamic import for code splitting
const Product = () => import('./theme/product');

babel-plugin-lodash

Optimizes Lodash imports:

// Before
import { map } from 'lodash';

// After (transformed)
import map from 'lodash/map';

Works with LodashPlugin for maximum optimization.

Grunt Configuration

Grunt handles auxiliary build tasks not covered by Webpack.

Gruntfile

module.exports = function (grunt) {
require('time-grunt')(grunt);
require('load-grunt-config')(grunt);

grunt.loadNpmTasks('grunt-run');
grunt.loadNpmTasks('grunt-stylelint');
grunt.registerTask('default', ['eslint', 'svgstore']);
};

Task Configuration

Tasks configured in config/ directory (loaded by load-grunt-config):

config/
├── eslint.js
├── stylelint.js
├── svgstore.js
└── run.js

SVG Sprite Generation

Combines individual SVG icons into a sprite:

// config/svgstore.js
module.exports = {
default: {
files: {
'assets/icons/icons.svg': ['assets/icons/*.svg']
},
options: {
prefix: 'icon-',
svg: {
xmlns: 'http://www.w3.org/2000/svg'
}
}
}
};

Usage in Templates:

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

ESLint

JavaScript linting:

// config/eslint.js
module.exports = {
options: {
configFile: '.eslintrc',
format: 'stylish'
},
target: ['assets/js/**/*.js']
};

Run: grunt eslint

Stylelint

Sass linting:

// config/stylelint.js
module.exports = {
options: {
configFile: '.stylelintrc',
formatter: 'string',
ignoreDisables: false,
failOnError: true,
outputFile: '',
reportNeedlessDisables: false,
fix: false,
syntax: 'scss'
},
src: ['assets/scss/**/*.scss']
};

Run: npm run stylelint or grunt stylelint

Time Grunt

Measures task execution time:

require('time-grunt')(grunt);

Outputs timing report after tasks complete:

Execution Time (2023-04-20 10:15:33 UTC)
loading tasks 1.2s ▇▇▇▇▇▇▇▇▇▇▇ 40%
eslint 1.1s ▇▇▇▇▇▇▇▇▇▇ 37%
svgstore 0.7s ▇▇▇▇▇▇▇ 23%
Total 3s

Sass Compilation

Handled by BigCommerce Stencil CLI (not Webpack).

Compilation Process

stencil start    # Compiles Sass on file change
stencil bundle # Compiles for production

Sass Settings

From config.json:

{
"css_compiler": "scss",
"autoprefixer_cascade": true,
"autoprefixer_browsers": [
"> 1%",
"last 2 versions",
"Firefox ESR"
]
}

Autoprefixer

Automatically adds vendor prefixes:

Input:

.box {
display: flex;
transform: rotate(45deg);
}

Output:

.box {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}

Code Quality

ESLint Configuration

.eslintrc (Airbnb style guide):

{
"extends": "airbnb/base",
"parser": "babel-eslint",
"env": {
"browser": true,
"jquery": true
},
"rules": {
"indent": ["error", 4],
"no-console": "warn",
"prefer-template": "off",
"max-len": ["warn", 120]
}
}

Stylelint Configuration

.stylelintrc (Sass guidelines):

{
"extends": "stylelint-config-sass-guidelines",
"plugins": ["stylelint-scss"],
"rules": {
"max-nesting-depth": 3,
"selector-max-compound-selectors": 4,
"color-hex-length": "short",
"string-quotes": "single"
}
}

Fixing Issues

# Auto-fix Sass issues
npm run stylelint:fix

# ESLint doesn't have auto-fix in this setup
# Manually fix issues or add --fix flag to Grunt task

Theme Bundling

Create uploadable theme package:

npm run bundle

Executes:

{
"scripts": {
"prebundle": "npx rimraf assets/slideout-variant-selector/node_modules",
"bundle": "npm run prebundle && stencil bundle"
}
}

Bundle Process

  1. Clean: Remove node_modules from slideout-variant-selector
  2. Compile: Stencil CLI compiles Sass and creates bundle
  3. Output: .zip file in project root

Bundle Contents

theme-bundle.zip
├── assets/
│ ├── dist/ # Compiled JS
│ ├── icons/ # SVG sprites
│ ├── img/ # Images
│ └── scss/ # Source Sass
├── config.json
├── lang/ # Translations
├── meta/ # Theme metadata
├── schema.json # Theme settings
└── templates/ # Handlebars templates

Excluded:

  • node_modules/
  • .git/
  • Source JavaScript (only compiled bundles)
  • Development tools

Uploading Theme

  1. Go to BigCommerce control panel
  2. Navigate to Storefront > My Themes
  3. Click Upload Theme
  4. Select the .zip bundle
  5. Wait for processing
  6. Apply theme to store

Performance Optimization

Code Splitting

Dynamic imports create separate chunks:

// app.js
const pageClasses = {
product: () => import('./theme/product'), // Separate chunk
category: () => import('./theme/category'), // Separate chunk
cart: () => import('./theme/cart'), // Separate chunk
};

Benefits:

  • Smaller initial bundle
  • Faster page loads
  • Only load needed code per page

Tree Shaking

Webpack eliminates unused code:

// Only imports used function
import { map } from 'lodash';

// Entire lodash library NOT included

Requires ES6 module syntax (import/export).

Minification

Production builds minify code:

Before:

function calculateTotal(items) {
const total = items.reduce((sum, item) => {
return sum + (item.price * item.quantity);
}, 0);
return total;
}

After:

function calculateTotal(e){return e.reduce((e,t)=>e+t.price*t.quantity,0)}

Lazy Loading

Non-critical scripts loaded asynchronously:

<script src="theme-bundle.head_async.js" async></script>

Images loaded as they enter viewport (Lazysizes).

Caching

Stencil CLI adds cache-busting hashes:

<script src="theme-bundle.main.js?v=1234567890"></script>

Browser caches assets until version changes.

Build Troubleshooting

Common Issues

Webpack Build Fails

Error: Module not found

Solution:

rm -rf node_modules
npm install

Sass Compilation Errors

Error: Undefined variable

Solution: Check variable imports in theme.scss:

@import "settings/colors";
@import "settings/global";

Out of Memory

Error: FATAL ERROR: ... JavaScript heap out of memory

Solution: Increase Node memory:

export NODE_OPTIONS=--max-old-space-size=4096
npm run build

Or in package.json:

{
"scripts": {
"build": "node --max-old-space-size=4096 node_modules/webpack/bin/webpack.js --config webpack.prod.js"
}
}

Bundle Too Large

Warning: asset size limit: The following asset(s) exceed the recommended size limit (244 KiB)

Solutions:

  1. Analyze bundle: Check assets/dist/report.html
  2. Split large modules
  3. Use dynamic imports
  4. Remove unused dependencies

Debug Mode

Run Webpack with verbose output:

npx webpack --config webpack.dev.js --progress --profile

Shows:

  • Build steps
  • Module dependencies
  • Timing information

Continuous Integration

GitHub Actions Example

name: Build Theme

on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '20'

- name: Install dependencies
run: npm ci

- name: Lint JavaScript
run: grunt eslint

- name: Lint Sass
run: npm run stylelint

- name: Build production
run: npm run build

- name: Bundle theme
run: npm run bundle

Pre-Commit Hooks

Using Husky for git hooks:

npm install --save-dev husky lint-staged

.huskyrc:

{
"hooks": {
"pre-commit": "lint-staged"
}
}

lint-staged.config.js:

module.exports = {
'*.js': ['eslint --fix', 'git add'],
'*.scss': ['stylelint --fix', 'git add']
};

Automatically lints and fixes files before commit.

Build Performance

Metrics

Typical build times on modern hardware:

  • Development Build: 3-5 seconds
  • Production Build: 8-12 seconds
  • Sass Compilation: 1-2 seconds
  • Theme Bundle: 5-8 seconds

Optimization Tips

  1. Use Development Build: Faster for local development
  2. Incremental Builds: Webpack caches modules
  3. Exclude node_modules: Already done in config
  4. Parallel Processing: Webpack 5 default
  5. Upgrade Hardware: More RAM, faster CPU helps

Version History

Recent build configuration changes:

  • v5.0.0: Upgraded to Node 18, Webpack 5, updated packages
  • v4.2.0: Updated Webpack config to match Cornerstone v6.12.0
  • Earlier versions used Webpack 4

Check CHANGELOG.md for detailed version history.