FreshJuice DEV uses a modern build pipeline that compiles Tailwind CSS, bundles JavaScript with esbuild, and syncs files to HubSpot using the HubSpot CLI.
Build Pipeline Overview
The build process consists of three main tools working together:
Tailwind CSS Compiles utility-first CSS from source files
esbuild Bundles JavaScript modules with Alpine.js
HubSpot CLI Uploads and watches theme files
NPM Scripts
All build commands are defined in package.json and orchestrated using npm-run-all:
{
"scripts" : {
"clean" : "rimraf './_temp' './_dist' && mkdir './_temp' './_dist'" ,
"fetch:hubspot" : "hs fetch --overwrite /FreshJuice ./theme && ./scripts/post-fetch.sh" ,
"upload:hubspot" : "hs upload ./theme /FreshJuiceDEV" ,
"watch:hubspot" : "hs watch ./theme /FreshJuiceDEV" ,
"watch:tailwind" : "npx @tailwindcss/cli -i ./source/css/tailwind.css -o ./theme/css/tailwind.css --watch" ,
"build:tailwind" : "npx @tailwindcss/cli -i ./source/css/tailwind.css -o ./theme/css/tailwind.css" ,
"watch:js" : "npx esbuild ./source/js/main.js --outfile=./theme/js/main.js --bundle --watch" ,
"build:js" : "npx esbuild ./source/js/main.js --outfile=./theme/js/main.js --bundle" ,
"build:zip" : "./scripts/build-zip.sh" ,
"version:patch" : "./scripts/bump-theme-version.sh patch" ,
"version:minor" : "./scripts/bump-theme-version.sh minor" ,
"version:major" : "./scripts/bump-theme-version.sh major" ,
"start" : "npm-run-all --parallel watch:*" ,
"build" : "npm-run-all --serial clean build:js build:tailwind build:zip"
}
}
Development Workflow
Starting Development
The npm run start command runs all watch tasks in parallel:
This executes three processes simultaneously:
watch:tailwind
Watches for changes in CSS files and recompiles Tailwind
watch:js
Watches for changes in JavaScript files and rebuilds the bundle
watch:hubspot
Watches the theme directory and uploads changes to HubSpot
The npm-run-all --parallel command ensures all three processes run concurrently, providing instant feedback during development.
File Watching Flow
Tailwind CSS Compilation
Tailwind scans multiple sources for class names:
@import "tailwindcss" source(none);
/* Tailwind scans these paths for class names */
@source "../../theme/**/*.html";
@source "../../theme/**/*.js";
@source "../js/farmerswife.js";
@source "./**/*.css";
@theme {
/* Custom CSS variables */
--color-cursor: #FFFFFF;
--color-terminal: #000000 ;
--font-cursive: "Caveat", cursive;
}
/* Layer imports */
@import "./base/reset.css" ;
@import "./base/typography.css" ;
@import "./components/system.css" ;
Compilation Process
Development mode with watch flag: npx @tailwindcss/cli \
-i ./source/css/tailwind.css \
-o ./theme/css/tailwind.css \
--watch
Features:
Fast incremental builds
Source maps for debugging
Instant recompilation on file changes
Production build without watch: npx @tailwindcss/cli \
-i ./source/css/tailwind.css \
-o ./theme/css/tailwind.css
Features:
Optimized output
Minified CSS
Purged unused classes
Custom Theme Configuration
The @theme directive extends Tailwind with custom design tokens:
@theme {
/* Custom colors */
--color-cursor: #FFFFFF;
--color-cursor-500: #FFFFFF;
--color-terminal: #000000 ;
--color-terminal-500: #000000 ;
/* Custom fonts */
--font-cursive: "Caveat", cursive;
/* Custom spacing */
--spacing-screenSinNav: calc(100vh - 5rem);
/* Aspect ratios */
--aspect-video: 16 / 9;
--aspect-golden: 1 .6180 / 1;
--aspect-macbook: 16 / 10;
}
Use CSS custom properties in the @theme directive to create reusable design tokens that can be referenced throughout your theme.
JavaScript Bundling with esbuild
Bundle Configuration
esbuild bundles all JavaScript modules into a single file:
npx esbuild ./source/js/main.js \
--outfile=./theme/js/main.js \
--bundle \
--watch # Only in development
Entry Point
The main JavaScript file imports all dependencies:
import debugLog from './modules/_debugLog' ;
import loadScript from './modules/_loadScript' ;
import flyingPages from "flying-pages-module" ;
import Alpine from "alpinejs" ;
import intersect from "@alpinejs/intersect" ;
import collapse from "@alpinejs/collapse" ;
import focus from "@alpinejs/focus" ;
import dataDOM from "./modules/Alpine.data/DOM" ;
// Make Alpine available globally
window . Alpine = Alpine ;
// Register Alpine plugins
Alpine . plugin ( intersect );
Alpine . plugin ( collapse );
Alpine . plugin ( focus );
// Register Alpine data
Alpine . data ( "xDOM" , dataDOM );
// Initialize on DOM ready
domReady (() => {
Alpine . start ();
flyingPages ({});
});
Bundled Dependencies
All npm packages are bundled into the output:
Minimal JavaScript framework for declarative interactivity
"@alpinejs/collapse" : "^3.14.9" ,
"@alpinejs/focus" : "^3.14.9" ,
"@alpinejs/intersect" : "^3.14.9"
Official Alpine.js plugins for common patterns
"lite-youtube-embed" : "^0.3.3"
Lightweight YouTube embeds for better performance
Bundle Output
The compiled bundle is placed in theme/js/main.js and included in the base template:
{{ require_js(get_asset_url("../../js/main.js"), { position: "footer", defer: true }) }}
esbuild is extremely fast, typically bundling in milliseconds. This makes the watch mode highly responsive during development.
HubSpot CLI Integration
Authentication
Before using HubSpot CLI commands, authenticate with your portal:
Common Operations
Continuously upload changes during development: npm run watch:hubspot
# Or directly:
hs watch ./theme /FreshJuiceDEV
Monitors ./theme directory
Uploads changes automatically
Shows upload status in terminal
One-time upload of all theme files: npm run upload:hubspot
# Or directly:
hs upload ./theme /FreshJuiceDEV
Uploads entire theme directory
Overwrites existing files
Use after major changes
Download theme files from HubSpot: npm run fetch:hubspot
# Or directly:
hs fetch --overwrite /FreshJuice ./theme
Downloads from HubSpot portal
Runs post-fetch cleanup script
Useful for syncing changes
Portal Configuration
The HubSpot CLI uses hubspot.config.yml for portal settings:
defaultPortal : production
portals :
- name : production
portalId : 12345678
authType : personalaccesskey
auth :
tokenInfo :
accessToken : your-access-token
Never commit hubspot.config.yml with authentication tokens. Add it to .gitignore to prevent accidental exposure.
Production Build
Full Build Process
Run a complete production build:
This executes the following steps sequentially:
clean
Remove _temp/ and _dist/ directories, then recreate them rimraf './_temp' './_dist' && mkdir './_temp' './_dist'
build:js
Bundle JavaScript with esbuild (production mode) npx esbuild ./source/js/main.js --outfile=./theme/js/main.js --bundle
build:tailwind
Compile Tailwind CSS (production mode with minification) npx @tailwindcss/cli -i ./source/css/tailwind.css -o ./theme/css/tailwind.css
build:zip
Create distribution ZIP file
Distribution Package
The build creates a versioned ZIP file ready for distribution:
_dist/
└── freshjuice-dev-hubspot-theme-v3.0.0-dev.zip
This ZIP contains the complete theme/ directory and can be:
Uploaded to HubSpot Design Manager
Shared with clients or team members
Archived for version control
Version Management
Bumping Version Numbers
Use semantic versioning scripts:
Patch (3.0.0 → 3.0.1)
Minor (3.0.0 → 3.1.0)
Major (3.0.0 → 4.0.0)
The version script updates:
package.json version number
Theme metadata files
Git tags (if in a git repository)
Build Optimization
Tailwind Optimization
Only import needed CSS layers
Use @source to limit scan paths
Leverage JIT mode for smaller builds
Remove unused @import statements
JavaScript Optimization
Import only used Alpine plugins
Tree-shake unused dependencies
Use dynamic imports for heavy modules
Minimize third-party dependencies
HubSpot CLI Tips
Use watch during active development
Upload only when making large changes
Exclude files with .hsignore
Test locally before uploading
Caching Strategy
Leverage HubSpot’s CDN caching
Use versioned asset URLs
Set appropriate cache headers
Invalidate cache after major updates
Troubleshooting
Tailwind classes not applying
Cause : Files not included in @source directivesSolution : Add the file path to source/css/tailwind.css:@source "../../theme/path/to/your/file.html";
Cause : Missing dependencies or import errorsSolution : Check that all imports are valid:npm install # Reinstall dependencies
npm run build:js # Run build to see detailed errors
Cause : Authentication issues or file permissionsSolution : Re-authenticate and check file paths:hs auth # Re-authenticate
hs upload ./theme /FreshJuiceDEV --debug # Upload with debug info
Cause : Too many files being scanned or watchedSolution : Optimize @source paths and use .hsignore:node_modules/
_temp/
_dist/
.git/
Best Practices
Always run npm run start during active development to ensure changes are compiled and uploaded in real-time.
Use watch mode
Keep all watchers running during development for instant feedback
Test builds locally
Run npm run build before deploying to catch compilation errors
Version consistently
Use the version scripts to maintain semantic versioning
Clean before major builds
Run npm run clean if you encounter caching issues
Project Structure Understand where files are located
Theme Architecture Learn how components work together