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.

  1. Enable bundler in Project Settings
  2. Upload your package.json
  3. Write JavaScript in the editor
  4. 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:

  1. Start Vite dev server:

    npm run dev
    
  2. Work in Stellify editor

  3. Changes hot-reload automatically

Cloud Development

Using Stellify Cloud:

  1. Edit JavaScript/CSS in editor
  2. Save changes
  3. Bundler compiles in background
  4. 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.json present

Slow Bundle Times

  • Reduce dependencies
  • Enable code splitting
  • Use production mode
  • Check for circular dependencies

Best Practices

Performance

  1. Code split by route - Don't load everything upfront
  2. Lazy load components - Import dynamically
  3. Optimize images - Use modern formats (WebP, AVIF)
  4. Enable compression - Gzip or Brotli

Security

  1. Validate user input - On both client and server
  2. Sanitize output - Prevent XSS
  3. Use CSRF tokens - Protect forms
  4. Don't expose secrets - Keep API keys server-side

Maintainability

  1. Follow conventions - Use standard directory structure
  2. Document complex code - Add comments
  3. Version lock dependencies - Use exact versions
  4. 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:

  1. Create JS/Vue files in the visual editor using clauses and statements
  2. Publish via the API to bundle with esbuild
  3. Script is automatically attached to the selected route
  4. 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 @click handler

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

  1. File Tree Loading - The entry file and all its includes are loaded recursively
  2. Code Assembly - JsAssemblerService converts clause UUIDs to actual code:
    • JS files: imports, statements, and function declarations
    • Vue files: <template>, <script setup>, with reactive code
  3. Bundling - Files are sent to the bundler service (esbuild) for compilation
  4. Upload - The bundled JS is uploaded to storage with a content hash
  5. 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';
  • .vue files use default imports
  • .js files 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 tag property to render
  • Check classes is an array, not a string
  • Verify child UUIDs in data array are valid elements

Next Steps


Need help? Check the stellify-bundler-service repository or contact support.

Please be aware that our documentation is under construction.