Skip to content

Instantly share code, notes, and snippets.

@faustinoaq
Last active January 17, 2026 17:44
Show Gist options
  • Select an option

  • Save faustinoaq/b19da758fc45155a0b3b10d9f578c5ce to your computer and use it in GitHub Desktop.

Select an option

Save faustinoaq/b19da758fc45155a0b3b10d9f578c5ce to your computer and use it in GitHub Desktop.
Front-end libraries (React, Vue, Angular) and the basic principles of how they work, all in a single file using pure JavaScript (VanillaJS).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Angular from Scratch</title>
<style>
.my-component {
font-family: Arial, sans-serif;
text-align: center;
padding: 50px;
background: #f0f0f0;
}
.my-component .container {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.my-component .message {
font-size: 24px;
margin-bottom: 20px;
}
.my-component .buttons button {
font-size: 16px;
padding: 10px 20px;
margin: 5px;
cursor: pointer;
border: none;
border-radius: 5px;
transition: background 0.3s;
}
.my-component .buttons button:hover {
background: #ddd;
}
.my-component .buttons button:active {
background: #ccc;
}
</style>
</head>
<body>
<div id="app">
<div class="my-component">
<div class="container">
<p class="message">Count: <span ng-bind="count"></span></p>
<div class="buttons">
<button ng-click="increment">Increment</button>
<button ng-click="decrement">Decrease</button>
</div>
</div>
</div>
</div>
<script>
// Reactive system to track changes
function reactive(data) {
const listeners = [];
const proxy = new Proxy(data, {
set(target, property, value) {
target[property] = value;
listeners.forEach(listener => listener()); // Notify all listeners on data change
return true;
}
});
proxy.subscribe = function (listener) {
listeners.push(listener);
};
return proxy;
}
// Our basic Angular-like app system
function myAngularApp(rootElement, controller) {
const data = reactive(controller.data());
// Bind methods to data
for (const key in controller.methods) {
data[key] = function () {
controller.methods[key].call(data); // Call the controller method in context of data
data.notify(); // Trigger re-render
};
}
// Notify function to re-render on changes
data.notify = function () {
compile(rootElement);
};
function compile(element) {
const bindElements = element.querySelectorAll('[ng-bind]');
const clickElements = element.querySelectorAll('[ng-click]');
// Set text content for bound elements
bindElements.forEach(el => {
const property = el.getAttribute('ng-bind');
el.textContent = data[property];
});
// Set up click handlers for elements with ng-click
clickElements.forEach(el => {
const methodName = el.getAttribute('ng-click');
el.onclick = data[methodName]; // Assign the method directly to onclick
});
}
// Initial compilation
compile(rootElement);
data.subscribe(() => compile(rootElement)); // Subscribe compile function to re-render on data change
}
// Define controller with data and methods
const MyController = {
data() {
return { count: 0 };
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
}
};
// Initialize the app
document.addEventListener('DOMContentLoaded', function () {
const rootElement = document.getElementById('app');
myAngularApp(rootElement, MyController);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My React from Scratch</title>
</head>
<body>
<script>
// Stateful logic with hooks
let currentComponent = null;
function useState(initialValue) {
if (!currentComponent) {
throw new Error('useState must be called within a component');
}
const stateIndex = currentComponent.stateIndex;
if (!currentComponent.state[stateIndex]) {
currentComponent.state[stateIndex] = [
initialValue,
(value) => {
currentComponent.state[stateIndex][0] = value;
currentComponent.render();
},
];
}
const stateTuple = currentComponent.state[stateIndex];
currentComponent.stateIndex++;
return [stateTuple[0], stateTuple[1]];
}
function createComponent(renderFn) {
return function Component() {
currentComponent = {
state: [],
stateIndex: 0,
renderFn: renderFn,
render: function () {
this.stateIndex = 0; // Reset index on each render
const newVNode = this.renderFn();
const rootElement = document.getElementById('root') || document.body;
if (!this.vnode) {
// Initial render
this.vnode = newVNode;
rootElement.appendChild(createElement(newVNode));
} else {
const patches = diff(this.vnode, newVNode);
patch(rootElement, patches);
this.vnode = newVNode;
}
},
};
currentComponent.render();
return currentComponent;
};
}
function h(tag, props, ...children) {
return { tag, props, children };
}
function createElement(vnode) {
if (typeof vnode === 'string') {
return document.createTextNode(vnode);
}
const { tag, props, children } = vnode;
const element = document.createElement(tag);
for (let key in props) {
element[key] = props[key];
}
children.forEach(child => element.appendChild(createElement(child)));
return element;
}
function diff(oldVNode, newVNode) {
if (!oldVNode) {
return { type: 'CREATE', newVNode };
}
if (!newVNode) {
return { type: 'REMOVE' };
}
if (typeof oldVNode !== typeof newVNode || oldVNode.tag !== newVNode.tag) {
return { type: 'REPLACE', newVNode };
}
if (typeof newVNode === 'string') {
if (oldVNode !== newVNode) {
return { type: 'TEXT', newVNode };
} else {
return null;
}
}
const patch = {
type: 'UPDATE',
props: diffProps(oldVNode.props, newVNode.props),
children: diffChildren(oldVNode.children, newVNode.children),
};
return patch;
}
function diffProps(oldProps, newProps) {
const patches = [];
for (let key in newProps) {
if (newProps[key] !== oldProps[key]) {
patches.push({ key, value: newProps[key] });
}
}
for (let key in oldProps) {
if (!(key in newProps)) {
patches.push({ key, value: undefined });
}
}
return patches;
}
function diffChildren(oldChildren, newChildren) {
const patches = [];
const maxLen = Math.max(oldChildren.length, newChildren.length);
for (let i = 0; i < maxLen; i++) {
patches.push(diff(oldChildren[i], newChildren[i]));
}
return patches;
}
function patch(parent, patchObj, index = 0) {
if (!patchObj) return;
const el = parent.childNodes[index];
switch (patchObj.type) {
case 'CREATE': {
const newEl = createElement(patchObj.newVNode);
parent.appendChild(newEl);
break;
}
case 'REMOVE': {
if (el) {
parent.removeChild(el);
}
break;
}
case 'REPLACE': {
const newEl = createElement(patchObj.newVNode);
if (el) {
parent.replaceChild(newEl, el);
} else {
parent.appendChild(newEl);
}
break;
}
case 'TEXT': {
if (el) {
el.textContent = patchObj.newVNode;
}
break;
}
case 'UPDATE': {
if (el) {
const { props, children } = patchObj;
props.forEach(({ key, value }) => {
if (value === undefined) {
el.removeAttribute(key);
} else {
el[key] = value;
}
});
children.forEach((childPatch, i) => {
patch(el, childPatch, i);
});
}
break;
}
}
}
const MyComponent = createComponent(function () {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
}
function decrement() {
setCount(count - 1);
}
return h('div', { className: 'my-component' },
h('div', { className: 'container' },
h('p', { className: 'message' }, `Count: ${count}`),
h('div', { className: 'buttons' },
h('button', { onclick: () => increment() }, 'Increment'),
h('button', { onclick: () => decrement() }, 'Decrease')
)
)
);
});
// Create an initial root element
const root = document.createElement('div');
root.id = 'root';
document.body.appendChild(root);
// Initialize App
const App = MyComponent();
App.render();
// Add CSS styling scoped to the component
const style = document.createElement('style');
style.textContent = `
.my-component {
font-family: Arial, sans-serif;
text-align: center;
padding: 50px;
background: #f0f0f0;
}
.my-component .container {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.my-component .message {
font-size: 24px;
margin-bottom: 20px;
}
.my-component .buttons button {
font-size: 16px;
padding: 10px 20px;
margin: 5px;
cursor: pointer;
border: none;
border-radius: 5px;
transition: background 0.3s;
}
.my-component .buttons button:hover {
background: #ddd;
}
.my-component .buttons button:active {
background: #ccc;
}
`;
document.head.appendChild(style);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Vue from Scratch</title>
</head>
<body>
<script>
function reactive(obj) {
const listeners = new Set();
const proxy = new Proxy(obj, {
get(target, property, receiver) {
if (typeof target[property] === 'object' && target[property] !== null) {
return reactive(target[property]);
}
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
const result = Reflect.set(target, property, value, receiver);
listeners.forEach(fn => fn());
return result;
}
});
proxy.subscribe = function (fn) {
listeners.add(fn);
};
proxy.unsubscribe = function (fn) {
listeners.delete(fn);
};
return proxy;
}
class Component {
constructor(options) {
this.template = options.template;
this.data = reactive(options.data());
this.methods = options.methods;
this.style = options.style;
this.rootId = options.rootId;
// Ensure root element exists
if (!document.getElementById(this.rootId)) {
const rootElement = document.createElement('div');
rootElement.id = this.rootId;
document.body.appendChild(rootElement);
}
this.data.subscribe(this.render.bind(this));
this.render();
}
compileTemplate(template) {
const match = template.match(/{{\s*(\w+)\s*}}/g);
return () => {
let compiledTemplate = template;
if (match) {
match.forEach(item => {
const key = item.replace(/{{\s*|\s*}}/g, '');
compiledTemplate = compiledTemplate.replace(item, this.data[key]);
});
}
return compiledTemplate;
};
}
render() {
const el = document.getElementById(this.rootId);
if (el) {
el.innerHTML = this.compileTemplate(this.template)();
this.applyMethods(el);
}
}
applyMethods(el) {
Object.keys(this.methods).forEach(methodName => {
const matches = el.querySelectorAll(`[data-action="${methodName}"]`);
matches.forEach(match => {
match.onclick = this.methods[methodName].bind(this.data);
});
});
}
}
const MyComponent = new Component({
template: `
<div class="my-component">
<div class="container">
<p class="message">Count: {{ count }}</p>
<div class="buttons">
<button data-action="increment">Increment</button>
<button data-action="decrement">Decrease</button>
</div>
</div>
</div>
`,
data() {
return {
count: 0,
};
},
methods: {
increment() {
this.count += 1;
},
decrement() {
this.count -= 1;
},
},
style: `
.my-component {
font-family: Arial, sans-serif;
text-align: center;
padding: 50px;
background: #f0f0f0;
}
.my-component .container {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.my-component .message {
font-size: 24px;
margin-bottom: 20px;
}
.my-component .buttons button {
font-size: 16px;
padding: 10px 20px;
margin: 5px;
cursor: pointer;
border: none;
border-radius: 5px;
transition: background 0.3s;
}
.my-component .buttons button:hover {
background: #ddd;
}
.my-component .buttons button:active {
background: #ccc;
}
`,
rootId: 'root' // Specify the ID for the root element
});
// Ensure CSS is applied to the component
const style = document.createElement('style');
style.textContent = MyComponent.style;
document.head.appendChild(style);
</script>
</body>
</html>
@fyodorio
Copy link
Copy Markdown

fyodorio commented Nov 5, 2024

It's funny that Angular version appears to be the most concise among others, which is not something anyone would expect πŸ˜… (especially as it's not Angular but rather AngularJS, the older, heavier version β€” and the same for Vue though, the good ol' Vue 2)

@Tofu-Xx
Copy link
Copy Markdown

Tofu-Xx commented Nov 20, 2024

I would like to introduce what I wrote
https://github.com/tofu-xx/vueey
Achieve the most core functions with the least amount of code

function Vue(opt) {
  let _active;
  const _deps = {};
  const $el = opt.el?.at ? document.querySelector(opt.el) : opt.el ?? document;
  const that = Object.assign(new Proxy(typeof opt.data == 'object' ? opt.data : opt.data?.() ?? {}, {
    get: (...args) => [Reflect.get(...args), (_deps[args[1]] ??= new Set()).add(_active)][0],
    set: (...args) => [Reflect.set(...args), _deps[args[1]]?.forEach(f => f?.())][0],
  }), opt.methods, { $el, $refs: {} });
  const thatKeyRex = new RegExp(Object.keys(that).join('\\b|').replaceAll('$', '\\$'), 'g');
  const toExpression = (raw) => new Function('$event', 'return ' + raw.replace(thatKeyRex, k => 'this.' + k));
  const walk = (walker, effect) => {
    const node = walker.currentNode;
    const { nodeType, data: tem } = node;
    if (nodeType == 1) for (const { name, value: raw } of node.attributes) {
      const bindName = name.slice(1);
      if (name[0] == '@')
        node['on' + bindName] = (/[^\s\w$]/.test(raw) ? toExpression(raw) : that[raw.trim()])?.bind(that);
      if (name[0] == ':')
        effect(() => node.setAttribute(bindName, node[bindName] = toExpression(raw).call(that)));
      name == 'ref' && (that.$refs[raw] = node);
    }
    if (nodeType == 3)
      effect(() => node.data = tem.replace(/\{\{(.*?)\}\}/g, (_, raw) => toExpression(raw).call(that)));
    walker.nextNode() ? walk(walker, effect) : opt.mounted?.call(that);
  };
  walk(document.createTreeWalker($el, 7), fn => (_active = fn, fn()));
  for (const [key, fn] of Object.entries(opt.watch ?? {})) {
    let oldVal = that[key];
    _deps[key].add(() => Promise.resolve().then(() => {
      const val = that[key];
      fn.call(that, val, oldVal);
      oldVal = val;
    }));
  }
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment