fusee-framework 1.0.0 → 1.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # Fusée Framework 🚀
2
- **v1.0.0 — Signals-First JS Framework | Atomic Reactivity | Peak Performance**
2
+ **v1.1.0 — Signals-First JS Framework | Atomic Reactivity | Peak Performance**
3
3
 
4
- Fusée is a custom, high-performance reactive JavaScript framework built for speed and simplicity. It features a recursive, non-greedy compiler, a signals-based reactivity engine, and a comprehensive CLI for instant application scaffolding.
4
+ Fusée is a custom, high-performance fine-grained reactive JavaScript framework built for speed and simplicity. It features a recursive, non-greedy compiler, a signals-based reactivity engine, and a comprehensive CLI for instant application scaffolding.
5
5
 
6
6
  ---
7
7
 
@@ -10,8 +10,8 @@ Fusée is a custom, high-performance reactive JavaScript framework built for spe
10
10
  The fastest way to get started with Fusée is via the CLI:
11
11
 
12
12
  ```bash
13
- # 1. Install globally (linking the engine)
14
- npm link
13
+ # 1. Install Fusée globally
14
+ npm install -g fusee-framework
15
15
 
16
16
  # 2. Scaffold your first project
17
17
  create-fusee-app .
@@ -70,7 +70,6 @@ export const Counter = defineComponent({
70
70
  ## 🚀 Future Roadmap
71
71
 
72
72
  - [ ] Global Store Management
73
- - [ ] Keyed `f-for` for list reconciliation
74
- - [ ] Advanced router transitions support
73
+ - [ ] Advanced file-based routing support
75
74
 
76
- Built with ❤️ by a forward-thinking JavaScript Architect. 🏎️🔥🏆🥇_
75
+ Built with ❤️ by me, Sebi Somu, a forward-thinking JavaScript Architect.
@@ -1,4 +1,4 @@
1
- import { defineComponent, signal, computed, effect, batch, untrack, onMount, onUnmount, type Signal, type Computed } from '../../framework/index.js'
1
+ import type { Signal, Computed } from 'fusee-framework'
2
2
 
3
3
  type CounterProps = {
4
4
  initialValue?: string
@@ -96,4 +96,3 @@ export const Counter = defineComponent({
96
96
  }
97
97
  }
98
98
  })
99
-
@@ -1,4 +1,4 @@
1
- import { defineComponent, signal, type SignalAccessor } from '../../framework/index.js'
1
+ import type { SignalAccessor } from 'fusee-framework'
2
2
  import { Counter } from './Counter.js'
3
3
 
4
4
  type CounterSectionProps = {
@@ -22,7 +22,7 @@ export const CounterSection = defineComponent({
22
22
 
23
23
  return {
24
24
  showCounter,
25
- get title() { return props.pageTitle },
25
+ get title() { return props.pageTitle || '' },
26
26
  toggleCounter: () => showCounter(!showCounter()),
27
27
  template: `
28
28
  <div style="margin-top: 30px; border-top: 1px solid #333; padding-top: 20px;">
