Skip to content
Language comparisonintermediate · 2 min read · 2026-11-01

Measure the AI Cost of JavaScript UI Choices

Compare a small vanilla JavaScript counter with a framework-style counter by measuring markup, source size, imports, dependency surface, and safe AI edit context.

What you will learn

  • After this lesson you can compare two UI implementations by their edit context, not only by runtime behavior.
  • After this lesson you can include required markup when measuring vanilla JavaScript UI code fairly.
  • After this lesson you can identify which imports and package-like APIs add context for AI-assisted edits.
  • After this lesson you can build a small deterministic metric for code size, imports, dependencies, and context load.

The concrete idea: context is part of the cost

Two UI implementations can look equivalent to users but very different to an AI assistant. The assistant must understand the code it edits, the APIs it calls, and the conventions that make the change safe.

For a tiny feature, vanilla JavaScript often has a small context surface: markup, DOM selection, an event listener, local state, and a text update. A framework-style version may introduce imports, JSX-like syntax, component state, and package APIs.

That extra context is not automatically bad. It becomes worthwhile when it buys consistency, composition, testing patterns, or scale. The practical question is: for this feature, what is the minimum safe context?

Same feature: vanilla JavaScript

The vanilla version implements a counter with one button. To compare fairly, the measured sample includes both the required HTML and the script that updates it.

An AI assistant asked to change this safely mostly needs the button markup, this script, and the intended behavior. The concepts are local: find the button, update a variable, listen for clicks, and write text.

Same feature: framework-style component

The framework-style version expresses the same UI as a component. The code is still short, but safe editing may require more surrounding knowledge: how state works, what JSX-like syntax means, how the component is rendered, and which package provides the API.

In the metric below, deps means visible package/API dependency names. The framework-style sample has deps: 1 because it imports from ui-framework. Build or transpile conventions may matter in a real project, but they are not counted as package dependencies here.

A simple metric you can run

The runnable snippets do not claim to benchmark AI systems. They create a repeatable proxy: non-empty lines, import count, visible dependency count, and a rough contextScore.

The score intentionally penalizes imports and dependencies because they pull in external API knowledge. You can tune the weights for your team, but keep the same metric when comparing alternatives.

How to use this in real reviews

Before choosing a framework for a small feature, ask:

  • Does the feature need shared state, routing, reusable components, or established team conventions?
  • Will future changes be safer because the framework gives structure?
  • Or is the feature local enough that vanilla JavaScript is easier for humans and AI tools to modify?

The best choice is not always the smallest snippet. It is the implementation with the lowest total maintenance cost for the expected future changes.

Code

Html · Vanilla JavaScript counter with required markupIllustrative · not executed
<button id="count">0</button>
<script>
const button = document.querySelector("#count");
let count = 0;
button.addEventListener("click", () => {
  count += 1;
  button.textContent = String(count);
});
</script>

This browser example includes the required HTML plus JavaScript. It is marked non-runnable because the sandbox is Node.js, not a browser DOM environment.

JavaScript · Framework-style counterIllustrative · not executed
import { useState } from "ui-framework";

export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

This illustrative component shows the same behavior with framework-style state and JSX-like markup. It is non-runnable because it needs a framework package and a compatible build pipeline.

JavaScript · JavaScript context-cost comparison✓ Executed & verified · v22.23.4
"use strict";

const samples = [
  {
    name: "vanilla",
    deps: 0,
    code: `
<button id="count">0</button>
<script>
const button = document.querySelector("#count");
let count = 0;
button.addEventListener("click", () => {
  count += 1;
  button.textContent = String(count);
});
</script>
`
  },
  {
    name: "framework-style",
    deps: 1,
    code: `
import { useState } from "ui-framework";

export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}
`
  }
];

function nonEmptyLines(code) {
  return code.trim().split("\n").filter(line => line.trim()).length;
}

function importCount(code) {
  return (code.match(/^\s*import\s/gm) || []).length;
}

function tokenEstimate(code) {
  return code.trim().split(/\s+/).length;
}

function contextScore(code, deps) {
  return tokenEstimate(code) + nonEmptyLines(code) * 2 + importCount(code) * 20 + deps * 30;
}

console.log("name|lines|imports|deps|contextScore");
for (const sample of samples) {
  console.log(`${sample.name}|${nonEmptyLines(sample.code)}|${importCount(sample.code)}|${sample.deps}|${contextScore(sample.code, sample.deps)}`);
}

