-
-
Save savage69kr/6e75e64848199e15155ec571e40bd3e3 to your computer and use it in GitHub Desktop.
Revisions
-
faustinoaq revised this gist
Oct 31, 2024 . 2 changed files with 4 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,6 +3,8 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My React from Scratch</title> </head> <body> This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,6 +3,8 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My Vue from Scratch</title> </head> <body> -
faustinoaq created this gist
Oct 31, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,144 @@ <!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> This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,264 @@ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> </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> This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,157 @@ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> </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>