Empowering LLMs to Build Web Apps: Vanilla JS vs. Frameworks

Published 1 Apr 2025

Exploring whether LLM-generated web apps should target existing frameworks, vanilla JavaScript, or a purpose-built LLM-first micro-framework.

Introduction

Large language models (LLMs) like GPT-4 can already generate production-quality front-end code in Vue, React, Svelte — even full apps with routing and state management. But those outputs still target today’s frameworks, which carry nontrivial boilerplate, build steps, and runtime overhead. What if we asked the LLM to sidestep existing abstractions and write vanilla JavaScript instead? Or better yet, target a purpose-built “LLM-First” mini-framework that minimizes token usage, build complexity, and runtime cost?

In this post we’ll:

  • Show how an LLM-driven component looks in Vue vs. vanilla JS.
  • Compare token footprint, bundle size, and dev iteration speed.
  • Sketch an “LLM-First” micro-framework API designed for minimalism.
  • Measure how much we gain by narrowing the gap between LLM prompt and output.

1. LLMs + Existing Frameworks

1.1 Typical Workflow

Prompt: “Generate a Vue 3 component that renders a todo list…”

Output: .vue file with <template>, <script>, optional <style>.

Build: Vite/Rollup/Webpack → JS/CSS bundles.

Runtime: Virtual DOM diffing, hydration, reactivity runtime.

Pros:

  • Familiar patterns, ecosystem plugins, hot-reload.
  • Encapsulated state, scoped CSS, JSX/TS support.

Cons:

  • ~100–200 tokens prompt length just to describe a component API.
  • Additional 2–5 kB shipped for framework runtime.
  • Build step latency when iterating.

1.2 Example: Counter Component in Vue vs. Vanilla JS

Vue 3 Composition API

<script setup>
import { ref } from 'vue'

const count = ref(0)
function increment() {
  count.value++
}
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

Prompt tokens: ~120

Runtime payload: ~7 kB gzip (Vue runtime + reactivity).

Build: requires Vite/Rollup.

Vanilla JS + Minimal Reactive Helper

// reactive.js (500 bytes)
export function reactive(obj, onChange) {
  return new Proxy(obj, {
    set(target, key, val) {
      target[key] = val;
      onChange();
      return true;
    }
  });
}

// counter.js
import { reactive } from './reactive.js';

const state = reactive({ count: 0 }, render);

function increment() {
  state.count++;
}

function render() {
  document.body.innerHTML = `
    <p>Count: ${state.count}</p>
    <button id="inc">Increment</button>
  `;
  document.getElementById('inc').onclick = increment;
}

render();

Prompt tokens: ~80

Runtime payload: ~0.5 kB gzip (your helper + app code).

Build: none — just ship .js.

Key takeaway: vanilla JS cuts bundle size by an order of magnitude and trims down the prompt by ~30%.

2. Measuring Efficiency

Metric Vue 3 Vanilla JS
Prompt length ~120 tokens ~80 tokens
Bundle size (gz) ~7 kB ~0.5 kB
Build step yes (~500 ms) no
Dev feedback loop slower instant reload

Clearly, boilerplate and runtime code in mainstream frameworks add both token and payload overhead. But vanilla JS loses out on ergonomics once your app grows beyond trivial widgets.

3. Toward an “LLM-First” Micro-Framework

What if we formalize a tiny runtime with primitives that:

  • Minimize prompt tokens by having a known, fixed API.
  • Bake reactivity into a 1 kB runtime.
  • Allow the LLM to emit direct imperative calls.

3.1 Proposed API

// lfm.js (~1 kB minified)
export const createApp      // takes root element
export const h              // hyperscript helper
export const reactive, effect

Usage pattern:

import { createApp, h, reactive, effect } from 'lfm.js';

const state = reactive({ todos: [] });

const app = createApp(document.body);

effect(() => {
  const list = state.todos.map(todo => h('li', {}, todo.text));
  app.render(h('ul', {}, ...list));
});

app.run(); // wires up reactivity

Prompt only needs to reference these 5 symbols.

LLM can focus on building tree structure and logic, not reactivity plumbing.

Runtime remains a single minified file.

4. Token and Performance Gains

Approach Prompt ↓ Bundle ↓ Iteration ↓
Vue / React / Svelte baseline baseline baseline
Vanilla JS –30% –90% instant
LLM-First LFM –50% –85% vs. Vanilla instant

Insight: By standardizing on a tiny, well-documented API, we give the LLM a narrow, familiar “vocabulary.” That slashes the prompt, cuts boilerplate, and still gives you reactive power.

Conclusion & Next Steps

Vanilla JS is a great baseline: low token overhead and tiny bundles.

But LLM-First frameworks hit the sweet spot — giving just enough structure for maintainability while staying lean.

I’m building an open-source “LF-Spark” (LLM-First Spark) that you can drop in and start feeding your LLM prompts against. Stay tuned!

Call to Action

Try it yourself: spin up a toy counter in vanilla JS vs. your favorite framework and measure prompt length and bundle size.

Feedback wanted: what primitives would you want in an LLM-First runtime?

Subscribe: I’ll soon release the “LF-Spark” prototype with demos.