Development Environment Setup Intermediate 12 min read

Webpack Configuration (Alternative Approach)

Configure Webpack as an alternative to Vite for React-powered Shopify themes. Output directly to shopify-theme/assets/ with the same simple structure.

While this course primarily uses Vite, Webpack remains a solid choice—especially if you’re extending an existing Dawn theme or your team already has Webpack expertise. This lesson covers how to configure Webpack to output directly to shopify-theme/assets/.

When to Choose Webpack

Consider Webpack over Vite when:

  • Extending Dawn: Dawn uses Webpack, and staying consistent simplifies things
  • Team familiarity: Your team knows Webpack’s configuration style
  • Specific plugins: You need Webpack-only plugins for your workflow
  • Legacy support: You need broader browser compatibility

Step 1: Install Dependencies

Terminal window
# Core Webpack packages
npm install -D webpack webpack-cli
# React and TypeScript loaders
npm install -D babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript
# TypeScript
npm install -D typescript @types/react @types/react-dom
# CSS handling
npm install -D css-loader style-loader mini-css-extract-plugin
# Production optimization
npm install -D terser-webpack-plugin css-minimizer-webpack-plugin
# React
npm install react react-dom

Step 2: Create Webpack Configuration

Create webpack.config.js in your project root:

webpack.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const isDev = process.env.NODE_ENV !== 'production';
/*
* Webpack config for Shopify themes
* Same output structure as Vite - direct to shopify-theme/assets/
*/
module.exports = {
// 'development' enables helpful error messages, 'production' enables minification
mode: isDev ? 'development' : 'production',
// Single entry point - Webpack bundles all imports from here
entry: './src/main.tsx',
output: {
filename: 'react-bundle.js',
// Output directly to theme - no intermediate dist/ folder
path: path.resolve(__dirname, 'shopify-theme/assets'),
// CRITICAL: Don't delete other theme assets (images, fonts, etc.)
clean: false,
},
// Source maps: 'eval-source-map' is fast for dev, 'source-map' is accurate for production
devtool: isDev ? 'eval-source-map' : 'source-map',
module: {
rules: [
// TypeScript and React - Babel handles both transpilation and JSX
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { targets: 'defaults' }], // JS compatibility
['@babel/preset-react', { runtime: 'automatic' }], // JSX transform (no React import needed)
'@babel/preset-typescript', // TypeScript → JavaScript
],
},
},
},
// CSS handling
{
test: /\.css$/,
use: [
// Dev: inject styles into DOM for fast refresh
// Prod: extract to separate .css file for caching
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: {
// Only treat .module.css files as CSS Modules
auto: true,
// Dev: readable class names for debugging
// Prod: short hashes for smaller files
localIdentName: isDev
? '[name]__[local]--[hash:base64:5]'
: '[hash:base64:8]',
},
},
},
],
},
// Images: inline small ones as base64, emit large ones as files
{
test: /\.(png|jpg|jpeg|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024, // Images under 4KB are inlined
},
},
generator: {
filename: 'react-images/[name][ext]',
},
},
],
},
resolve: {
// Allow importing without extensions: import Button from './Button'
extensions: ['.tsx', '.ts', '.js', '.jsx'],
// Path aliases - must match tsconfig.json paths
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@hooks': path.resolve(__dirname, 'src/hooks'),
'@stores': path.resolve(__dirname, 'src/stores'),
'@utils': path.resolve(__dirname, 'src/utils'),
'@types': path.resolve(__dirname, 'src/types'),
},
},
plugins: [
// Extract CSS to separate file (not inlined in JS)
// Better for caching - CSS can be cached separately from JS
new MiniCssExtractPlugin({
filename: 'react-bundle.css',
}),
],
optimization: {
minimizer: [
// Minify JavaScript
new TerserPlugin({
terserOptions: {
compress: {
drop_console: !isDev, // Remove console.log in production
},
},
}),
// Minify CSS
new CssMinimizerPlugin(),
],
},
// Cleaner terminal output
stats: {
colors: true,
modules: false, // Hide module-by-module details
children: false, // Hide child compiler output
},
watchOptions: {
ignored: /node_modules/, // Don't watch node_modules (performance)
aggregateTimeout: 300, // Wait 300ms after changes before rebuilding (debounce)
},
};

Key settings:

  • output.path: 'shopify-theme/assets' — Direct output to theme
  • output.clean: false — Preserves other assets in the folder

Step 3: Configure Babel

Create .babelrc for additional configuration (optional, since we inline it above):

{
"presets": [
["@babel/preset-env", {
"targets": "> 0.5%, last 2 versions, not dead",
"useBuiltIns": "usage",
"corejs": 3
}],
["@babel/preset-react", {
"runtime": "automatic"
}],
"@babel/preset-typescript"
]
}