@@ -1,5 +1,3 @@
1
- import { defineComponent, signal } from '../../framework/index.js'
2
-
3
1
  export const UserForm = defineComponent({
4
2
  setup() {
5
3
  const userName = signal('Guest')
package/app/main.js CHANGED
@@ -1,10 +1,12 @@
1
- import { createRouter, mountOutlet } from '../framework/index.js'
1
+ import { createRouter } from '../framework/index.js'
2
2
  import { Home } from './routes/Home.js'
3
3
  import { About } from './routes/About.js'
4
+ import { TestFor } from './routes/TestFor.js'
4
5
 
5
6
  createRouter([
6
7
  { path: '/', component: Home },
7
8
  { path: '/about', component: About },
9
+ { path: '/test-for', component: TestFor },
8
10
  ])
9
11
 
10
12
  const outlet = document.getElementById('app')
@@ -1,5 +1,3 @@
1
- import { defineComponent, signal, onMount } from '../../framework/index.js'
2
-
3
1
  export const About = defineComponent({
4
2
  props: [],
5
3
  setup() {
@@ -1,4 +1,3 @@
1
- import { defineComponent, signal } from '../../framework/index.js'
2
1
  import { UserForm } from '../components/UserForm.js'
3
2
  import { CounterSection } from '../components/CounterSection.js'
4
3
 
@@ -1,9 +1,9 @@
1
- import { defineComponent, signal, type SignalAccessor } from '../../framework/index.js'
1
+ import type { Signal } from 'fusee-framework'
2
2
  import { UserForm } from '../components/UserForm.js'
3
3
  import { CounterSection } from '../components/CounterSection.js'
4
4
 
5
5
  type HomeResult = {
6
- title: SignalAccessor<string>
6
+ title: Signal<string>
7
7
  changeTitle: () => void
8
8
  template: string
9
9
  }
@@ -11,15 +11,14 @@ type HomeResult = {
11
11
  export const Home = defineComponent({
12
12
  components: { UserForm, CounterSection },
13
13
  setup(): HomeResult {
14
- const title = signal('Welcome!')
15
-
14
+ const title = signal<string>('Welcome!')
16
15
  return {
17
16
  title,
18
17
  changeTitle: () => title('Changed Title!'),
19
18
  template: `
20
19
  <div class="page">
21
20
  <h1>{{ title.toUpperCase() }}</h1>
22
- <p>This is an SPA built with my own JavaScript custom framework!</p>
21
+ <p>This is an SPA built with my own (TypeScript!) custom framework!</p>
23
22
  <p>Made with passion by Sebastian Șomu</p>
24
23
 
25
24
  <button @click="changeTitle" class="primary-btn" style="margin-bottom: 20px;">Change App Title</button>
@@ -0,0 +1,41 @@
1
+ export const TestFor = defineComponent({
2
+ setup() {
3
+ const items = signal([
4
+ { id: 1, name: 'Item 1' },
5
+ { id: 2, name: 'Item 2' },
6
+ { id: 3, name: 'Item 3' }
7
+ ])
8
+
9
+ return {
10
+ items,
11
+ addItem: () => {
12
+ const nextId = Math.max(0, ...items().map(i => i.id)) + 1
13
+ items([...items(), { id: nextId, name: `Item ${nextId}` }])
14
+ },
15
+ shuffleItems: () => {
16
+ const shuffled = [...items()].sort(() => Math.random() - 0.5)
17
+ items(shuffled)
18
+ },
19
+ template: `
20
+ <div class="page">
21
+ <h1>Keyed f-for Test</h1>
22
+ <p>Focus an input below, then click "Shuffle" or "Add". The focus should stay!</p>
23
+
24
+ <div style="margin-bottom: 20px; display: flex; gap: 10px;">
25
+ <button @click="addItem" class="primary-btn">Add Item</button>
26
+ <button @click="shuffleItems" class="primary-btn">Shuffle Items</button>
27
+ </div>
28
+
29
+ <div style="display: flex; flex-direction: column; gap: 10px;">
30
+ <div f-for="item in items" :key="item.id"
31
+ style="padding: 10px; border: 1px solid #333; display: flex; gap: 10px; align-items: center;">
32
+ <span>ID: {{ item.id }}</span>
33
+ <input type="text" :value="item.name" placeholder="Type here..."
34
+ style="background: #111; color: #fff; border: 1px solid #444; padding: 5px;" />
35
+ </div>
36
+ </div>
37
+ </div>
38
+ `
39
+ }
40
+ }
41
+ })
@@ -0,0 +1,56 @@
1
+ import type { Signal } from 'fusee-framework'
2
+
3
+ type Item = {
4
+ id: number
5
+ name: string
6
+ }
7
+
8
+ type TestForResult = {
9
+ items: Signal<Item[]>
10
+ addItem: () => void
11
+ shuffleItems: () => void
12
+ template: string
13
+ }
14
+
15
+ export const TestFor = defineComponent({
16
+ setup(): TestForResult {
17
+ const items = signal<Item[]>([
18
+ { id: 1, name: 'Item 1' },
19
+ { id: 2, name: 'Item 2' },
20
+ { id: 3, name: 'Item 3' }
21
+ ])
22
+
23
+ return {
24
+ items,
25
+ addItem: () => {
26
+ const nextIds = items().map(i => i.id)
27
+ const nextId = nextIds.length > 0 ? Math.max(...nextIds) + 1 : 1
28
+ items([...items(), { id: nextId, name: `Item ${nextId}` }])
29
+ },
30
+ shuffleItems: () => {
31
+ const shuffled = [...items()].sort(() => Math.random() - 0.5)
32
+ items(shuffled)
33
+ },
34
+ template: `
35
+ <div class="page">
36
+ <h1>Keyed f-for Test (TypeScript)</h1>
37
+ <p>Focus an input below, then click "Shuffle" or "Add". The focus should stay!</p>
38
+
39
+ <div style="margin-bottom: 20px; display: flex; gap: 10px;">
40
+ <button @click="addItem" class="primary-btn">Add Item</button>
41
+ <button @click="shuffleItems" class="primary-btn">Shuffle Items</button>
42
+ </div>
43
+
44
+ <div style="display: flex; flex-direction: column; gap: 10px;">
45
+ <div f-for="item in items" :key="item.id"
46
+ style="padding: 10px; border: 1px solid #333; display: flex; gap: 10px; align-items: center;">
47
+ <span>ID: {{ item.id }}</span>
48
+ <input type="text" :value="item.name" placeholder="Type here..."
49
+ style="background: #111; color: #fff; border: 1px solid #444; padding: 5px;" />
50
+ </div>
51
+ </div>
52
+ </div>
53
+ `
54
+ }
55
+ }
56
+ })
@@ -0,0 +1,12 @@
1
+ export { }
2
+ declare global {
3
+ const batch: typeof import('fusee-framework').batch
4
+ const computed: typeof import('fusee-framework').computed
5
+ const defineComponent: typeof import('fusee-framework').defineComponent
6
+ const effect: typeof import('fusee-framework').effect
7
+ const mountOutlet: typeof import('fusee-framework').mountOutlet
8
+ const onMount: typeof import('fusee-framework').onMount
9
+ const onUnmount: typeof import('fusee-framework').onUnmount
10
+ const signal: typeof import('fusee-framework').signal
11
+ const untrack: typeof import('fusee-framework').untrack
12
+ }
@@ -0,0 +1,8 @@
1
+ export const FuseePreset = {
2
+ 'fusee-framework': [
3
+ 'signal', 'computed', 'effect',
4
+ 'batch', 'untrack',
5
+ 'defineComponent', 'onMount', 'onUnmount',
6
+ 'mountOutlet', 'mountTemplate'
7
+ ]
8
+ }
@@ -6,7 +6,16 @@ import { fileURLToPath } from 'url';
6
6
 
7
7
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
- const sourceFramework = path.join(__dirname, '..');
9
+
10
+ // Find the package root
11
+ let pkgRoot = __dirname;
12
+ while (pkgRoot !== path.dirname(pkgRoot) && !fs.existsSync(path.join(pkgRoot, 'package.json'))) {
13
+ pkgRoot = path.dirname(pkgRoot);
14
+ }
15
+
16
+ const sourceFramework = fs.existsSync(path.join(pkgRoot, 'framework'))
17
+ ? path.join(pkgRoot, 'framework')
18
+ : pkgRoot;
10
19
 
11
20
  const projectName = process.argv[2] || 'my-fusee-app';
12
21
  const projectPath = path.isAbsolute(projectName) ? projectName : path.join(process.cwd(), projectName);
@@ -24,9 +33,8 @@ async function scaffold() {
24
33
  function copyDir(src, dest, skipTypes = false) {
25
34
  if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
26
35
  fs.readdirSync(src).forEach(item => {
27
- // Sărim folderul types SAU orice fișier .d.ts rătăcit
28
36
  if (skipTypes && (item === 'types' || item.endsWith('.d.ts'))) return;
29
- if (item === 'bin' || item === 'node_modules' || item === '.git') return;
37
+ if (item === 'bin' || item === 'node_modules' || item === '.git' || item === 'dist') return;
30
38
 
31
39
  const s = path.join(src, item);
32
40
  const d = path.join(dest, item);
@@ -35,81 +43,127 @@ async function scaffold() {
35
43
  });
36
44
  }
37
45
 
38
- const coreFolders = ['app', 'app/components'];
46
+ const coreFolders = ['app', 'app/components', 'app/routes'];
39
47
  coreFolders.forEach(f => {
40
48
  const dir = path.join(projectPath, f);
41
49
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
42
50
  });
43
51
 
44
- console.log('📦 Injecting Fusée Core Engine (Strict Mode)...');
52
+ console.log('📦 Injecting Fusée Core Engine...');
45
53
  copyDir(sourceFramework, path.join(projectPath, 'framework'), choice === 'js');
46
54
 
55
+ const ext = choice === 'ts' ? 'ts' : 'js';
56
+
57
+ // 1. package.json
47
58
  const packageJson = {
48
59
  name: projectName === '.' ? 'fusee-project' : projectName,
49
60
  version: '1.0.0',
50
61
  type: 'module',
51
62
  scripts: { "dev": "vite", "build": "vite build" },
52
- devDependencies: { "vite": "^5.0.0" }
63
+ devDependencies: {
64
+ "vite": "^5.0.0",
65
+ "unplugin-auto-import": "^0.17.0"
66
+ }
53
67
  };
54
68
  if (choice === 'ts') packageJson.devDependencies["typescript"] = "^5.0.0";
55
69
  fs.writeFileSync(path.join(projectPath, 'package.json'), JSON.stringify(packageJson, null, 2));
56
70
 
71
+ // 2. vite.config.js
72
+ const viteConfig = `import { defineConfig } from 'vite';
73
+ import path from 'path';
74
+ import AutoImport from 'unplugin-auto-import/vite';
75
+ import { FuseePreset } from './framework/auto-import-preset.js';
76
+
77
+ export default defineConfig({
78
+ resolve: {
79
+ alias: {
80
+ 'fusee-framework': path.resolve(__dirname, './framework')
81
+ }
82
+ },
83
+ plugins: [
84
+ AutoImport({
85
+ imports: [FuseePreset],
86
+ dts: true // Generates auto-imports.d.ts
87
+ })
88
+ ]
89
+ });`;
90
+ fs.writeFileSync(path.join(projectPath, 'vite.config.js'), viteConfig);
91
+
92
+ // 3. tsconfig.json (if TS)
57
93
  if (choice === 'ts') {
58
- const tsConfig = { compilerOptions: { "target": "ESNext", "module": "ESNext", "moduleResolution": "node", "strict": true, "esModuleInterop": true, "skipLibCheck": true } };
94
+ const tsConfig = {
95
+ compilerOptions: {
96
+ "target": "ESNext",
97
+ "module": "ESNext",
98
+ "moduleResolution": "bundler",
99
+ "strict": true,
100
+ "esModuleInterop": true,
101
+ "skipLibCheck": true,
102
+ "paths": {
103
+ "fusee-framework": ["./framework/types/index.d.ts"]
104
+ }
105
+ },
106
+ "include": ["app/**/*", "framework/**/*", "auto-imports.d.ts"]
107
+ };
59
108
  fs.writeFileSync(path.join(projectPath, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2));
60
109
  }
61
110
 
62
- const ext = choice === 'ts' ? 'ts' : 'js';
63
- const indexHtml = `
64
- <!DOCTYPE html>
111
+ // 4. index.html
112
+ const indexHtml = `<!DOCTYPE html>
65
113
  <html>
66
114
  <head>
67
115
  <meta charset="UTF-8">
68
- <title>Strict Fusée App</title>
116
+ <title>Fusée App</title>
69
117
  <style>
70
- body { background: #050505; color: white; font-family: system-ui; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
71
- #app { text-align: center; background: #111; padding: 3rem; border-radius: 20px; border: 1px solid #222; box-shadow: 0 10px 40px rgba(255, 51, 51, 0.1); }
72
- h1 { color: #ff3333; text-shadow: 0 0 20px rgba(255, 51, 51, 0.3); margin: 0; font-size: 2.5rem; }
118
+ body { background: #050505; color: white; font-family: system-ui; margin: 0; }
119
+ #app { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; }
120
+ .hero { text-align: center; margin-bottom: 2rem; }
121
+ h1 { color: #ff3333; font-size: 3rem; margin: 0; }
73
122
  </style>
74
123
  </head>
75
124
  <body>
76
125
  <div id="app">
77
- <h1>Fusée 🚀</h1>
78
- <p style="color: #ff9999; opacity: 0.8; margin-bottom: 2rem;">A reactive framework built with passion.</p>
126
+ <div class="hero">
127
+ <h1>Fusée 🚀</h1>
128
+ <p style="opacity: 0.6;">Modern Reactive Framework</p>
129
+ </div>
79
130
  {{ Counter }}
80
131
  </div>
81
132
  <script type="module" src="/app/main.${ext}"></script>
82
133
  </body>
83
- </html>
84
- `;
134
+ </html>`;
85
135
  fs.writeFileSync(path.join(projectPath, 'index.html'), indexHtml);
86
136
 
87
- const btnStyle = "background:#ff3333; color:white; border:none; width:45px; height:45px; border-radius:12px; cursor:pointer; font-size:1.6rem; font-weight:bold; transition: 0.3s; box-shadow: 0 4px 15px rgba(255, 51, 51, 0.2);";
137
+ // 5. Counter component
138
+ const btnStyle = "background:#ff3333; color:white; border:none; width:50px; height:50px; border-radius:15px; cursor:pointer; font-size:1.5rem; transition: 0.2s;";
88
139
  const counterContent = choice === 'ts'
89
- ? `import { signal } from '../../framework/core/signal.js';
90
- import { defineComponent } from '../../framework/core/component.js';
140
+ ? `import type { Signal } from 'fusee-framework'
141
+
142
+ interface CounterResult {
143
+ count: Signal<number>
144
+ inc: () => void
145
+ dec: () => void
146
+ template: string
147
+ }
91
148
 
92
149
  export const Counter = defineComponent({
93
- setup() {
150
+ setup(): CounterResult {
94
151
  const count = signal<number>(0);
95
152
  return {
96
153
  count,
97
154
  inc: () => count(count() + 1),
98
155
  dec: () => count(count() - 1),
99
156
  template: \`
100
- <div style="display:flex; align-items:center; justify-content:center; gap:2.5rem;">
157
+ <div style="display:flex; align-items:center; gap:2rem; background:#111; padding:2rem; border-radius:20px; border:1px solid #222;">
101
158
  <button @click="dec" style="${btnStyle}">-</button>
102
- <span style="font-size:2.8rem; font-weight:bold; min-width:70px;">{{ count }}</span>
159
+ <span style="font-size:3rem; font-weight:bold; min-width:60px; text-align:center;">{{ count }}</span>
103
160
  <button @click="inc" style="${btnStyle}">+</button>
104
161
  </div>
105
162
  \`
106
163
  };
107
164
  }
108
165
  });`
109
- : `import { signal } from '../../framework/core/signal.js';
110
- import { defineComponent } from '../../framework/core/component.js';
111
-
112
- export const Counter = defineComponent({
166
+ : `export const Counter = defineComponent({
113
167
  setup() {
114
168
  const count = signal(0);
115
169
  return {
@@ -117,23 +171,23 @@ export const Counter = defineComponent({
117
171
  inc: () => count(count() + 1),
118
172
  dec: () => count(count() - 1),
119
173
  template: \`
120
- <div style="display:flex; align-items:center; justify-content:center; gap:2.5rem;">
174
+ <div style="display:flex; align-items:center; gap:2rem; background:#111; padding:2rem; border-radius:20px; border:1px solid #222;">
121
175
  <button @click="dec" style="${btnStyle}">-</button>
122
- <span style="font-size:2.8rem; font-weight:bold; min-width:70px;">{{ count }}</span>
176
+ <span style="font-size:3rem; font-weight:bold; min-width:60px; text-align:center;">{{ count }}</span>
123
177
  <button @click="inc" style="${btnStyle}">+</button>
124
178
  </div>
125
179
  \`
126
180
  };
127
181
  }
128
182
  });`;
129
-
130
183
  fs.writeFileSync(path.join(projectPath, `app/components/Counter.${ext}`), counterContent);
131
184
 
132
- const mainContent = `import { mountTemplate } from '../framework/core/compiler.js';
133
- import { Counter } from './components/Counter.${ext}';
185
+ // 6. main.js
186
+ const mainContent = `import { Counter } from './components/Counter';
134
187
 
135
188
  const root = document.getElementById('app');
136
189
  if (root) {
190
+ // mountTemplate is now auto-imported via Fusée!
137
191
  mountTemplate(root.innerHTML, root, {}, { Counter });
138
192
  }
139
193
  `;
@@ -64,7 +64,7 @@ export function compileNode(node, context, components, effects) {
64
64
  }
65
65
 
66
66
  if (node.nodeType === 1) {
67
- processDirectives(node, context, components, effects)
67
+ if (processDirectives(node, context, components, effects)) return
68
68
  processBindingAttrs(node, context, effects)
69
69
  bindComponents(node, components, context, effects)
70
70
  } else if (node.nodeType === 3) {
@@ -80,7 +80,7 @@ export function compileNode(node, context, components, effects) {
80
80
 
81
81
  function compileOnce(node, context, components, effects) {
82
82
  if (node.nodeType === 1) {
83
- processDirectives(node, context, components, effects)
83
+ if (processDirectives(node, context, components, effects)) return
84
84
  processBindingAttrs(node, context, effects)
85
85
  bindComponents(node, components, context, effects)
86
86
  } else if (node.nodeType === 3) {
@@ -167,48 +167,51 @@ function processBindingAttrs(el, context, effects) {
167
167
  }
168
168
  }
169
169
 
170
- function bindComponents(container, components, context, effects) {
171
- const placeholders = container.querySelectorAll('[data-component]')
172
- const els = container.matches && container.matches('[data-component]') ? [container, ...placeholders] : placeholders
173
- for (const placeholder of els) {
174
- const name = placeholder.dataset.component
175
- const ComponentFn = components[name]
176
- if (ComponentFn) {
177
- const props = {}
178
- for (const attr of [...placeholder.attributes]) {
179
- const attrName = attr.name.toLowerCase()
180
-
181
- if (attrName.startsWith('data-prop-')) {
182
- props[attrName.slice(10)] = attr.value
183
- } else if (attrName.startsWith('data-bind-prop-')) {
184
- const propName = attrName.slice(15)
185
- const expression = attr.value.trim()
186
-
187
- if (expression in context) {
188
- Object.defineProperty(props, propName, {
189
- get() {
190
- const val = context[expression]
191
- return typeof val === 'function' && val.isSignal ? val() : val
192
- },
193
- enumerable: true,
194
- configurable: true
195
- })
196
- } else {
197
- Object.defineProperty(props, propName, {
198
- get() {
199
- return evaluateExpression(expression, context)
200
- },
201
- enumerable: true,
202
- configurable: true
203
- })
204
- }
170
+ function bindComponents(el, components, context, effects) {
171
+ if (el.nodeType !== 1) return
172
+ const name = el.getAttribute('data-component')
173
+ if (!name) return
174
+
175
+ // Remove the attribute immediately to prevent recursive compilers from
176
+ // re-identifying this element as a component placeholder.
177
+ el.removeAttribute('data-component')
178
+
179
+ const ComponentFn = components[name]
180
+ if (ComponentFn) {
181
+ const props = {}
182
+ for (const attr of [...el.attributes]) {
183
+ const attrName = attr.name.toLowerCase()
184
+
185
+ if (attrName.startsWith('data-prop-')) {
186
+ props[attrName.slice(10)] = attr.value
187
+ } else if (attrName.startsWith('data-bind-prop-')) {
188
+ const propName = attrName.slice(15)
189
+ const expression = attr.value.trim()
190
+
191
+ if (expression in context) {
192
+ Object.defineProperty(props, propName, {
193
+ get() {
194
+ const val = context[expression]
195
+ return typeof val === 'function' && val.isSignal ? val() : val
196
+ },
197
+ enumerable: true,
198
+ configurable: true
199
+ })
200
+ } else {
201
+ Object.defineProperty(props, propName, {
202
+ get() {
203
+ return evaluateExpression(expression, context)
204
+ },
205
+ enumerable: true,
206
+ configurable: true
207
+ })
205
208
  }
206
209
  }
207
- const childComponent = ComponentFn(props)
208
- childComponent.render(placeholder)
209
- if (effects) {
210
- effects.push(() => childComponent.unmount())
211
- }
210
+ }
211
+ const childComponent = ComponentFn(props)
212
+ childComponent.render(el)
213
+ if (effects) {
214
+ effects.push(() => childComponent.unmount())
212
215
  }
213
216
  }
214
217
  }