fusee-framework 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +8 -0
- package/app/components/Counter.js +38 -0
- package/app/main.js +11 -0
- package/app/routes/About.js +24 -0
- package/app/routes/Home.js +22 -0
- package/framework/core/component.js +128 -0
- package/framework/core/signal.js +52 -0
- package/framework/index.js +4 -0
- package/framework/router/router.js +69 -0
- package/index.html +103 -0
- package/package.json +20 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SebiSomu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defineComponent, signal, onMount, onUnmount } from '../../framework/index.js'
|
|
2
|
+
|
|
3
|
+
export const Counter = defineComponent({
|
|
4
|
+
props: ['initialValue'],
|
|
5
|
+
setup(props) {
|
|
6
|
+
const count = signal(Number(props.initialValue) || 0)
|
|
7
|
+
const double = signal(0)
|
|
8
|
+
|
|
9
|
+
// Manual computed: keep double in sync
|
|
10
|
+
// (could use computed() from framework too)
|
|
11
|
+
function increment() { count(count() + 1); double(count() * 2) }
|
|
12
|
+
function decrement() { count(count() - 1); double(count() * 2) }
|
|
13
|
+
function reset() { count(0); double(0) }
|
|
14
|
+
|
|
15
|
+
onMount(() => console.log('[Counter] mounted, initial:', count()))
|
|
16
|
+
onUnmount(() => console.log('[Counter] unmounted'))
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
count,
|
|
20
|
+
double,
|
|
21
|
+
increment,
|
|
22
|
+
decrement,
|
|
23
|
+
reset,
|
|
24
|
+
template: `
|
|
25
|
+
<div class="counter">
|
|
26
|
+
<h2>Counter</h2>
|
|
27
|
+
<p class="value">{{ count }}</p>
|
|
28
|
+
<p class="derived">double: {{ double }}</p>
|
|
29
|
+
<div class="actions">
|
|
30
|
+
<button @click="decrement">−</button>
|
|
31
|
+
<button @click="reset">reset</button>
|
|
32
|
+
<button @click="increment">+</button>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
`
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
})
|
package/app/main.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createRouter, mountOutlet } from '../framework/index.js'
|
|
2
|
+
import { Home } from './routes/Home.js'
|
|
3
|
+
import { About } from './routes/About.js'
|
|
4
|
+
|
|
5
|
+
createRouter([
|
|
6
|
+
{ path: '/', component: Home },
|
|
7
|
+
{ path: '/about', component: About },
|
|
8
|
+
])
|
|
9
|
+
|
|
10
|
+
const outlet = document.getElementById('app')
|
|
11
|
+
mountOutlet(outlet)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineComponent, signal, onMount } from '../../framework/index.js'
|
|
2
|
+
|
|
3
|
+
export const About = defineComponent({
|
|
4
|
+
props: [],
|
|
5
|
+
setup() {
|
|
6
|
+
const loaded = signal(false)
|
|
7
|
+
|
|
8
|
+
onMount(() => {
|
|
9
|
+
setTimeout(() => loaded(true), 300)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
loaded,
|
|
14
|
+
template: `
|
|
15
|
+
<div class="page">
|
|
16
|
+
<h1>About</h1>
|
|
17
|
+
<p>Framework custom — prototip SPA.</p>
|
|
18
|
+
<p><strong>Features:</strong> signals, defineComponent, hash router, lifecycle hooks.</p>
|
|
19
|
+
<p class="status">{{ loaded }}</p>
|
|
20
|
+
</div>
|
|
21
|
+
`
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineComponent, signal } from '../../framework/index.js'
|
|
2
|
+
import { Counter } from '../components/Counter.js'
|
|
3
|
+
|
|
4
|
+
export const Home = defineComponent({
|
|
5
|
+
props: [],
|
|
6
|
+
components: { Counter },
|
|
7
|
+
setup() {
|
|
8
|
+
const title = signal('Welcome!')
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
title,
|
|
12
|
+
template: `
|
|
13
|
+
<div class="page">
|
|
14
|
+
<h1>{{ title }}</h1>
|
|
15
|
+
<p>This is an SPA built with my own JavaScript custom framework!</p>
|
|
16
|
+
<p>Made with passion by Sebastian Șomu</p>
|
|
17
|
+
{{ Counter }}
|
|
18
|
+
</div>
|
|
19
|
+
`
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
})
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// ─── Component System ─────────────────────────────────────────────────────────
|
|
2
|
+
import { effect } from './signal.js'
|
|
3
|
+
|
|
4
|
+
// Lifecycle context — set while setup() runs
|
|
5
|
+
let currentInstance = null
|
|
6
|
+
|
|
7
|
+
export function onMount(fn) {
|
|
8
|
+
if (currentInstance) currentInstance._mountHooks.push(fn)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function onUnmount(fn) {
|
|
12
|
+
if (currentInstance) currentInstance._unmountHooks.push(fn)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function defineComponent(options) {
|
|
16
|
+
return function ComponentFactory(props = {}) {
|
|
17
|
+
const instance = {
|
|
18
|
+
props,
|
|
19
|
+
_mountHooks: [],
|
|
20
|
+
_unmountHooks: [],
|
|
21
|
+
_effects: [],
|
|
22
|
+
_element: null,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Run setup() with lifecycle context active
|
|
26
|
+
currentInstance = instance
|
|
27
|
+
const result = options.setup(props)
|
|
28
|
+
currentInstance = null
|
|
29
|
+
|
|
30
|
+
// Validate props defined in component
|
|
31
|
+
const allowedProps = options.props || []
|
|
32
|
+
for (const key of Object.keys(props)) {
|
|
33
|
+
if (!allowedProps.includes(key)) {
|
|
34
|
+
console.warn(`[framework] Unknown prop "${key}" passed to component.`)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Template rendering ───────────────────────────────────────────────────
|
|
39
|
+
function render(container) {
|
|
40
|
+
instance._element = container
|
|
41
|
+
|
|
42
|
+
const e = effect(() => {
|
|
43
|
+
const html = resolveTemplate(result.template, result, options.components || {})
|
|
44
|
+
container.innerHTML = html
|
|
45
|
+
bindEvents(container, result)
|
|
46
|
+
bindComponents(container, options.components || {})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
instance._effects.push(e)
|
|
50
|
+
|
|
51
|
+
// Run mount hooks after first render
|
|
52
|
+
for (const hook of instance._mountHooks) hook()
|
|
53
|
+
|
|
54
|
+
return instance
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── Unmount ──────────────────────────────────────────────────────────────
|
|
58
|
+
function unmount() {
|
|
59
|
+
for (const hook of instance._unmountHooks) hook()
|
|
60
|
+
// Clean up all reactive effects
|
|
61
|
+
for (const e of instance._effects) {
|
|
62
|
+
for (const dep of e.deps) dep.delete(e)
|
|
63
|
+
e.deps.clear()
|
|
64
|
+
}
|
|
65
|
+
if (instance._element) instance._element.innerHTML = ''
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { render, unmount, instance }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── Template resolver ─────────────────────────────────────────────────────────
|
|
73
|
+
// Replaces {{ expr }} with evaluated values from the component result object.
|
|
74
|
+
function resolveTemplate(template, context, components) {
|
|
75
|
+
return template.replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => {
|
|
76
|
+
if (key in components) {
|
|
77
|
+
// Nested component: {{ Counter }} → render component placeholder
|
|
78
|
+
return `<div data-component="${key}"></div>`
|
|
79
|
+
}
|
|
80
|
+
const val = context[key]
|
|
81
|
+
if (typeof val === 'function') {
|
|
82
|
+
// Could be a signal — call it to get value
|
|
83
|
+
try { return val() } catch { return '' }
|
|
84
|
+
}
|
|
85
|
+
return val ?? ''
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Event binding ─────────────────────────────────────────────────────────────
|
|
90
|
+
// Finds all elements with @event attributes and attaches listeners.
|
|
91
|
+
// Re-runs after every template re-render (innerHTML wipe clears old listeners).
|
|
92
|
+
function bindEvents(container, context) {
|
|
93
|
+
const all = container.querySelectorAll('*')
|
|
94
|
+
for (const el of all) {
|
|
95
|
+
for (const attr of [...el.attributes]) {
|
|
96
|
+
if (attr.name.startsWith('@')) {
|
|
97
|
+
const eventName = attr.name.slice(1) // @click → click
|
|
98
|
+
const handlerName = attr.value // "increment"
|
|
99
|
+
const handler = context[handlerName]
|
|
100
|
+
if (typeof handler === 'function') {
|
|
101
|
+
el.removeAttribute(attr.name)
|
|
102
|
+
el.addEventListener(eventName, handler)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── Nested component mounting ─────────────────────────────────────────────────
|
|
110
|
+
// Finds data-component placeholders and mounts child components inside them.
|
|
111
|
+
function bindComponents(container, components) {
|
|
112
|
+
const placeholders = container.querySelectorAll('[data-component]')
|
|
113
|
+
for (const placeholder of placeholders) {
|
|
114
|
+
const name = placeholder.dataset.component
|
|
115
|
+
const ComponentFn = components[name]
|
|
116
|
+
if (ComponentFn) {
|
|
117
|
+
// Read props from data-prop-* attributes if present
|
|
118
|
+
const props = {}
|
|
119
|
+
for (const attr of [...placeholder.attributes]) {
|
|
120
|
+
if (attr.name.startsWith('data-prop-')) {
|
|
121
|
+
props[attr.name.slice(10)] = attr.value
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const child = ComponentFn(props)
|
|
125
|
+
child.render(placeholder)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// ─── Reactivity Engine ───────────────────────────────────────────────────────
|
|
2
|
+
// Solid-style signals: count() to read, count(val) to write.
|
|
3
|
+
// An "effect" is any function that should re-run when signals it reads change.
|
|
4
|
+
|
|
5
|
+
let currentEffect = null
|
|
6
|
+
|
|
7
|
+
export function signal(initialValue) {
|
|
8
|
+
let value = initialValue
|
|
9
|
+
const subscribers = new Set()
|
|
10
|
+
|
|
11
|
+
function accessor(newValue) {
|
|
12
|
+
if (newValue === undefined) {
|
|
13
|
+
// READ — track dependency if inside an effect
|
|
14
|
+
if (currentEffect) {
|
|
15
|
+
subscribers.add(currentEffect)
|
|
16
|
+
currentEffect.deps.add(subscribers)
|
|
17
|
+
}
|
|
18
|
+
return value
|
|
19
|
+
} else {
|
|
20
|
+
// WRITE — notify subscribers only if value changed
|
|
21
|
+
if (newValue !== value) {
|
|
22
|
+
value = newValue
|
|
23
|
+
for (const sub of [...subscribers]) sub()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return accessor
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function effect(fn) {
|
|
32
|
+
const run = () => {
|
|
33
|
+
// Clean up previous subscriptions before re-running
|
|
34
|
+
for (const dep of run.deps) dep.delete(run)
|
|
35
|
+
run.deps.clear()
|
|
36
|
+
|
|
37
|
+
currentEffect = run
|
|
38
|
+
try { fn() }
|
|
39
|
+
finally { currentEffect = null }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
run.deps = new Set()
|
|
43
|
+
run() // run immediately to collect dependencies
|
|
44
|
+
return run
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function computed(fn) {
|
|
48
|
+
// A computed is a read-only signal derived from other signals
|
|
49
|
+
const result = signal(undefined)
|
|
50
|
+
effect(() => result(fn()))
|
|
51
|
+
return result
|
|
52
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// ─── Framework Public API ─────────────────────────────────────────────────────
|
|
2
|
+
export { signal, computed, effect } from './core/signal.js'
|
|
3
|
+
export { defineComponent, onMount, onUnmount } from './core/component.js'
|
|
4
|
+
export { createRouter, navigate, mountOutlet } from './router/router.js'
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// ─── Hash-based SPA Router ────────────────────────────────────────────────────
|
|
2
|
+
// Routes are declared as: { path: '/about', component: AboutComponent }
|
|
3
|
+
// Navigation via <a href="#/about"> or router.navigate('/about')
|
|
4
|
+
|
|
5
|
+
let _routes = []
|
|
6
|
+
let _outlet = null
|
|
7
|
+
let _currentInstance = null
|
|
8
|
+
|
|
9
|
+
export function createRouter(routes) {
|
|
10
|
+
_routes = routes
|
|
11
|
+
|
|
12
|
+
// Listen for hash changes (back/forward + manual URL edits)
|
|
13
|
+
window.addEventListener('hashchange', () => _resolve())
|
|
14
|
+
|
|
15
|
+
// Initial render on page load
|
|
16
|
+
window.addEventListener('DOMContentLoaded', () => _resolve())
|
|
17
|
+
|
|
18
|
+
return { navigate }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function navigate(path) {
|
|
22
|
+
window.location.hash = path
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── Mount outlet ──────────────────────────────────────────────────────────────
|
|
26
|
+
// Call this with the DOM element that acts as <router-outlet>
|
|
27
|
+
export function mountOutlet(el) {
|
|
28
|
+
_outlet = el
|
|
29
|
+
_resolve()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Internal resolution ───────────────────────────────────────────────────────
|
|
33
|
+
function _resolve() {
|
|
34
|
+
if (!_outlet) return
|
|
35
|
+
|
|
36
|
+
// Parse current hash: '#/about' → '/about', fallback to '/'
|
|
37
|
+
const hash = window.location.hash
|
|
38
|
+
const path = hash.startsWith('#') ? hash.slice(1) || '/' : '/'
|
|
39
|
+
|
|
40
|
+
const matched = _routes.find(r => _matchPath(r.path, path))
|
|
41
|
+
|
|
42
|
+
if (!matched) {
|
|
43
|
+
_outlet.innerHTML = `<p style="color:red">[framework] No route matched for "${path}"</p>`
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Unmount previous component to run cleanup hooks
|
|
48
|
+
if (_currentInstance) {
|
|
49
|
+
try { _currentInstance.unmount() } catch {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_outlet.innerHTML = ''
|
|
53
|
+
|
|
54
|
+
const componentFn = matched.component
|
|
55
|
+
_currentInstance = componentFn()
|
|
56
|
+
_currentInstance.render(_outlet)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Simple path matching — supports exact paths and :param segments
|
|
60
|
+
function _matchPath(routePath, actualPath) {
|
|
61
|
+
const routeParts = routePath.split('/').filter(Boolean)
|
|
62
|
+
const actualParts = actualPath.split('/').filter(Boolean)
|
|
63
|
+
|
|
64
|
+
if (routeParts.length !== actualParts.length) return false
|
|
65
|
+
|
|
66
|
+
return routeParts.every((part, i) =>
|
|
67
|
+
part.startsWith(':') || part === actualParts[i]
|
|
68
|
+
)
|
|
69
|
+
}
|
package/index.html
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ro">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>MyFramework — SPA Demo</title>
|
|
7
|
+
<style>
|
|
8
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
font-family: system-ui, sans-serif;
|
|
12
|
+
background: #0f0f13;
|
|
13
|
+
color: #e8e8f0;
|
|
14
|
+
min-height: 100vh;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
nav {
|
|
18
|
+
display: flex;
|
|
19
|
+
gap: 1rem;
|
|
20
|
+
padding: 1rem 2rem;
|
|
21
|
+
background: #1a1a24;
|
|
22
|
+
border-bottom: 1px solid #2a2a3a;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
nav a {
|
|
26
|
+
color: #8b8bff;
|
|
27
|
+
text-decoration: none;
|
|
28
|
+
font-weight: 500;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
nav a:hover { color: #fff; }
|
|
32
|
+
|
|
33
|
+
#app {
|
|
34
|
+
padding: 2rem;
|
|
35
|
+
max-width: 600px;
|
|
36
|
+
margin: 0 auto;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.page h1 { margin-bottom: 1rem; font-size: 1.8rem; }
|
|
40
|
+
.page p { margin-bottom: 0.5rem; color: #aaa; line-height: 1.6; }
|
|
41
|
+
|
|
42
|
+
.counter {
|
|
43
|
+
margin-top: 2rem;
|
|
44
|
+
padding: 1.5rem;
|
|
45
|
+
background: #1a1a24;
|
|
46
|
+
border: 1px solid #2a2a3a;
|
|
47
|
+
border-radius: 10px;
|
|
48
|
+
display: inline-block;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.counter h2 { margin-bottom: 1rem; font-size: 1rem; color: #888; text-transform: uppercase; letter-spacing: 0.1em; }
|
|
52
|
+
|
|
53
|
+
.value {
|
|
54
|
+
font-size: 4rem !important;
|
|
55
|
+
font-weight: 700;
|
|
56
|
+
color: #8b8bff !important;
|
|
57
|
+
text-align: center;
|
|
58
|
+
line-height: 1;
|
|
59
|
+
margin-bottom: 0.25rem !important;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.derived {
|
|
63
|
+
text-align: center;
|
|
64
|
+
font-size: 0.85rem !important;
|
|
65
|
+
color: #555 !important;
|
|
66
|
+
margin-bottom: 1rem !important;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.actions {
|
|
70
|
+
display: flex;
|
|
71
|
+
gap: 0.5rem;
|
|
72
|
+
justify-content: center;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.actions button {
|
|
76
|
+
padding: 0.5rem 1.25rem;
|
|
77
|
+
border: 1px solid #3a3a5a;
|
|
78
|
+
background: #0f0f1a;
|
|
79
|
+
color: #e8e8f0;
|
|
80
|
+
border-radius: 6px;
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
font-size: 1.1rem;
|
|
83
|
+
transition: background 0.15s;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.actions button:hover { background: #2a2a3a; }
|
|
87
|
+
|
|
88
|
+
.status {
|
|
89
|
+
margin-top: 1rem;
|
|
90
|
+
font-size: 0.8rem;
|
|
91
|
+
color: #4caf50 !important;
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
94
|
+
</head>
|
|
95
|
+
<body>
|
|
96
|
+
<nav>
|
|
97
|
+
<a href="#/">Home</a>
|
|
98
|
+
<a href="#/about">About</a>
|
|
99
|
+
</nav>
|
|
100
|
+
<div id="app"></div>
|
|
101
|
+
<script type="module" src="./app/main.js"></script>
|
|
102
|
+
</body>
|
|
103
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fusee-framework",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Fusée - Signals-first JS Framework | Peak Performance",
|
|
5
|
+
"main": "framework/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"type": "commonjs",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/SebiSomu/fusee.git"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"registry": "https://registry.npmjs.org/"
|
|
19
|
+
}
|
|
20
|
+
}
|