turbo-web 4.2.5 → 4.2.7

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 ADDED
@@ -0,0 +1,132 @@
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/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
+
100
+ [//]: # (Best practices and guidelines for building applications with the framework.)
101
+ ## V. BEST PRACTICES
102
+
103
+ ---
104
+
105
+ ### Routing
106
+ Make sure to ...
107
+
108
+ ```const routes = [{ path: '/', component: Home }, {}, {}]```
109
+ ```const router = new HashRouter(routes)```
110
+ ```await router.init()```
111
+ ```#isInitialized```
112
+
113
+ ### Components
114
+ ...
115
+
116
+ ### Centralized Store
117
+ Must be initialized in a separate file (store.js).
118
+ ```
119
+ export const store = new Store({
120
+ state: {
121
+ count: 0,
122
+ user: null
123
+ },
124
+ mutations: {
125
+ increment(state, payload = 1) {
126
+ state.count += payload
127
+ },
128
+ setUser(state, user) {
129
+ state.user = user
130
+ }
131
+ }
132
+ ```
@@ -5,7 +5,6 @@ import '../dist/turbo.js';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
 
8
- // 1. gets the project name from the terminal command
9
8
  const projectName = process.argv[2];
10
9
 
11
10
  if (!projectName) {
@@ -14,41 +13,67 @@ if (!projectName) {
14
13
  process.exit(1);
15
14
  }
16
15
 
17
- // 2. defines the target directory path
16
+ // defines the target directory path
18
17
  const projectPath = path.join(process.cwd(), projectName);
18
+ const srcPath = path.join(projectPath, 'src');
19
19
 
20
- // 3. creates the directories
20
+ // creates the directories
21
21
  fs.mkdirSync(projectPath, { recursive: true });
22
22
  fs.mkdirSync(path.join(projectPath, 'src'), { recursive: true });
23
23
 
24
- // 4. defines the boilerplate file contents
25
- const storeContent = `import { Store } from 'your-framework';
26
-
27
- export const appStore = new Store({
28
- state: {},
29
- mutations: {},
30
- actions: {}
31
- });
32
- `;
33
-
34
- const routerContent = `export function initRouter() {
35
- window.addEventListener('hashchange', () => {
36
- const path = window.location.hash.slice(1).toLowerCase() || '/';
37
- console.log('Navigated to:', path);
38
- // Add route matching and component mounting logic here
39
- });
40
- }
41
- `;
24
+ // auto initiating package.json
25
+ const packageJsonContent = {
26
+ name: projectName,
27
+ version: "1.0.0",
28
+ type: "module",
29
+ dependencies: {
30
+ "turbo-web": "latest"
31
+ },
32
+ scripts: {
33
+ "start": "npx serve ."
34
+ }
35
+ };
36
+
37
+ fs.writeFileSync(
38
+ path.join(projectPath, 'package.json'),
39
+ JSON.stringify(packageJsonContent, null, 2)
40
+ );
41
+
42
+ // defines the boilerplate file contents
43
+ const storeContent = `import { Store } from 'turbo-web';
44
+ export const appStore = new Store({ state: { count: 0 } });`;
45
+
46
+ const routerContent = `import { HashRouter } from 'turbo-web';
47
+ export const router = new HashRouter([{ path: '/', component: null }]);`;
48
+
49
+ const mainContent = `import { createApp, h } from 'turbo-web';
50
+ import { router } from './router.js';
51
+ import { appStore } from './store.js';
52
+
53
+ const App = { render: () => h('div', {}, ['Turbo Power!']) };
54
+ createApp(App, {}, { router, store: appStore }).mount(document.body);`;
55
+
56
+ // write the files to the user's new project directory
57
+ fs.writeFileSync(path.join(srcPath, 'store.js'), storeContent);
58
+ fs.writeFileSync(path.join(srcPath, 'router.js'), routerContent);
59
+ fs.writeFileSync(path.join(srcPath, 'main.js'), mainContent);
42
60
 
43
- const mainContent = `import { appStore } from './store.js';
44
- import { initRouter } from './router.js';
61
+ // defining HTML
62
+ const htmlContent = `<!DOCTYPE html>
63
+ <html>
64
+ <head><title>${projectName}</title></head>
65
+ <body>
66
+ <script type="module" src="./src/main.js"></script>
67
+ </body>
68
+ </html>`;
45
69
 
46
- initRouter();
47
- `;
70
+ fs.writeFileSync(path.join(projectPath, 'index.html'), htmlContent);
48
71
 
49
- // 5. Write the files to the user's new project directory
50
- fs.writeFileSync(path.join(projectPath, 'src', 'store.js'), storeContent);
51
- fs.writeFileSync(path.join(projectPath, 'src', 'router.js'), routerContent);
52
- fs.writeFileSync(path.join(projectPath, 'src', 'main.js'), mainContent);
72
+ console.log(`
73
+ BUENO! [${projectName}] is TURBO charged for WEB development.
53
74
 
54
- console.log(`BUENO! You project:${projectName} is TURBO charged for web development!`);
75
+ Next steps:
76
+ 1. cd ${projectName}
77
+ 2. npm install
78
+ "You are ready for development!"
79
+ `);
package/dist/turbo.js CHANGED
@@ -1193,4 +1193,59 @@ const RouterOutlet = defineComponent({
1193
1193
  },
1194
1194
  });
1195
1195
 
1196
- export { DOM_TYPES, HashRouter, RouterLink, RouterOutlet, createApp, defineComponent, h, hFragment, hSlot, hString, nextTick };
1196
+ class Store {
1197
+ #state = {}
1198
+ #initialState = {}
1199
+ #mutations = {}
1200
+ #actions = {}
1201
+ #dispatcher = new Dispatcher()
1202
+ #storageKey = 'turbo_state'
1203
+ constructor({ state = {}, mutations = {}, actions = {} }) {
1204
+ this.#initialState = structuredClone(state);
1205
+ const saved = localStorage.getItem(this.#storageKey);
1206
+ this.#state = saved ? JSON.parse(saved) : state;
1207
+ this.#mutations = mutations;
1208
+ this.#actions = actions;
1209
+ window.addEventListener('storage', (event) => {
1210
+ if (event.key === this.#storageKey && event.newValue) {
1211
+ this.#state = JSON.parse(event.newValue);
1212
+ this.#dispatcher.dispatch('state-change', this.state);
1213
+ }
1214
+ });
1215
+ }
1216
+ get state() {
1217
+ return structuredClone(this.#state)
1218
+ }
1219
+ commit(mutationName, payload) {
1220
+ const mutation = this.#mutations[mutationName];
1221
+ if (typeof mutation !== 'function') {
1222
+ console.warn(`[Store] Mutation "${mutationName}" does not exist.`);
1223
+ return
1224
+ }
1225
+ mutation(this.#state, payload);
1226
+ localStorage.setItem(this.#storageKey, JSON.stringify(this.#state));
1227
+ this.#dispatcher.dispatch('state-change', this.state);
1228
+ }
1229
+ async dispatch(actionName, payload) {
1230
+ const action = this.#actions[actionName];
1231
+ if (typeof action !== 'function') {
1232
+ console.warn(`[Store] Action "${actionName}" does not exist.`);
1233
+ return
1234
+ }
1235
+ const context = {
1236
+ commit: this.commit.bind(this),
1237
+ state: this.state
1238
+ };
1239
+ return await action(context, payload)
1240
+ }
1241
+ subscribe(handler) {
1242
+ return this.#dispatcher.subscribe('state-change', handler)
1243
+ }
1244
+ clear() {
1245
+ localStorage.removeItem(this.#storageKey);
1246
+ this.#state = structuredClone(this.#initialState);
1247
+ this.#dispatcher.dispatch('state-change', this.state);
1248
+ }
1249
+ }
1250
+
1251
+ export { DOM_TYPES, HashRouter, RouterLink, RouterOutlet, Store, createApp, defineComponent, h, hFragment, hSlot, hString, nextTick };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turbo-web",
3
- "version": "4.2.5",
3
+ "version": "4.2.7",
4
4
  "main": "dist/turbo.js",
5
5
  "files": [
6
6
  "dist",