vaderjs 1.4.1-lv56aadeg5 → 1.4.2-bml56

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.
@@ -0,0 +1,235 @@
1
+ /**
2
+ * @fileoverview - A simple router for vaderjs - Kuai
3
+ * @version - 1.0.0
4
+ */
5
+ export class Kuai{
6
+ constructor(config = { container: '#app'}){
7
+ this.routes = [];
8
+ this.middleware = [];
9
+ this.container = config.container ? config.container : document.getElementById('app');
10
+ this.renderPlugins = [];
11
+ }
12
+
13
+ res = {
14
+ /**
15
+ * @description render text to the container
16
+ * @param {string} data
17
+ */
18
+ text: (data) => {
19
+ this.container.innerHTML = data;
20
+ },
21
+ /**
22
+ * @method html
23
+ * @description render html to the container
24
+ * @param {any} data
25
+ * @returns
26
+ */
27
+ html: (data) => {
28
+ switch(typeof data){
29
+ case 'function':
30
+ if(this.renderPlugins.length > 0){
31
+ this.renderPlugins.forEach((plugin) => {
32
+ if(plugin.for === 'html'){
33
+ console.log('Plugin', plugin)
34
+ plugin.plugin(data, this.container)
35
+ }
36
+ })
37
+ return;
38
+ }
39
+ this.container.innerHTML = data();
40
+ break;
41
+ case 'string':
42
+ this.container.innerHTML = data;
43
+ break;
44
+ }
45
+
46
+ },
47
+ /**
48
+ * @method json
49
+ * @description render json to the container
50
+ * @param {Object} data
51
+ */
52
+ json: (data) => {
53
+ this.container.innerHTML = `<Oject data=${JSON.stringify(data)}></Object>`
54
+ }
55
+ }
56
+ req = {
57
+ /**
58
+ * @method navigate
59
+ * @description - navigate to a new route
60
+ * @param {string} path
61
+ */
62
+ navigate: (path) => {
63
+ window.history.pushState({}, '', path);
64
+ let currentPath = this.match(window.location.pathname.replace('/index.html', ''))
65
+ if(currentPath){
66
+ this.currentRoute = currentPath.path
67
+ currentPath.callback(this.res, currentPath.params, this.extractQueryParams(window.location.search));
68
+ }
69
+ },
70
+ /**
71
+ * @method back
72
+ * @description - go back to the previous route
73
+ */
74
+ back: () => {
75
+ window.history.back();
76
+ },
77
+
78
+ /**
79
+ * @method forward
80
+ * @description - go forward to the next route
81
+ * @returns {void}
82
+ * **/
83
+ forward: () => {
84
+ window.history.forward();
85
+ },
86
+ url: window.location
87
+ }
88
+ /**
89
+ * @private
90
+ */
91
+ extractQueryParams(path){
92
+ let params = new URLSearchParams(path);
93
+ let query = {};
94
+ for(let param of params){
95
+ query[param[0]] = param[1];
96
+ }
97
+ return query;
98
+ }
99
+ /**
100
+ * @private
101
+ */
102
+ extractParams(routePath, currentPath){
103
+ const routeParts = routePath.split('/').filter((part) => part !== '');
104
+ const hashParts = currentPath.split('/').filter((part) => part !== '');
105
+ const params = {};
106
+ routeParts.forEach((part, index) => {
107
+ if (part.startsWith(':')) {
108
+ const paramName = part.slice(1);
109
+ params[paramName] = hashParts[index];
110
+ }else if(part.startsWith('*')){
111
+ let array = hashParts.slice(index)
112
+ array.forEach((i, index)=>{
113
+ params[index] = i
114
+ })
115
+ };
116
+ });
117
+ return params;
118
+ }
119
+ use(path, middleware){
120
+ this.middleware.push({path, middleware});
121
+ }
122
+ /**
123
+ * @method usePlugin
124
+ * @description - add a plugin to handle how the route should be rendered
125
+ * @param {Function} plugin
126
+ * @param {('html')} method
127
+ */
128
+ usePlugin(plugin, method){
129
+ this.renderPlugins.push({plugin, for: method})
130
+ }
131
+ /**
132
+ * @method match
133
+ * @description - match a route to the current path and return the route object
134
+ * @param {string} route
135
+ * @returns {Object} - {path: string, callback: Function, params: Object}
136
+ */
137
+ match(hash){
138
+ hash = hash.endsWith('/') ? hash.slice(0, -1) : hash;
139
+ hash.includes('index.html') ? hash = hash.replace('index.html', '') : null;
140
+ if(hash.includes('?')){
141
+ hash = hash.split('?')[0]
142
+ }
143
+ let route = this.routes.find((route) => {
144
+ if (route.path === hash) {
145
+ return true;
146
+ }
147
+
148
+ if(hash === '' && route.path === '/'){
149
+ return true
150
+ }
151
+
152
+
153
+ if (route.path.includes('*') || route.path.includes(':')) {
154
+ const routeParts = route.path.split('/').filter((part) => part !== '');
155
+ const hashParts = hash.split('/').filter((part) => part !== '');
156
+ if(this.basePath){
157
+ hashParts.shift();
158
+ }
159
+ if (routeParts.length !== hashParts.length && !route.path.endsWith('*')) {
160
+ return false;
161
+ }
162
+
163
+ for (let index = 0; index < routeParts.length; index++) {
164
+ const routePart = routeParts[index];
165
+ const hashPart = hashParts[index];
166
+
167
+
168
+ if (routePart.startsWith(':') || routePart.startsWith('*')) {
169
+
170
+ continue;
171
+ }
172
+
173
+ if (routePart !== hashPart) {
174
+ return false;
175
+ }
176
+ }
177
+
178
+
179
+ return true;
180
+ }
181
+
182
+ });
183
+ if(route){
184
+ let params = this.extractParams(route.path, hash)
185
+ return { ...route, params}
186
+ }
187
+ return null;
188
+
189
+ }
190
+
191
+ /**
192
+ * @description - create a new route
193
+ * @param {string} path
194
+ * @param {Function} callback
195
+ */
196
+ get(path, callback){
197
+ this.routes.push({path, callback});
198
+ }
199
+
200
+ /**
201
+ * @method listen
202
+ * @description - listen for route changes
203
+ */
204
+ listen(){
205
+ let currentPath = this.match(window.location.pathname.replace('/index.html', ''))
206
+ if(currentPath){
207
+ this.middleware.forEach((middleware) => {
208
+ if(middleware.path === currentPath.path){
209
+ middleware.middleware();
210
+ }
211
+ });
212
+ this.currentRoute = currentPath.path
213
+ let obj = {
214
+ ...this.res,
215
+ res: this.res,
216
+ req:{
217
+ ...this.req,
218
+ params: (param) => currentPath.params[param]
219
+
220
+ }
221
+ }
222
+ currentPath.callback(obj);
223
+ }
224
+ window.onpopstate = () => {
225
+ let currentPath = this.match(window.location.pathname.replace('/index.html', ''))
226
+ if(currentPath){
227
+ this.currentRoute = currentPath.path
228
+ currentPath.callback(this.res, currentPath.params, this.extractQueryParams(window.location.search));
229
+ }
230
+ }
231
+
232
+ }
233
+ }
234
+
235
+ export default Kuai;
@@ -0,0 +1,127 @@
1
+ import * as Bun from 'bun'
2
+ import { Document, DOMParser} from 'vaderjs/binaries/Kalix/index.js'
3
+ import { renderToString } from '../../server'
4
+ import fs from 'fs'
5
+ let routes = new Bun.FileSystemRouter({
6
+ dir: process.cwd() + '/pages',
7
+ style:"nextjs"
8
+ }).routes
9
+ globalThis.isNotFirstRun = false
10
+ async function generate(){
11
+ let config = await import(process.cwd() + '/vader.config.js').then((config) => { return config.default })
12
+ let provider = config?.host?.provider
13
+
14
+ let providerRoutes = []
15
+ for(var i in routes){
16
+ let path = i
17
+ let file = routes[i]
18
+ let comp = require(file).default
19
+ if(!comp){
20
+ continue;
21
+ }
22
+ let document = new Document()
23
+ let div = document.createElement('div')
24
+ div.setAttribute('id', 'root')
25
+ let dom = await renderToString(comp)
26
+ div.setContent(dom)
27
+ dom = div.toString("outerHTML")
28
+ let folder = path.split('/pages')[0]
29
+ let newPath = process.cwd() + '/build' + folder + '/index.html'
30
+ let name = comp.name || 'App'
31
+ file = file.replace(/\\/g, '/').replace('\/\/', '/').replace(process.cwd().replace(/\\/g, '/'), '').split('/pages')[1].replace('.tsx', '.js').replace('.jsx', '.js')
32
+ let isParamRoute = path.includes('[')
33
+ let baseFolder = ''
34
+ if(isParamRoute){
35
+ baseFolder = path.split('[')[0]
36
+ let providerPath;
37
+ switch(true){
38
+ case provider === 'vercel':
39
+ providerPath = `${baseFolder}:${path.split('[')[1].split(']')[0]}`
40
+ providerRoutes.push({source: providerPath, dest: `${path}/index.html`})
41
+ break;
42
+ case provider === 'nginx':
43
+ providerPath = `RewriteRule ^${baseFolder.replace('/', '')}.*$ ${path}/index.html [L]`
44
+ providerRoutes.push(providerPath)
45
+ break;
46
+ }
47
+ }else{
48
+ let providerPath;
49
+ switch(true){
50
+ case provider === 'vercel':
51
+ providerPath = `${path}`
52
+ providerRoutes.push({source: providerPath, dest: `${path}/index.html`})
53
+ break;
54
+ case provider === 'nginx':
55
+ if(i == '/'){
56
+ break;
57
+ }
58
+ providerPath = `RewriteRule ^${path.replace('/', '')}/$ ${path}/index.html [L]`
59
+ providerRoutes.push(providerPath)
60
+ break;
61
+
62
+ }
63
+ }
64
+ dom = dom + `
65
+ <script type="module">
66
+ import { render } from '/src/client.js'
67
+ import ${name} from '/pages/${file.replace(process.cwd(), '')}'
68
+ import Kuai from '/src/router.js'
69
+ let kuai = new Kuai()
70
+ kuai.get('${path}', (c) => {
71
+ render(${name}, document.getElementById('root'), c.req, c.res)
72
+ })
73
+
74
+ kuai.listen()
75
+
76
+ `
77
+ dom = dom + `</script>`
78
+ Bun.write(newPath, dom)
79
+
80
+ }
81
+ switch(true){
82
+ case provider === 'vercel':
83
+ let vercelJSON = process.cwd() + '/vercel.json'
84
+ let vercel = {
85
+ "rewrites": providerRoutes
86
+ }
87
+
88
+ fs.writeFileSync(vercelJSON, JSON.stringify(vercel))
89
+ break;
90
+ case provider === 'nginx':
91
+ let full = `
92
+ Header add x-powered-by "vaderjs"
93
+ <IfModule mod_rewrite.c>
94
+ RewriteEngine On
95
+ RewriteBase /
96
+
97
+ # If the request is not for a file or directory
98
+ RewriteCond %{REQUEST_FILENAME} !-f
99
+ RewriteCond %{REQUEST_FILENAME} !-d
100
+
101
+ # Rewrite specific paths to index.html
102
+ ${providerRoutes.join('\n')}
103
+ </IfModule>
104
+ `
105
+ fs.writeFileSync(process.cwd() + '/.htaccess', full)
106
+ break;
107
+ }
108
+ console.log(`\x1b[32mSuccess\x1b[0m - Static files generated`)
109
+
110
+ return true
111
+ }
112
+
113
+
114
+ /**
115
+ * @name Vaderjs SSG Plugin
116
+ * @description This plugin is used to generate static files from the deep object - using kalix.js
117
+ * @version 0.0.1
118
+ */
119
+ export default {
120
+ name: 'Vaderjs SSG Plugin',
121
+ version: '0.0.1',
122
+ init: async (component) => {
123
+
124
+ console.log(`\x1b[36mwait \x1b[0m - generating static files`)
125
+ return generate(component)
126
+ }
127
+ }
@@ -0,0 +1,8 @@
1
+ export default {
2
+ name: "vercel",
3
+ description: "Vercel functions",
4
+ version: "0.0.1",
5
+ init: async function (config) {
6
+ console.warn("Vercel functions are not yet supported");
7
+ }
8
+ }
@@ -0,0 +1,181 @@
1
+
2
+ //@ts-nocheck
3
+ import fs from 'fs'
4
+ const router = new Bun.FileSystemRouter({
5
+ dir: process.cwd() + '/pages',
6
+ style:'nextjs'
7
+ })
8
+ let wsClients = []
9
+ function handleContentTypes(url: string){
10
+ let types: any = {
11
+ 'html': 'text/html',
12
+ 'css': 'text/css',
13
+ 'js': 'text/javascript',
14
+ 'json': 'application/json',
15
+ 'png': 'image/png',
16
+ 'jpg': 'image/jpg',
17
+ 'jpeg': 'image/jpeg',
18
+ 'svg': 'image/svg+xml',
19
+ 'gif': 'image/gif',
20
+ 'ico': 'image/x-icon',
21
+ 'tiff': 'image/tiff',
22
+ 'woff': 'font/woff',
23
+ 'woff2': 'font/woff2',
24
+ }
25
+
26
+ let typeArray = Array.from(Object.keys(types))
27
+
28
+ let ext = url.split('.').pop()
29
+ if(typeArray.includes(ext)){
30
+ return types[ext]
31
+ }
32
+
33
+ }
34
+
35
+
36
+ function spawn_ssr_server(config: any ){
37
+ if(!fs.existsSync(process.cwd() + '/routes')){
38
+ throw new Error('SSR requires a routes directory to be present in your project')
39
+ }
40
+ let routesRouter = new Bun.FileSystemRouter({
41
+ dir: process.cwd() + '/routes',
42
+ style: 'nextjs'
43
+ })
44
+ async function serve (req,res){
45
+ routesRouter.reload()
46
+ if(res.upgrade(req)){
47
+ return
48
+ }
49
+ let url = new URL(req.url)
50
+ if(url.pathname.includes('.')){
51
+ url.pathname = url.pathname.replace('/\/', '/').replace('/build/', '')
52
+ if(url.pathname.includes('/build')){
53
+ url.pathname = url.pathname.replace('/build', '')
54
+ }
55
+ let file = process.cwd() + '/build' + url.pathname
56
+ let fileType = handleContentTypes(file)
57
+ if(fs.existsSync(file)){
58
+ let content = await Bun.file(file).text()
59
+ return new Response(content, {status: 200, headers: {'Content-Type': fileType, 'x-powered-by': 'Vader'}})
60
+ }
61
+ }
62
+ let routeMatch = routesRouter.match(url.pathname)
63
+ if(routeMatch){
64
+ let route = require(routeMatch.filePath).default
65
+
66
+ if(route?.name !== req?.method){
67
+ return new Response('Method Not Allowed', {status: 405})
68
+ }else{
69
+ try {
70
+ let Request = {
71
+ req,
72
+ res
73
+ }
74
+ let response = await route(Request)
75
+
76
+ if(response instanceof Response){
77
+ // set x-powered-by header
78
+ response.headers.set('x-powered-by', 'Vader')
79
+ return response
80
+ }
81
+ throw new Error(`Route ${routeMatch.filePath.split('/routes')[1]} did not return a response in file ${routeMatch.filePath}`)
82
+ } catch (error) {
83
+ console.log(error)
84
+ return new Response('Internal Server Error', {status: 500})
85
+ }
86
+ }
87
+
88
+
89
+ }
90
+ }
91
+ let server = Bun.serve({
92
+ port: config.env.PORT || 3000,
93
+ hostname: config.host.hostname || 'localhost',
94
+ reusePort: true,
95
+ lowMemoryMode: true,
96
+ development: false,
97
+ websocket: {
98
+ message(event){
99
+
100
+ },
101
+
102
+ open(ws){
103
+ wsClients.push(ws)
104
+ },
105
+ },
106
+ async fetch(req,res){
107
+ return await serve(req,res)
108
+ },
109
+
110
+ error(error) {
111
+ console.log(error)
112
+ }
113
+ })
114
+
115
+ return {serve, server}
116
+ }
117
+
118
+ function spawnServer(config: any){
119
+ Bun.serve({
120
+ port: config.env.PORT || 3000,
121
+ hostname: config.host.hostname || 'localhost',
122
+ reusePort: true,
123
+ websocket: {
124
+ message(event){
125
+
126
+ },
127
+ open(ws){
128
+ wsClients.push(ws)
129
+ },
130
+ },
131
+ async fetch(req,res){
132
+ router.reload()
133
+ if(res.upgrade(req)){
134
+ return
135
+ }
136
+ let url = new URL(req.url)
137
+ if(url.pathname.includes('.')){
138
+ url.pathname = url.pathname.replace('/\/', '/')
139
+ url.pathname = url.pathname.replace('/build/', '')
140
+ if(url.pathname.includes('/build')){
141
+ url.pathname = url.pathname.replace('/build', '')
142
+ }
143
+ let file = process.cwd() + '/build' + url.pathname
144
+ let fileType = handleContentTypes(file)
145
+ if(fs.existsSync(file)){
146
+ let content = await Bun.file(file).text()
147
+ return new Response(content, {status: 200, headers: {'Content-Type': fileType, 'x-powered-by': 'Vader'}})
148
+ }
149
+ }
150
+ let route = router.match(url.pathname)
151
+ if(route){
152
+ let path = route.filePath.split('/pages')[1].replace('.jsx', '.js').replace('.tsx', '.js').replace('.ts', '.js')
153
+ let html = fs.readFileSync(process.cwd() + `/build/${path.replace('.js', '.html')}`).toString()
154
+ return new Response(html, {status: 200, headers: {'Content-Type': 'text/html', 'x-powered-by': 'Vader'}})
155
+ }
156
+ return new Response('Not Found', {status: 404})
157
+ }
158
+ })
159
+
160
+
161
+ }
162
+ /**
163
+ * @name Vader Router
164
+ * @description This plugin is responsible for routing and serving the application
165
+ */
166
+ export default {
167
+ name: 'Vader Router',
168
+ version: '1.0.0',
169
+ once: true,
170
+ init: ( ) => {
171
+ if(!globalThis.isListening){
172
+ let config = require(process.cwd() + '/vader.config.js').default
173
+ if(process.env.mode === 'production'){
174
+ console.log(`Listening at - http://${config.host.hostname}:${config.env.PORT}`)
175
+ spawnServer(config)
176
+ }
177
+ config?.env?.SSR ? spawn_ssr_server(config ) : spawnServer(config)
178
+ globalThis.isListening = true
179
+ }
180
+ }
181
+ }
@@ -0,0 +1,129 @@
1
+ import { Document, Element} from "vaderjs/binaries/Kalix"
2
+ class Component {
3
+ constructor(props){
4
+ this.props = props
5
+ this._key = Math.random().toString(36).substring(7)
6
+ this.state = {}
7
+ this.htmlNode = null
8
+ this.firstChild = null
9
+ this.Element = Element
10
+ }
11
+
12
+
13
+ setState(newState){
14
+ this.state = newState
15
+ }
16
+
17
+
18
+ useState(name, initialValue){
19
+ if(!this.state[name]){
20
+ this.state[name] = initialValue
21
+ }
22
+
23
+ let getState = () => this.state[name]
24
+ let setState = (newValue) => {
25
+
26
+
27
+ }
28
+ return [getState, setState]
29
+
30
+ }
31
+ useReducer(name, reducer, initialState){
32
+ let [state, setState] = this.useState(name, initialState)
33
+ let dispatch = (action) => {
34
+ let newState = reducer(state(), action)
35
+ setState(newState)
36
+ }
37
+
38
+ return [state, dispatch]
39
+ }
40
+
41
+ useEffect(effect, deps){
42
+
43
+ }
44
+
45
+ render(){
46
+ return null
47
+ }
48
+
49
+ }
50
+ export async function renderToString(element, args = []) {
51
+ let data = typeof element === 'function' ? await element(args) : element
52
+ let doc = new Document()
53
+ let el = doc.createElement(data)
54
+
55
+ return el.toString()
56
+
57
+ }
58
+
59
+ /**
60
+ * @description This function is used to generate the server side rendered page
61
+ * @param {Element} element
62
+ * @param {Object} options
63
+ * @param {string} options.entry - The entry file for the component (refers to /build/pages/~)
64
+ * @param {Function} options.props - The server side props function
65
+ * @param {Array} args - The arguments to pass to the props function - ie Request or any other data
66
+ * @param {*} args
67
+ * @returns
68
+ */
69
+
70
+ export async function generatePage(element, options = {entry:"", props: any}, args = []) {
71
+ let config = await import(process.cwd() + '/vader.config.js').then((config) => { return config.default })
72
+ //@ts-ignore
73
+ globalThis.isServer = true
74
+ // @ts-ignore
75
+ let serverSideProps = await options.props({req: args[0], res: args[1]}, args.slice(2))
76
+ let name = element.name
77
+
78
+ let comp = new Component(serverSideProps?.props)
79
+ element = element.bind(comp)
80
+ comp.render = (props)=> {
81
+ return element(props)
82
+ }
83
+ let data = new Document().createElement(comp.render({props: serverSideProps.props, ...args}))
84
+ let document = new Document()
85
+ document.documentElement.setContent(data.querySelector('html') ? data.querySelector('html').innerHTML : '')
86
+ document.documentElement.setAttribute('lang', data.querySelector('html') ? data.querySelector('html').getAttribute('lang') : 'en')
87
+ document.head.setContent(data.querySelector('head') ? data.querySelector('head').innerHTML : '')
88
+ data.removeChild(data.querySelector('head'))
89
+ let div = new Document().createElement('div')
90
+ div.setAttribute('id', 'app')
91
+ div.setContent(data.innerHTML)
92
+ document.body.appendChild(div)
93
+ let script = new Document().createElement('script')
94
+ script.setAttribute('type', 'module')
95
+
96
+ script.setContent(script.innerHTML + '\n' + `
97
+
98
+ import ${name} from "${options?.entry}"
99
+ import {render} from '/src/client.js'
100
+ import Kuai from '/src/router.js'
101
+ let kuai = new Kuai({container: document.getElementById('app')})
102
+ kuai.get('/', (c) => {
103
+ c.html(render(${name}, document.getElementById('app'), {...c, props: ${JSON.stringify(serverSideProps.props)}}))
104
+ })
105
+ kuai.use('/', () => console.log('Middleware'))
106
+ kuai.listen()
107
+ ${
108
+ config?.mode === 'development' ? `
109
+ let ws = new WebSocket('ws://localhost:${env.PORT || 3000}')
110
+ ws.onopen = () => {
111
+ ws.send('Hello')
112
+ }
113
+ ws.onmessage = (e) => {
114
+ if(e.data === 'reload'){
115
+ window.location.reload()
116
+ }
117
+ }
118
+ ws.onclose = () => {
119
+ console.log('Connection closed')
120
+ window.location.reload()
121
+ }
122
+ ` :''
123
+ }
124
+ `)
125
+ document.body.appendChild(script)
126
+ return `<!DOCTYPE html><html lang="${document.documentElement.getAttribute('lang')}">${document.head.toString()}${document.body.innerHTML}</html>`
127
+ }
128
+
129
+ export default renderToString