This standard-library Node.js program compares both implementations fairly by counting the vanilla HTML as part of the source context and counting one visible framework dependency.

Python · Python version of the same comparison✓ Executed & verified · Python 3.11.2
import re

samples = [
    {
        "name": "vanilla",
        "deps": 0,
        "code": '''
<button id="count">0</button>
<script>
const button = document.querySelector("#count");
let count = 0;
button.addEventListener("click", () => {
  count += 1;
  button.textContent = String(count);
});
</script>
'''
    },
    {
        "name": "framework-style",
        "deps": 1,
        "code": '''
import { useState } from "ui-framework";

export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}
'''
    }
]

def non_empty_lines(code):
    return len([line for line in code.strip().split("\n") if line.strip()])

def import_count(code):
    return len(re.findall(r"^\s*import\s", code, re.MULTILINE))

def token_estimate(code):
    return len(code.strip().split())

def context_score(code, deps):
    return token_estimate(code) + non_empty_lines(code) * 2 + import_count(code) * 20 + deps * 30

print("name|lines|imports|deps|contextScore")
for sample in samples:
    code = sample["code"]
    deps = sample["deps"]
    print(f'{sample["name"]}|{non_empty_lines(code)}|{import_count(code)}|{deps}|{context_score(code, deps)}')
Output (real run)
name|lines|imports|deps|contextScore
vanilla|9|0|0|41
framework-style|9|1|1|95

This Python program performs the same calculation, showing that the comparison idea is language-independent.

Common mistakes

  • ✕ Comparing vanilla JavaScript code without counting the HTML it requires.

    ✓ Include the required markup, such as <button id="count">0</button>, in the measured context or split the score into markup plus script for both options.

  • ✕ Treating dependency count as a vague penalty without defining it.

    ✓ Define exactly what is counted. In this lesson, deps means visible package/API dependency names; the framework-style sample has one: ui-framework.

  • ✕ Assuming a framework implementation is always the wrong choice for AI-assisted development.

    ✓ Use the metric as a decision aid. Frameworks can still pay off when the app needs shared state, routing, forms, tests, team conventions, or reusable components.

  • ✕ Sending an AI assistant only a component file when behavior depends on parent usage, framework lifecycle, or package conventions.

    ✓ Provide the smallest safe context: the component, relevant parent usage, dependency list, tests, and the framework patterns used by the project.

Interview questions

What does 'AI cost' mean in this lesson?

It means the amount of markup, source code, dependency knowledge, framework concepts, and project convention context an AI assistant needs to modify code safely.

Why can a short framework component still have a higher context cost?

Because the component may rely on imports, state APIs, JSX-like syntax, rendering conventions, build tooling, and project patterns that are not fully visible in the component's line count.

What is the minimum safe context for changing the vanilla counter shown here?

The relevant button markup, the script that owns the counter behavior, and the intended behavior. For the shown code, no package or build context is needed.

Why is deps set to 1 for the framework-style sample?

Because the shown code imports from one visible package/API source: ui-framework. The metric does not count a second hidden dependency unless it is explicitly defined.

When is a framework worth the extra context?

When it reduces total maintenance cost through reusable components, shared state patterns, routing, validation, tests, team conventions, or consistency across a larger app.

Practice

  1. Take one small UI widget from your project and list the minimum files an AI assistant would need to modify it safely.
  2. Extend the metric program to add a concepts column for ideas like DOM events, hooks, JSX, routing, or global state.
  3. Rewrite the vanilla counter to support a reset button, then estimate how much additional context is needed.

Quick quiz

Q1. What is the exact output of the JavaScript comparison program?
Q2. Why does the lesson count imports and dependencies when estimating AI edit cost?
Q3. What fairness issue is fixed by including `<button id="count">0</button>` in the vanilla sample?
Q4. Which statement is the most accurate conclusion from this comparison?

Key takeaways

  • For AI-assisted edits, count context surface, not just source lines.
  • A fair vanilla-vs-framework comparison must include required markup on both sides.
  • Imports and visible dependencies add API knowledge the assistant may need.
  • Tiny local UI behavior can be cheaper to explain in vanilla JavaScript.
  • Frameworks can still be the better choice when structure and scale matter.

References

See the full JavaScript learning path