Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Understand wtf its we want to accomplish #1

Merged
merged 9 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore

# Logs

logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Caches

.cache

# Diagnostic reports (https://nodejs.org/api/report.html)

report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# Runtime data

pids
_.pid
_.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover

lib-cov

# Coverage directory used by tools like istanbul

coverage
*.lcov

# nyc test coverage

.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)

.grunt

# Bower dependency directory (https://bower.io/)

bower_components

# node-waf configuration

.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)

build/Release

# Dependency directories

node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)

web_modules/

# TypeScript cache

*.tsbuildinfo

# Optional npm cache directory

.npm

# Optional eslint cache

.eslintcache

# Optional stylelint cache

.stylelintcache

# Microbundle cache

.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history

.node_repl_history

# Output of 'npm pack'

*.tgz

# Yarn Integrity file

.yarn-integrity

# dotenv environment variable files

.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)

.parcel-cache

# Next.js build output

.next
out

# Nuxt.js build / generate output

.nuxt
dist

# Gatsby files

# Comment in the public line in if your project uses Gatsby and not Next.js

# https://nextjs.org/blog/next-9-1#public-directory-support

# public

# vuepress build output

.vuepress/dist

# vuepress v2.x temp and cache directory

.temp

# Docusaurus cache and generated files

.docusaurus

# Serverless directories

.serverless/

# FuseBox cache

.fusebox/

# DynamoDB Local files

.dynamodb/

# TernJS port file

.tern-port

# Stores VSCode versions used for testing VSCode extensions

.vscode-test

# yarn v2

.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# IntelliJ based IDEs
.idea

# Finder (MacOS) folder config
.DS_Store
20 changes: 20 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Copyright (c) 2024 Cyrus Ndirangu and others.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
191 changes: 189 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,192 @@
# Syronjs

> This is a hobby project exploring the in depths of Javascript got the motivation from the React compiler.
> This is a hobby framework project exploring the in depths of Javascript got the motivation from the React compiler.

- The inspiration behind this project is the exploration of the how DOM manipulation and business logic can integrate seamlessly.
- The inspiration behind this project is the exploration of the how DOM manipulation and business logic can integrate seamlessly.

### Why?

- Working frameworks has always been a mystery to me:
1. How do they work internally?
1. How do they re-render a component?
1. How do they only update parts of the DOM that change?
1. How does the URL change without a page request to the server?

- These days its uncommon to find applications written in vanilla JavaScript. This is because modern frameworks boost productivity and make it easier to build complex applications.

> It's worth noting frontend frameworks are tools; a means to an end, the end being efficiently building and maintaining (complex) applications.

## Shortcomings?

