my-framework-almaz: A Learning Framework
my-framework-almaz (npm package `my-framework-almaz`, current stable version 2.0.7) is an educational frontend framework explicitly designed as a companion to the book "Build a frontend framework from scratch." Its primary purpose is to teach web developers the underlying mechanics of modern frontend frameworks, rather than for production application development. The framework evolves through different versions, mirroring chapters of the book, introducing core concepts like the Virtual DOM, implemented via `h()`, `hString()`, and `hFragment()` functions, and DOM manipulation functions such as `mountDOM()` and `destroyDOM()`. Early versions (like v1.0) utilized a simple state management pattern with a `Dispatcher` where any state change triggered a complete re-rendering of the entire view. Subsequent versions, starting from v2.0 (as indicated by the introduction of a 'reconciliation algorithm'), move towards more efficient partial DOM updates. Due to its pedagogical nature, it lacks the optimizations, security considerations, and robust feature set expected in production-grade frameworks. Its release cadence is likely tied to book updates and chapters.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'mountDOM')
cause The `mountDOM` function was either not imported or the package was incorrectly installed/referenced, leading to `mountDOM` being `undefined`.fixEnsure `import { mountDOM } from 'my-framework-almaz';` is present at the top of your file and the `my-framework-almaz` package is correctly installed in your `node_modules`. -
ReferenceError: h is not defined
cause The `h` function, used for creating virtual DOM nodes, was used without being imported.fixAdd `import { h } from 'my-framework-almaz';` to the top of your JavaScript/TypeScript file where `h` is utilized. -
DOM element not found (when mounting app)
cause The target DOM element specified for `mountDOM` (e.g., `document.getElementById('app-root')`) does not exist in the HTML document at the time `mountDOM` is called.fixEnsure your HTML includes the target element (e.g., `<div id="app-root"></div>`) and that `mountDOM` is called only after the DOM is fully loaded, typically within a `DOMContentLoaded` listener.
Warnings
- gotcha This framework is explicitly NOT intended for production use. It is a pedagogical tool for learning frontend framework internals and should not be deployed in live applications due to potential performance, security, and stability limitations.
- breaking Version 1.0 of the framework (corresponding to Chapter 6 of the book) implements state changes by fully destroying and recreating the DOM on every update. Later versions introduce a 'reconciliation algorithm' to efficiently update only changed parts of the DOM, which implies significant internal architectural changes and potentially different patterns for component lifecycle and updates.
- gotcha As an educational framework, `my-framework-almaz` may not prioritize security or performance optimizations typically found in production-grade libraries. Its code might be simplified to illustrate concepts rather than achieve maximum efficiency or robustness.
Install
-
npm install my-framework-almaz -
yarn add my-framework-almaz -
pnpm add my-framework-almaz
Imports
- createApp
const createApp = require('my-framework-almaz').createAppimport { createApp } from 'my-framework-almaz' - h
import h from 'my-framework-almaz'
import { h } from 'my-framework-almaz' - mountDOM
const { mountDOM } = require('my-framework-almaz')import { mountDOM } from 'my-framework-almaz' - Dispatcher
import { Dispatcher } from 'my-framework-almaz'
Quickstart
import { createApp, h, mountDOM, Dispatcher } from 'my-framework-almaz';
interface State {
message: string;
count: number;
}
const initialState: State = {
message: 'Hello, my-framework-almaz!',
count: 0,
};
let appState = { ...initialState };
const dispatcher = new Dispatcher();
const UPDATE_MESSAGE = 'UPDATE_MESSAGE';
const INCREMENT_COUNT = 'INCREMENT_COUNT';
dispatcher.subscribe(UPDATE_MESSAGE, (payload: string) => {
appState.message = payload;
});
dispatcher.subscribe(INCREMENT_COUNT, () => {
appState.count++;
});
function renderApp() {
return h('div', { id: 'my-app' }, [
h('h1', {}, appState.message),
h('p', {}, `Count: ${appState.count}`),
h('button', {
onclick: () => {
dispatcher.dispatch(INCREMENT_COUNT);
updateView();
},
}, 'Increment Count'),
h('button', {
onclick: () => {
dispatcher.dispatch(UPDATE_MESSAGE, 'Updated message from command!');
updateView();
},
}, 'Update Message'),
]);
}
const app = createApp(renderApp);
function updateView() {
const rootElement = document.getElementById('app-root');
if (rootElement) {
while (rootElement.firstChild) {
rootElement.removeChild(rootElement.firstChild);
}
mountDOM(app.render(), rootElement);
}
}
document.addEventListener('DOMContentLoaded', () => {
const root = document.createElement('div');
root.id = 'app-root';
document.body.appendChild(root);
updateView();
});