Advanced Topics
JavaScript Bundling
JavaScript Bundling with Stellify
Bundle modern JavaScript and frontend assets using the Stellify Bundler Service for your Laravel applications.
Repository
stellify-bundler-service - JavaScript bundling service for Stellify projects
Overview
The Stellify Bundler allows you to:
- Bundle JavaScript modules with Vite
- Compile TypeScript
- Process CSS (Tailwind, PostCSS)
- Optimize assets for production
- Hot module replacement in development
Why Use the Bundler?
Modern Laravel apps use Vite for asset compilation. The Stellify Bundler integrates this into your workflow:
- Write JavaScript/TypeScript in Stellify
- Bundle automatically on save
- Deploy optimized assets with your code
- No local Node.js setup required
Installation
Option 1: Stellify Cloud (Recommended)
Bundling is built into the platform. No installation needed.
- Enable bundler in Project Settings
- Upload your
package.json - Write JavaScript in the editor
- Assets bundle automatically
Option 2: Self-Hosted Service
Run the bundler service yourself:
git clone https://github.com/Stellify-Software-Ltd/stellify-bundler-service.git
cd stellify-bundler-service
npm install
npm run build
npm start
Then configure Stellify to use your instance:
Settings → Bundler → Custom URL
https://your-bundler-service.com
Quick Start
1. Create package.json
In your Stellify project:
{
"name": "my-stellify-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"@vitejs/plugin-laravel": "^1.0.0",
"vite": "^5.0.0",
"tailwindcss": "^3.4.0",
"postcss": "^8.4.0",
"autoprefixer": "^10.4.0"
}
}
2. Create vite.config.js
import { defineConfig } from 'vite';
import laravel from '@vitejs/plugin-laravel';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
],
});
3. Create JavaScript Entry Point
resources/js/app.js:
import './bootstrap';
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();
4. Create CSS Entry Point
resources/css/app.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
5. Bundle
Click Bundle Assets in Stellify, or save files to trigger auto-bundling.
Project Structure
Recommended Structure
resources/
├── js/
│ ├── app.js # Main JavaScript entry
│ ├── bootstrap.js # Bootstrap dependencies
│ └── components/
│ ├── navbar.js
│ └── modal.js
├── css/
│ ├── app.css # Main CSS entry
│ └── components/
│ └── buttons.css
└── views/
└── layouts/
└── app.blade.php # Include bundled assets
Using JavaScript Frameworks
Alpine.js
Perfect for Laravel + Stellify:
Install:
{
"dependencies": {
"alpinejs": "^3.13.0"
}
}
Configure:
// resources/js/app.js
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();
Use in Blade:
<div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
<div x-show="open">Content</div>
</div>
Vue.js
For complex SPAs:
Install:
{
"dependencies": {
"vue": "^3.4.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0"
}
}
Configure:
// vite.config.js
import { defineConfig } from 'vite';
import laravel from '@vitejs/plugin-laravel';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [
laravel({
input: ['resources/js/app.js'],
refresh: true,
}),
vue(),
],
});
Create Component:
<!-- resources/js/components/Welcome.vue -->
<template>
<div>
<h1>{{ message }}</h1>
</div>
</template>
<script setup>
import { ref } from 'vue';
const message = ref('Hello from Vue!');
</script>
Use in app.js:
import { createApp } from 'vue';
import Welcome from './components/Welcome.vue';
createApp({})
.component('Welcome', Welcome)
.mount('#app');
React
For React enthusiasts:
Install:
{
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.2.0"
}
}
Configure:
// vite.config.js
import { defineConfig } from 'vite';
import laravel from '@vitejs/plugin-laravel';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
laravel({
input: ['resources/js/app.jsx'],
refresh: true,
}),
react(),
],
});
Working with CSS
Tailwind CSS
Install:
{
"devDependencies": {
"tailwindcss": "^3.4.0",
"postcss": "^8.4.0",
"autoprefixer": "^10.4.0"
}
}
Configure tailwind.config.js:
export default {
content: [
"./resources/**/*.blade.php",
"./resources/**/*.js",
"./resources/**/*.vue",
],
theme: {
extend: {},
},
plugins: [],
}
Configure postcss.config.js:
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
Use in CSS:
/* resources/css/app.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn-primary {
@apply bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600;
}
}
Sass/SCSS
{
"devDependencies": {
"sass": "^1.70.0"
}
}
// resources/css/app.scss
$primary-color: #3490dc;
.button {
background: $primary-color;
&:hover {
background: darken($primary-color, 10%);
}
}
Update Vite config:
input: ['resources/css/app.scss', 'resources/js/app.js']
Including Bundled Assets
In Blade Templates
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My App</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
<div id="app">
<!-- Your content -->
</div>
</body>
</html>
The @vite() directive:
- Development: Links to Vite dev server (hot reload)
- Production: Links to bundled files in
public/build/
Preloading Assets
@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Preload critical fonts -->
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
Development Workflow
Local Development
If running Stellify locally:
-
Start Vite dev server:
npm run dev -
Work in Stellify editor
-
Changes hot-reload automatically
Cloud Development
Using Stellify Cloud:
- Edit JavaScript/CSS in editor
- Save changes
- Bundler compiles in background
- Preview updated assets
Production Builds
Optimization
Production builds are automatically optimized:
- ✅ Minification
- ✅ Tree shaking
- ✅ Code splitting
- ✅ Source maps (optional)
- ✅ Asset hashing for cache busting
Build Command
npm run build
Or in Stellify:
Project Settings → Bundle Assets → Production Build
Output to public/build/:
public/build/
├── manifest.json
├── assets/
│ ├── app-[hash].js
│ ├── app-[hash].css
│ └── logo-[hash].png
Deploying Bundled Assets
Option 1: Export with assets
stellify export --include-assets
Option 2: Build on server
# On your server after pulling code
npm install --production
npm run build
Option 3: CDN upload
Upload public/build/ to your CDN and update .env:
ASSET_URL=https://cdn.example.com
Advanced Configuration
Code Splitting
Split code by route/component:
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor': ['alpine', 'axios'],
'dashboard': ['./resources/js/pages/dashboard.js'],
},
},
},
},
});
Environment Variables
Access env vars in JavaScript:
// In .env
VITE_API_URL=https://api.example.com
// In JavaScript
const apiUrl = import.meta.env.VITE_API_URL;
Note: Prefix with VITE_ to expose to frontend.
Custom Aliases
// vite.config.js
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './resources/js'),
'@components': path.resolve(__dirname, './resources/js/components'),
},
},
});
Usage:
import Button from '@components/Button.vue';
Source Maps
Enable for debugging:
// vite.config.js
export default defineConfig({
build: {
sourcemap: true, // or 'inline' or 'hidden'
},
});
Third-Party Libraries
Installing Packages
Add to package.json:
{
"dependencies": {
"axios": "^1.6.0",
"lodash": "^4.17.21",
"dayjs": "^1.11.0"
}
}
Stellify bundler automatically installs dependencies.
Using in Code
// resources/js/app.js
import axios from 'axios';
import _ from 'lodash';
import dayjs from 'dayjs';
window.axios = axios;
window._ = _;
window.dayjs = dayjs;
// Now available globally or import where needed
Optimizing Bundle Size
Use tree-shaking imports:
// Bad (imports entire library)
import _ from 'lodash';
// Good (imports only what you need)
import { debounce, throttle } from 'lodash-es';
Troubleshooting
Bundle Fails
Error: Cannot find module 'X'
- Check package.json
- Verify dependencies installed
- Try re-bundling
Error: Syntax error in X.js
- Check JavaScript syntax
- Ensure valid ES6/TypeScript
- Review browser compatibility
Assets Not Loading
In Development:
- Ensure Vite dev server running
- Check
@vite()directive in Blade - Verify asset paths
In Production:
- Run
npm run build - Check
public/build/exists - Verify
manifest.jsonpresent
Slow Bundle Times
- Reduce dependencies
- Enable code splitting
- Use production mode
- Check for circular dependencies
Best Practices
Performance
- Code split by route - Don't load everything upfront
- Lazy load components - Import dynamically
- Optimize images - Use modern formats (WebP, AVIF)
- Enable compression - Gzip or Brotli
Security
- Validate user input - On both client and server
- Sanitize output - Prevent XSS
- Use CSRF tokens - Protect forms
- Don't expose secrets - Keep API keys server-side
Maintainability
- Follow conventions - Use standard directory structure
- Document complex code - Add comments
- Version lock dependencies - Use exact versions
- Regular updates - Keep packages current
Examples
Example 1: Simple Alpine.js App
// resources/js/app.js
import Alpine from 'alpinejs';
Alpine.data('counter', () => ({
count: 0,
increment() {
this.count++;
}
}));
Alpine.start();
<!-- In Blade -->
<div x-data="counter">
<p x-text="count"></p>
<button @click="increment">Increment</button>
</div>
Example 2: Vue Component Library
// resources/js/app.js
import { createApp } from 'vue';
import Button from './components/Button.vue';
import Modal from './components/Modal.vue';
import Card from './components/Card.vue';
const app = createApp({});
app.component('Button', Button);
app.component('Modal', Modal);
app.component('Card', Card);
app.mount('#app');
Example 3: Axios API Integration
// resources/js/api.js
import axios from 'axios';
const api = axios.create({
baseURL: '/api',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
}
});
export const getUsers = () => api.get('/users');
export const createUser = (data) => api.post('/users', data);
Publishing Visual Editor Files
Stellify's visual code editor allows you to create JavaScript and Vue files using the clause-based interface. These files can be published as bundled scripts and attached to routes.
Overview
The Publish workflow:
- Create JS/Vue files in the visual editor using clauses and statements
- Publish via the API to bundle with esbuild
- Script is automatically attached to the selected route
- Bundle loads in the preview when viewing the route
Creating Files in the Visual Editor
JavaScript Files
Create a .js file and add:
- Statements - Top-level code like imports, variable declarations, function calls
- Methods - Functions defined in the file
- Includes - Other files to import (creates import statements)
Example structure for main.js:
File: main.js (type: js)
├── includes: [Counter.vue UUID]
├── statements:
│ └── createApp(Counter).mount('#app')
Vue Single File Components
Create a .vue file with:
- Template - Root element UUIDs for the component template
- Statements - Script setup code (imports, refs, computed, etc.)
- Methods - Component methods
Example structure for Counter.vue:
File: Counter.vue (type: vue)
├── template: [root-div UUID]
├── statements:
│ ├── import { ref } from 'vue'
│ └── const count = ref(0)
├── methods:
│ ├── increment() { count.value++ }
│ └── decrement() { count.value-- }
Template Elements
Vue component templates are built from Element records:
{
"uuid": "element-uuid",
"tag": "div",
"classes": ["flex", "gap-4"],
"data": ["child-element-uuid-1", "child-element-uuid-2"],
"text": "Static text content",
"statements": ["statement-uuid"],
"click": "method-uuid"
}
- tag - HTML tag name
- classes - Array of CSS classes
- data - Child element UUIDs
- text - Static text content
- statements - UUIDs for Vue interpolation
{{ expression }} - click - Method UUID for
@clickhandler
Publishing a Bundle
API Endpoint
POST /api/publish
Request Body
{
"uuid": "entry-file-uuid",
"filename": "app",
"route": "route-uuid"
}
| Parameter | Required | Description |
|-----------|----------|-------------|
| uuid | Yes | UUID of the entry file (e.g., main.js) |
| filename | No | Output filename (defaults to file name) |
| route | No | Route UUID to attach the script to |
Response
{
"success": true,
"message": "Bundle published successfully",
"data": {
"url": "https://storage.example.com/bundles/app.a1b2c3d4.js",
"path": "bundles/app.a1b2c3d4.js",
"hash": "a1b2c3d4",
"size": 12345
}
}
How It Works
- File Tree Loading - The entry file and all its includes are loaded recursively
- Code Assembly -
JsAssemblerServiceconverts clause UUIDs to actual code:- JS files: imports, statements, and function declarations
- Vue files:
<template>,<script setup>, with reactive code
- Bundling - Files are sent to the bundler service (esbuild) for compilation
- Upload - The bundled JS is uploaded to storage with a content hash
- Route Attachment - A script meta tag is created/updated and attached to the route
Script Loading
When a route is previewed, script meta tags are injected after Vue mounts:
// Scripts are dynamically injected to avoid Vue template compilation issues
if (window.App?.body?.meta && window.App?.meta) {
window.App.body.meta.forEach(uuid => {
const metaOpts = window.App.meta[uuid];
if (metaOpts && metaOpts.type === 'script' && metaOpts.src) {
const script = document.createElement('script');
script.src = metaOpts.src;
document.body.appendChild(script);
}
});
}
File Includes
Use the includes array to import other files:
{
"name": "main",
"type": "js",
"includes": ["counter-vue-uuid", "utils-js-uuid"]
}
This generates:
import Counter from './Counter.vue';
import * as utils from './utils.js';
.vuefiles use default imports.jsfiles use namespace imports
Element Conventions
Click Events
Add click handlers to elements:
{
"tag": "button",
"text": "Increment",
"click": "increment-method-uuid"
}
Outputs: <button @click="increment">Increment</button>
Vue Interpolation
Display reactive values with the statements array:
{
"tag": "span",
"statements": ["count-statement-uuid"]
}
Outputs: <span>{{ count }}</span>
Example: Counter Component
Counter.vue file structure:
Template:
└── div.p-4.text-center
├── h1 (text: "Counter")
├── p (statements: [count])
├── button (text: "-", click: decrement)
└── button (text: "+", click: increment)
Statements:
├── import { ref } from 'vue'
└── const count = ref(0)
Methods:
├── increment: count.value++
└── decrement: count.value--
main.js file structure:
Includes: [Counter.vue]
Statements:
└── createApp(Counter).mount('#app')
Published output:
// Bundled, minified JS with Vue runtime
// Uploaded to: /bundles/main.a1b2c3d4.js
Troubleshooting
Bundle Not Loading
- Check the route has the script meta tag attached
- Verify the bundle URL is accessible
- Check browser console for loading errors
Component Not Rendering
- Ensure the mount target element exists (
#app) - Check Vue component syntax in assembled code
- Verify all statement/clause UUIDs resolve correctly
Template Issues
- Elements must have a
tagproperty to render - Check
classesis an array, not a string - Verify child UUIDs in
dataarray are valid elements
Next Steps
- Learn about MCP & AI Integration for AI-assisted development
- Read Exporting Code to deploy your app
- Explore Configuration for environment setup
Need help? Check the stellify-bundler-service repository or contact support.
- Previous
- Exporting Code
- Next
- MCP & AI Setup