turbo-web 4.2.6 → 4.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -0
- package/dist/turbo.js +61 -2
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# TURBO-WEB FRAMEWORK
|
|
2
|
+
|
|
3
|
+
## I. OVERVIEW
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Lightweight JavaScript framework based on a book "Build a **Frontend Web Framework** (from scratch)" by **Angel Sola Orbaiceta** with additional features sprinkled on top.
|
|
8
|
+
|
|
9
|
+
[//]: # (Installation instructions.)
|
|
10
|
+
## II. INSTALLATION
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
There are 4 ways to utilize this framework: **NPM installation**, **SETUP script** (NODE), **locally** and through **CDN**.
|
|
14
|
+
### (1) NPM Installation
|
|
15
|
+
To install Turbo-Web framework, run in your terminal ```npm install turbo-web```
|
|
16
|
+
|
|
17
|
+
### (2) NODE initialization script
|
|
18
|
+
To install the framework with pre-configured templates using CLI, run in your terminal ```npx turbo-web PROJECT_NAME```
|
|
19
|
+
This will create a starting kit: project directory, package.json, index.html + src/ with router.js, main.js and store.js
|
|
20
|
+
|
|
21
|
+
// script exists under bin/turbo-charge.js
|
|
22
|
+
|
|
23
|
+
### (3) Locally
|
|
24
|
+
To import the framework locally - you must include the bundled file located in ```./framework/runtime/dist/turbo.js``` as imports
|
|
25
|
+
|
|
26
|
+
// This file can be generated using script ```npm run build```
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
|
|
30
|
+
```'import {createApp, defineComponent, h, hFragment,} from '../../../framework/runtime/dist/turbo.js'```
|
|
31
|
+
|
|
32
|
+
### (4) Using CDN (Content Delivery Network)
|
|
33
|
+
You can import any version of the framework directly into your JS file:
|
|
34
|
+
|
|
35
|
+
```import {createApp, defineComponent, h, hFragment,} from 'https://unpkg.com/turbo-web'```
|
|
36
|
+
|
|
37
|
+
CDN Version control ( version 1x.2x.3x):
|
|
38
|
+
1. x MAJOR: @1 or @^1.0.0 - fetches latest compatible minor and path release
|
|
39
|
+
2. x MINOR: @1.2 or @~1.2.0 - fetches latest compatible patch release of a certain MINOR version 0.X.0
|
|
40
|
+
3. x PATCH: @1.2.3 - fetches a specific build with full version control
|
|
41
|
+
|
|
42
|
+
``````import ... from 'https://unpkg.com/turbo-web@2'`````` - will import latest minor + patch version of 2.X.X.
|
|
43
|
+
|
|
44
|
+
[//]: # (A "Getting Started" guide.)
|
|
45
|
+
## III. GETTING STARTED
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
### [1] IMPORT
|
|
50
|
+
1. (2.) NPM installation & NODE script: Import from packages:
|
|
51
|
+
> import { createApp, defineComponent, h, hFragment } from 'turbo-web'
|
|
52
|
+
|
|
53
|
+
3. (4.) Locally & CDN: You must use relative paths (./ or ../):
|
|
54
|
+
> import { createApp, defineComponent, h, hFragment } from './framework/runtime/index.js'
|
|
55
|
+
|
|
56
|
+
### [2] LAUNCH
|
|
57
|
+
|
|
58
|
+
You can run server using ```npm run serve:examples```.
|
|
59
|
+
|
|
60
|
+
If you change code inside **examples/** make sure to do a hard reset for changes to apply:
|
|
61
|
+
> F12 -> right-click on the REFRESH button -> Empty Cache & Hard Reload OR ( Ctrl + Shift + R )
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
[//]: # (A detailed explanation of each feature with code examples.)
|
|
65
|
+
## IV. FEATURES
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
### [CORE] Feature 1: Declarative UI
|
|
70
|
+
Using declarative methods to define final UI instead of manually handling DOM elements. "What the UI looks like vs step-by-step imperative programming"
|
|
71
|
+
|
|
72
|
+
### [CORE] Feature 2: Virtual DOM
|
|
73
|
+
Inversed Control principle - the user doesn't need to manipulate the DOM directly, the framework creates a lightweight
|
|
74
|
+
JavaScript object tree that represents the UI.
|
|
75
|
+
|
|
76
|
+
### [CORE] Feature 3: Reconciliation Algorithm
|
|
77
|
+
Inversed Control principle - optimizing DOM manipulations by the process of diffing and patching only the required nodes (improves DOM re-rendering & re-painting).
|
|
78
|
+
|
|
79
|
+
### [CORE] Feature 4: Components
|
|
80
|
+
Encapsulation & Reusability principles: making UI building blocks with better combined hierarchical tree structure.
|
|
81
|
+
|
|
82
|
+
### Feature 5: Client-Side Router (#hash-based)
|
|
83
|
+
Allows for the **SPA design** (Single Page Application) with route guards (fn: checkNavigation) and a common "catch-all route" (cases: route not found). Based on a hash part of the URL, implemented with regex pattern matching by simulating URL path, parameters and query as part of the hash fragment itself.
|
|
84
|
+
// router.js, route-matchers.js
|
|
85
|
+
|
|
86
|
+
### Feature 6: Centralized Store
|
|
87
|
+
Separation of Concerns & Single Source of Truth principles: Allows for a global state handling and eliminates the need for prop drilling.
|
|
88
|
+
...
|
|
89
|
+
|
|
90
|
+
### Feature 7: Persistent Storage
|
|
91
|
+
Single Source of Truth principle: App's state is persistent between sessions.
|
|
92
|
+
...
|
|
93
|
+
|
|
94
|
+
...
|
|
95
|
+
|
|
96
|
+
### Feature X: Node-based Initialization through CLI
|
|
97
|
+
Scaffolding setup script that creates base structure with required templates for the App.
|
|
98
|
+
|
|
99
|
+
Script: ```npx turbo-web ${PROJECT_NAME}``` creates "Project" & src directory + main.js, router.js, store.js + package.json
|
|
100
|
+
|
|
101
|
+
[//]: # (Best practices and guidelines for building applications with the framework.)
|
|
102
|
+
## V. BEST PRACTICES
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### Routing
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
```const routes = [{ path: '/', component: Home }, {}, {}]```
|
|
110
|
+
```const router = new HashRouter(routes)```
|
|
111
|
+
```await router.init()```
|
|
112
|
+
```#isInitialized```
|
|
113
|
+
|
|
114
|
+
### Components
|
|
115
|
+
...
|
|
116
|
+
|
|
117
|
+
### Centralized Store
|
|
118
|
+
Must be initialized in a separate file (store.js).
|
|
119
|
+
```
|
|
120
|
+
export const store = new Store({
|
|
121
|
+
state: {
|
|
122
|
+
count: 0,
|
|
123
|
+
user: null
|
|
124
|
+
},
|
|
125
|
+
mutations: {
|
|
126
|
+
increment(state, payload = 1) {
|
|
127
|
+
state.count += payload
|
|
128
|
+
},
|
|
129
|
+
setUser(state, user) {
|
|
130
|
+
state.user = user
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
package/dist/turbo.js
CHANGED
|
@@ -1050,6 +1050,9 @@ function defineComponent({ render, state, ...methods }) {
|
|
|
1050
1050
|
get appContext() {
|
|
1051
1051
|
return this.#appContext
|
|
1052
1052
|
}
|
|
1053
|
+
get $http() {
|
|
1054
|
+
return this.appContext?.http;
|
|
1055
|
+
}
|
|
1053
1056
|
get elements() {
|
|
1054
1057
|
if (this.#vdom == null) {
|
|
1055
1058
|
return []
|
|
@@ -1172,10 +1175,11 @@ const RouterLink = defineComponent({
|
|
|
1172
1175
|
const RouterOutlet = defineComponent({
|
|
1173
1176
|
state() {
|
|
1174
1177
|
return {
|
|
1175
|
-
matchedRoute:
|
|
1178
|
+
matchedRoute: null,
|
|
1176
1179
|
}
|
|
1177
1180
|
},
|
|
1178
1181
|
onMounted() {
|
|
1182
|
+
this.updateState({ matchedRoute: this.appContext.router.matchedRoute });
|
|
1179
1183
|
this.boundHandler = this.handleRouteChange.bind(this);
|
|
1180
1184
|
this.appContext.router.subscribe(this.boundHandler);
|
|
1181
1185
|
},
|
|
@@ -1193,4 +1197,59 @@ const RouterOutlet = defineComponent({
|
|
|
1193
1197
|
},
|
|
1194
1198
|
});
|
|
1195
1199
|
|
|
1196
|
-
|
|
1200
|
+
class Store {
|
|
1201
|
+
#state = {}
|
|
1202
|
+
#initialState = {}
|
|
1203
|
+
#mutations = {}
|
|
1204
|
+
#actions = {}
|
|
1205
|
+
#dispatcher = new Dispatcher()
|
|
1206
|
+
#storageKey = 'turbo_state'
|
|
1207
|
+
constructor({ state = {}, mutations = {}, actions = {} }) {
|
|
1208
|
+
this.#initialState = structuredClone(state);
|
|
1209
|
+
const saved = localStorage.getItem(this.#storageKey);
|
|
1210
|
+
this.#state = saved ? JSON.parse(saved) : state;
|
|
1211
|
+
this.#mutations = mutations;
|
|
1212
|
+
this.#actions = actions;
|
|
1213
|
+
window.addEventListener('storage', (event) => {
|
|
1214
|
+
if (event.key === this.#storageKey && event.newValue) {
|
|
1215
|
+
this.#state = JSON.parse(event.newValue);
|
|
1216
|
+
this.#dispatcher.dispatch('state-change', this.state);
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
get state() {
|
|
1221
|
+
return structuredClone(this.#state)
|
|
1222
|
+
}
|
|
1223
|
+
commit(mutationName, payload) {
|
|
1224
|
+
const mutation = this.#mutations[mutationName];
|
|
1225
|
+
if (typeof mutation !== 'function') {
|
|
1226
|
+
console.warn(`[Store] Mutation "${mutationName}" does not exist.`);
|
|
1227
|
+
return
|
|
1228
|
+
}
|
|
1229
|
+
mutation(this.#state, payload);
|
|
1230
|
+
localStorage.setItem(this.#storageKey, JSON.stringify(this.#state));
|
|
1231
|
+
this.#dispatcher.dispatch('state-change', this.state);
|
|
1232
|
+
}
|
|
1233
|
+
async dispatch(actionName, payload) {
|
|
1234
|
+
const action = this.#actions[actionName];
|
|
1235
|
+
if (typeof action !== 'function') {
|
|
1236
|
+
console.warn(`[Store] Action "${actionName}" does not exist.`);
|
|
1237
|
+
return
|
|
1238
|
+
}
|
|
1239
|
+
const context = {
|
|
1240
|
+
commit: this.commit.bind(this),
|
|
1241
|
+
state: this.state
|
|
1242
|
+
};
|
|
1243
|
+
return await action(context, payload)
|
|
1244
|
+
}
|
|
1245
|
+
subscribe(handler) {
|
|
1246
|
+
return this.#dispatcher.subscribe('state-change', handler)
|
|
1247
|
+
}
|
|
1248
|
+
clear() {
|
|
1249
|
+
localStorage.removeItem(this.#storageKey);
|
|
1250
|
+
this.#state = structuredClone(this.#initialState);
|
|
1251
|
+
this.#dispatcher.dispatch('state-change', this.state);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
export { DOM_TYPES, HashRouter, RouterLink, RouterOutlet, Store, createApp, defineComponent, h, hFragment, hSlot, hString, nextTick };
|