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:
-
main (
app.js): Main application bundle- Page-specific modules (dynamically imported)
- Global functionality
- BigCommerce Stencil Utils
-
head_async (
lazysizes): Critical async scripts- Lazy image loading library
- Loaded in
<head>withasyncattribute
-
font (
font.js): Web font loader- WebFontLoader for Google Fonts
- Prevents FOUT (Flash of Unstyled Text)
-
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.jsonbrowserslist - 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 applicationtheme-bundle.head_async.js- Lazy image loadertheme-bundle.font.js- Font loadertheme-bundle.polyfills.js- Browser polyfillstheme-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-mapfor 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
.mapfiles 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
- Clean: Remove
node_modulesfrom slideout-variant-selector - Compile: Stencil CLI compiles Sass and creates bundle
- Output:
.zipfile 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
- Go to BigCommerce control panel
- Navigate to Storefront > My Themes
- Click Upload Theme
- Select the
.zipbundle - Wait for processing
- 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:
- Analyze bundle: Check
assets/dist/report.html - Split large modules
- Use dynamic imports
- 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
- Use Development Build: Faster for local development
- Incremental Builds: Webpack caches modules
- Exclude node_modules: Already done in config
- Parallel Processing: Webpack 5 default
- 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.