If using the separate .babelrc with useBuiltIns, you’ll need core-js:

Terminal window
npm install core-js

Step 4: TypeScript Configuration

The same tsconfig.json from the Vite setup works here:

{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@hooks/*": ["src/hooks/*"],
"@stores/*": ["src/stores/*"],
"@utils/*": ["src/utils/*"],
"@types/*": ["src/types/*"]
}
},
"include": ["src"]
}

Step 5: Package.json Scripts

{
"scripts": {
"dev": "webpack --watch --mode development",
"build": "webpack --mode production",
"typecheck": "tsc --noEmit"
}
}

Step 6: Run the Build

Terminal window
# Development with watch
npm run dev
# Production build
npm run build

Output:

asset react-bundle.js 142 KiB [emitted] (name: main)
asset react-bundle.css 892 bytes [emitted] (name: main)
webpack 5.89.0 compiled successfully in 1245 ms

Check the output:

Terminal window
ls shopify-theme/assets/
# react-bundle.js react-bundle.css

Files go directly to your theme—no intermediate folders or copy steps.

Webpack vs Vite Configuration Comparison

Here’s how the same features are configured in each tool:

FeatureViteWebpack
Entry pointbuild.rollupOptions.inputentry
Output dirbuild.outDiroutput.path
Preserve assetsemptyOutDir: falseclean: false
Path aliasesresolve.aliasresolve.alias
CSS ModulesAutomatic for .module.csscss-loader options
TypeScriptBuilt-inbabel-loader + presets
MinificationBuilt-in (terser)TerserPlugin
Source mapsbuild.sourcemapdevtool
Watch modevite build --watchwebpack --watch

Advanced Webpack Configuration

Code Splitting (Multiple Bundles)

webpack.config.js
/*
* Code Splitting: Create separate bundles per page type
* Reduces initial load since users only download code for their current page
*/
module.exports = {
entry: {
// Each key becomes a separate bundle
main: './src/main.tsx', // Always loaded (header, cart drawer)
product: './src/entries/product.tsx', // Only on product pages
collection: './src/entries/collection.tsx', // Only on collection pages
},
output: {
// [name] is replaced with entry key: react-main.js, react-product.js, etc.
filename: 'react-[name].js',
path: path.resolve(__dirname, 'shopify-theme/assets'),
clean: false,
},
plugins: [
new MiniCssExtractPlugin({
// CSS is also split per entry point
filename: 'react-[name].css',
}),
],
};

Environment Variables

const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.DEBUG': JSON.stringify(process.env.DEBUG || 'false'),
}),
],
};

Access in code:

if (process.env.DEBUG === 'true') {
console.log('Debug mode');
}

Bundle Analysis

Install the analyzer:

Terminal window
npm install -D webpack-bundle-analyzer

Add to config:

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: '../bundle-report.html',
}),
],
};

Run and view the report:

Terminal window
npm run build
open bundle-report.html

Faster Rebuilds with Caching

module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
watchOptions: {
ignored: /node_modules/,
},
};

Troubleshooting Webpack

”Module not found” for path aliases

Ensure TypeScript and Webpack aliases match exactly:

webpack.config.js
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
}
tsconfig.json
"paths": {
"@/*": ["src/*"]
}

Large bundle size

Check what’s included:

Terminal window
npm install -D webpack-bundle-analyzer
# Add plugin and run build

Common culprits:

  • Entire lodash library (use lodash-es or individual imports)
  • Unused icon libraries
  • Moment.js (use date-fns instead)

Other assets getting deleted

Make sure clean is set to false:

output: {
path: path.resolve(__dirname, 'shopify-theme/assets'),
clean: false, // IMPORTANT!
}

Complete File Structure

After this lesson with Webpack:

your-project/
├── shopify-theme/
│ ├── assets/
│ │ ├── react-bundle.js ✓ (generated by Webpack)
│ │ └── react-bundle.css ✓ (generated by Webpack)
│ └── layout/
│ └── theme.liquid ✓
├── src/
│ ├── styles/
│ │ └── main.css ✓
│ └── main.tsx ✓
├── package.json ✓
├── tsconfig.json ✓
└── webpack.config.js ✓

Key Takeaways

  1. Same folder structure as Vite—only the config file differs
  2. Output directly to shopify-theme/assets/—no copy plugins needed
  3. Use clean: false to preserve other theme assets
  4. Babel handles TypeScript and React transpilation
  5. Your React code is identical regardless of build tool choice

In the next lesson, we’ll integrate Shopify CLI with our build process for the complete development workflow.

Finished this lesson?

Mark it complete to track your progress.

Discussion

Loading comments...