1. We will only support the [standard HTML namespace](https://www.w3.org/1999/xhtml/) - this means we will expect HTML elements to be defined within the namespace.
1. We will not support [component-scoped CSS](https://www.gatsbyjs.com/docs/how-to/styling/css-modules/). [More on what that is](https://github.com/css-modules/css-modules). Which means our CSS will be global, and class names have to be unique.


## Features
1. [] Virtual DOM Abstraction.
1. [] Reconciliation Algorithm.
1. [] Component-Based Architecture, where each component:
- [] Has its own state
- [] Has its own lifecycle methods
- [] Re-renders itself and its children when its state changes
1. [] A(n) (SPA) Single Page Application Router, that updates the URL without a page request to the server.
1. [] Slots to render content inside a component.
1. [] HTML templates that compile to JavaScript render functions.
1. [] Server Side Rendering (SSR) - Renders the view on the server instead of the browser for faster page loads and better SEO.

#### Framework vs Library

- When you use a library you use its code and call its functions. When you use a framework, you write the code the framework calls.
- The framework is in charge of running the application, and executes your code appropriately.
- I realized I was a framework fanboy but have no idea how they work internally. Like what the hell is a virtual DOM? You mean there's a `reconciliation algorithm` (how browser decides what in the DOM needs updating)?

- The goal of this project si to borrow ideas from Vue , Svelte, React, Preact and Angular just to name a few. It's to build a simple framework that includes some if not most of the features of these frameworks.

- It's worth noting [Svelte does not use the Virtual DOM](https://svelte.dev/blog/virtual-dom-is-pure-overhead), and considers it to be pure overhead.

<center><video width="320" height="240" controls src="./docs/im-fast.mp4" alt="I'M FAST AF BOIII"/></center>

- But we'll use the [virtual DOM because its really fast](https://www.youtube.com/watch?v=x7cQ3mrcKaY). The virtual DOM is a lightweight copy of the actual DOM. When the state of the application changes, the virtual DOM is updated instead of the actual DOM. The virtual DOM is then compared to the actual DOM and only the differences are updated. This is what makes the virtual DOM so fast.

- For instance, consider the following HTML markup:

```html
<div class="name">
<label for="name-input">Name</label>
<input type="text" id="name-input" />
<button>Save</button> <!-- Its worth noting that saveName() event handler is typically not shown in the HTML markup, but added programmatically -->
</div>
```

> The `reconciliation algorithm` is the process that decides what changes need to be made to the browser's DOM to bring it in sync with the virtual DOM. The algorithm is responsible for updating the browser's DOM efficiently.

- Below is a vDOM (Virtual DOM) representation of the HTML markup above:

![Virtual DOM](./docs/vdom.png)


#### Hydration
- This is the process by which a framework matches HTML elements with corresponding virtual DOM nodes and attach event handlers to make the HTML interactive on the browser.
- The `hydration algorithm` binds the browser to the virtual DOM, and is responsible for updating the browser's DOM efficiently. SSR requires a server to run, but serving static files is cheaper than rendering pages as users request them.

- It's not React, but its something alright. Below is the architecture of the framework:

![SyronJS Framework Architecture](./docs/syronjs-arch.png)

- We will add a [Webpack](https://webpack.js.org/) loader module to compile HTML templates to JavaScript render functions. This will allow us to write HTML templates and have them compiled to JavaScript render functions. Which means instead of writing this:

```javascript
function render() {
return h('div', { class: 'container' }, [
h('h1', {}, ['Look, Ma!']),
h('p', {}, ["I'm building a framework!"])
// h() is a function that creates a virtual DOM node
])
}

```

- We can write this:

```html
<div class="container">
<h1>Look, Ma!</h1>
<p>I'm building a framework!</p>
</div>
```

## How frontend frameworks work
- Let's briefly learn how frontend frameworks work, from outside.

### Developer Story:
- A new project is created from the framework's CLI tool or by manually installing the dependencies and configuring the project.

#### SideNote: NodeJS
- A frontend project is a regular NodeJS project. Using NodeJS helps by providing an infrastructure to `run scripts`, `compile`, and `bundle` our code as well as managing dependencies. We could forego the whole process and use a CDN., and manage dependencies manually - an inconvinience.
- `Components` are created and they define a part of the application's view, and how the user interacts with it. Components are written in HTML, CSS and JavaScript code.
- Most frameworks use the Single FIle Component (SFC) format, where the HTML, CSS and JavaScript code are in the same file. This makes it easier to manage the component's code.
- The exception is `Angular`, which uses three separate files for the HTML, CSS and TypeScript code, allowing for better separation of languages and concerns imo.
- `React` & `Preact` use JSX - a syntax extension for JavaScript that looks like HTML - instead of writing HTML code.
- `Svelte`, `Vue` and `Angular`, use HTML templates with `directives` to 'add' or 'modify' the behaviour of the DOM elements such as, iterating over and displaying an array of items, or conditionally rendering an element.
- Check out some Vue and Angular examples below:
```javascript
// Vue
<p v-if="salamu">
Au Sio
</p>

// Angular
{#if salamu}
<p>Au Sio</p>
{/if}

// React
{salamu && <p>Au Sio</p>}
```
- The code is thhen bundled into fewer files than originally written, to enable the browser to load the application faster with fewer requests. This is done using a bundler like Webpack or Rollup.
- The files can also be minified to reduce their size, by removing whitespace and comments, and renaming variables to shorter names. This process is called `building`.

- Most of the work of building the application is usually done by the framework itself., which provides a CLI toolto build the app with a simple script like `npm run build`.
- What is involved in building:
1. The template for each component is transformed - by the template compiler - into JS code, that, executed in the browser, creates the component's view.
1. The components' code - split in multiple files - is transformed into a single JS file `app.bundle.js`. However for larger applications ot's no tincommon to have multiple bundles and lazily load them.
1. The 3rd party code used by the application is bundled into a separate file `vendor.bundle.js`. This file includes the code for the framework itself, along with 3rd party libs. This file is cached by the browser and is not re-downloaded when the application is updated.
1. The CSS code is in the components is extracted and bundled into a single file `bundle.css`. This file is loaded by the browser and used to style the application (Can be multiple files).
1. The HTML file that will be served to the user `index.html` is generated or copied from the static assets directory.
1. The static assets, such as images, fonts, and other files, are copied to the output directory, can optionally be preprocessed, minified, or optimized to a different format.

- From the above we can see a typical build process yields 4 files (or more for larger apps):
1. `index.html` - The entry point of the application.
1. `app.bundle.js` - The application's code.
1. `vendor.bundle.js` - The framework's code and 3rd party libraries.
1. `bundle.css` - The application's styles.

- It's worth noting, when a file is `statically` served, the server doesn't need to do anything before sending it to the user, the server simply reads the file from disk and sends it to the user's browser.
- Below is a simple illustration of the build process:

![Build Process](./docs/build-process.png)

### Browser Story:
- In an SPA, the server responds with a - mostly empty - HTML file that's used to load application's JavaScript and CSS files. The framework will then create and update the application's view using the [`Document API`](https://developer.mozilla.org/en-US/docs/Web/API/Document) and the [`CSS Object Model (CSSOM) API`](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model).
- A router makes sure not to reload the entire application when the user navigates to a different URL, but rather updates the view to show the new content.
- `Mounting` - The process of attaching the application to the DOM - is done by the framework, which creates the root component and attaches it to the DOM.

- The framework is in charge of updating only the parts of the HTML that need to be updated in a process called `patching` the DOM. A single change to the DOM is called a `patch`.

- Making changes to the DOM is always `expensive`, it's the framework's role to minimize the number of changes made to the document. [Here is a blog post that explains why DOM manipulation is expensive](https://web.dev/learn/performance/understanding-the-critical-path). How any of this is achieved varies across frameworks, some use a vDOM.

#### How frameworks update the view
- As of 24/05/2024:
- Svelte understands the ways the view can be updated at compilation time, and produces JS code to update the exact parts of the view that need to be patched for each possible state change. It is remarkably performant since it does the least amount of work in the browser to update the view.

- Angular runs a `change detection routine` - comparing the last state it used to render the view with the current state - everytime it detects the state might have changed.

- Changes to the state of a Component typically happen when an event listener runs, when data is requested to a server via a HTTP requesr or when `MacroTasks` (such as setTimeout() or setInterval()) or `MicroTasks` (such as Promise.then()) are executed.

- Angular makes this possible by using `Zone.js` - a library that monkey patches the browser's APIs to detect when a task is run, and then runs the change detection routine - an execution context that's aware of the asynchronous tasks running at any given time.

- [JavaScript Event Loop](https://javascript.info/event-loop), is a good resource to understand how the JS event loop works and the difference between micro and macro tasks.

- Most frameworks use a vDOM representation of the view, by comparing the last known vDOM with the DOM after a state change, they compute the minimum changes required to update the HTML.

- React does this vDOM comparison every time the state is changed by the component using either `setState()` or `useState()` hook's mechanism.
- Vue uses a remarkable approach imo and includes a reactivity layer the developer can use to define the application's state. These reactivity primitives wrap regular JS objects (arrays or sets), and primitives (strings, numbers, booleans) and when the state changes, notify the components that depend on it to re-render.

#### Routes
- An SPA works with a single HTML file where the markup code is updated programmatically by the framework, so new HTML pages are not requested from the server, when a user navigates to a different route.
- The illusion of multiple pages is created by the framework, which renders the components configured for each route.

#### Server Side Rendering (SSR) & Hydration
- A server-side rendered application is a web application that renders the HTML markup on the server and sends it to the browser. This means there needs to be a backend that handles requests and renders HTML pages.
- In the browser, the frontend code is responsible for handling user interactions and updating the view to reflect the changes when a user navigates to a different route. However, when a user navigates to a different route, the browser requests a new HTML page from the server instead of programmatically updating HTML markup.
- Here is an illustration of what that looks like:

![SSR](./docs/ssr-flow.png)

- The server generates the page each time its requested. The HTML file served to the user displays already rendered HTML markup, negating the need of using the Document API to generate it programmatically.
- Its worth noting the HTML from the server lacks interactivity (event listeners).

![Hydration Meme](./docs/hydration-meme.png)

- The HTML instructs the browser to load the application JS and CSS files, once JS code is parsed, the framework code connects the existing HTML (produced in the server), to the component's vDOM and attach event handlers. Afterwards can the user interact with the page.
- When a user interacts with the page, the frameworks event handlers are triggered and the framework patches parts of the HTML that need to be updated. All this happens in the browser.
Binary file added bun.lockb
Binary file not shown.
Binary file added docs/build-process.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/hydration-meme.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/hydration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/im-fast.mp4
Binary file not shown.
Binary file added docs/ssr-flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/syronjs-arch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/vdom.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading