create-fluixi 0.1.0-alpha.11 → 0.1.0-alpha.12

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/dist/index.js CHANGED
@@ -140,6 +140,52 @@ const UI_DEPS = {
140
140
  };
141
141
  /** Build-time only: it runs in the config, not in the app. */
142
142
  const UI_DEV_DEPS = { '@fluixi-ui/resolver': 'alpha' };
143
+ /**
144
+ * What `fluixi.d.ts` holds until the compiler regenerates it.
145
+ *
146
+ * Only the intrinsics: the component globals depend on which packages are installed and
147
+ * on the resolver rules, so listing them here would go stale the moment a component is
148
+ * added. The compiler rewrites the whole file on the first dev run.
149
+ */
150
+ const INTRINSICS_DTS = `// GENERATED by @fluixi/compiler, do not edit.
151
+ //
152
+ // The reactive intrinsics, which the compiler lowers to their \`create*\` calls. They
153
+ // need no import. This file is rewritten on the first \`fluixi dev\`, which adds the
154
+ // components the resolver supplies without an import.
155
+ import * as __fluixi_reactive_signal from '@fluixi/reactive/signal';
156
+ import * as __fluixi_reactive_store from '@fluixi/reactive/store';
157
+
158
+ declare global {
159
+ const $signal: typeof __fluixi_reactive_signal.signal;
160
+ const $memo: typeof __fluixi_reactive_signal.memo;
161
+ const $effect: typeof __fluixi_reactive_signal.effect;
162
+ const $store: typeof __fluixi_reactive_store.store;
163
+ const $resource: typeof __fluixi_reactive_signal.resource;
164
+ const $selector: typeof __fluixi_reactive_signal.createSelector;
165
+ const $deferred: typeof __fluixi_reactive_signal.createDeferred;
166
+ const $untrack: typeof __fluixi_reactive_signal.untrack;
167
+ const $untrackStore: typeof __fluixi_reactive_store.untrackStore;
168
+ }
169
+ `;
170
+ /**
171
+ * The cascade-layer order, declared before anything defines a layer.
172
+ *
173
+ * A layer ranks by where it is FIRST declared, and one nobody declared is appended last,
174
+ * which makes it the strongest. That default is wrong twice over here. The fluixi-css
175
+ * reset defines `flx.base`, which @fluixi-ui's own sheets do not declare, so the reset
176
+ * would outrank the components it is meant to sit under, and an app's plain
177
+ * `button { ... }` reset would outrank them too.
178
+ *
179
+ * Declaring the order up front fixes both: the app's element resets sit lowest, then the
180
+ * engine's preflight, then the component layers. An app's own `@layer base` and
181
+ * `@layer components` are deliberately absent, so they stay unregistered and keep
182
+ * winning, which is what an app expects of its own classes.
183
+ */
184
+ const LAYERS_CSS = `/* Cascade-layer order. Imported before any component CSS, because a layer ranks by
185
+ where it is first declared and an undeclared one would rank last, above everything.
186
+ Add this app's own layers only if you want them to rank BELOW the component styles. */
187
+ @layer app-reset, flx.reset, flx.base, flx.tokens, flx.components, flx.skin;
188
+ `;
143
189
  /** Add to a package.json section without dropping what the template already had. */
144
190
  function addDeps(pkg, section, deps) {
145
191
  pkg[section] = Object.fromEntries(Object.entries({ ...(pkg[section] ?? {}), ...deps }).sort(([a], [b]) => a.localeCompare(b)));
@@ -159,10 +205,20 @@ function styleSheet(ui, css) {
159
205
  const sources = `@source '.';\n@source '../index.html';`;
160
206
  const theme = `@theme {\n /* Design tokens. A --color-* scale here drives the whole app. */\n}`;
161
207
  if (css === 'fluixi-css') {
162
- // No import: the vite plugin injects the engine, and `emitUiTokens` bridges the
163
- // scales below onto the `--flx-ui-*` properties the components read, so importing
164
- // the token sheet as well would only restate what the bridge already emits.
165
- return [darkVariant, '', sources, '', theme, ''].join('\n');
208
+ // The layers are pulled in by directive rather than by import, and they come last:
209
+ // `@theme` above has to be read before `utilities` is generated from it.
210
+ return [
211
+ darkVariant,
212
+ '',
213
+ sources,
214
+ '',
215
+ theme,
216
+ '',
217
+ '@fluixi base;',
218
+ '@fluixi utilities;',
219
+ '@fluixi components;',
220
+ '',
221
+ ].join('\n');
166
222
  }
167
223
  if (css === 'tailwind') {
168
224
  return [`@import 'tailwindcss';`, '', darkVariant, '', theme, ''].join('\n');
@@ -218,7 +274,7 @@ function addVitePlugin(target, mode, importLine, call) {
218
274
  * asked to resolve, and the components below need them defined before their own rules
219
275
  * land. `emitUiTokens` does not replace this, that bridge covers a subset.
220
276
  */
221
- function importStyles(target, mode, ui) {
277
+ function importStyles(target, mode, ui, css) {
222
278
  const base = mode === 'ssr' ? 'entry-client' : 'main';
223
279
  const file = ['tsx', 'ts']
224
280
  .map((ext) => join(target, 'src', `${base}.${ext}`))
@@ -226,6 +282,8 @@ function importStyles(target, mode, ui) {
226
282
  if (!file)
227
283
  return;
228
284
  const head = [
285
+ // First, before anything that defines a layer.
286
+ ...(css === 'fluixi-css' ? [`import './layers.css';`] : []),
229
287
  ...(ui === 'fluixi-ui' ? [`import '@fluixi-ui/tokens/tokens.css';`] : []),
230
288
  `import './styles.css';`,
231
289
  '',
@@ -290,6 +348,13 @@ async function main() {
290
348
  const gi = join(target, '_gitignore');
291
349
  if (existsSync(gi))
292
350
  renameSync(gi, join(target, '.gitignore'));
351
+ // The ambient declarations for the `$` intrinsics.
352
+ //
353
+ // The compiler rewrites this file on the first `fluixi dev`, adding whatever the
354
+ // resolver supplies without an import. Shipping it now means an editor knows what
355
+ // `$signal` is before the app has ever been run, instead of reporting "Cannot find
356
+ // name" across a project that compiles.
357
+ writeFileSync(join(target, 'fluixi.d.ts'), INTRINSICS_DTS);
293
358
  const pkgPath = join(target, 'package.json');
294
359
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
295
360
  pkg.name = name;
@@ -309,14 +374,20 @@ async function main() {
309
374
  // The bridge only matters with the component kit installed: it maps the theme
310
375
  // scales onto the `--flx-ui-*` custom properties those components read.
311
376
  `fluixiCss({ reset: true${ui === 'fluixi-ui' ? ', emitUiTokens: true' : ''} })`);
312
- writeFileSync(join(target, 'fluixi.css.config.js'), `const config = {\n purge: true,\n content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],\n mode: { dark: '[data-theme="dark"]' },\n theme: { extend: {} },\n};\n\nexport default config;\n`);
377
+ // No `fluixi.css.config.js`: `@theme`, `@source` and `@custom-variant` in the
378
+ // stylesheet say the same things, and a project with both has two places to change
379
+ // one colour. The config file is still read when an app adds one.
313
380
  }
314
381
  if (ui === 'fluixi-ui' || css !== 'none') {
382
+ // Only for fluixi-css: it is the engine that defines `flx.base`, and without a
383
+ // declared order that layer lands above the components it belongs under.
384
+ if (css === 'fluixi-css')
385
+ writeFileSync(join(target, 'src', 'layers.css'), LAYERS_CSS);
315
386
  const sheet = styleSheet(ui, css);
316
387
  // An empty sheet still gets written: it is where an app puts its own css, and the
317
388
  // entry already imports it.
318
389
  writeFileSync(join(target, 'src', 'styles.css'), sheet);
319
- importStyles(target, mode, ui);
390
+ importStyles(target, mode, ui, css);
320
391
  }
321
392
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
322
393
  // Outro + next steps. The leading gutter only makes sense after the intro/prompts.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-fluixi",
3
- "version": "0.1.0-alpha.11",
3
+ "version": "0.1.0-alpha.12",
4
4
  "license": "MIT",
5
5
  "author": "Ibrahima Touré",
6
6
  "description": "Scaffold a new Fluixi app — `npm create fluixi <dir>`.",
@@ -1,13 +1,12 @@
1
- import { createSignal } from '@fluixi/core';
2
1
 
3
2
  export function App() {
4
- const [count, setCount] = createSignal(0);
3
+ const count = $signal(0);
5
4
  return (
6
5
  <div class="app">
7
6
  <h1>Fluixi</h1>
8
- <button onClick={() => setCount(count() - 1)}>-</button>
7
+ <button onClick={() => count.set(count() - 1)}>-</button>
9
8
  <span>{count()}</span>
10
- <button onClick={() => setCount(count() + 1)}>+</button>
9
+ <button onClick={() => count.set(count() + 1)}>+</button>
11
10
  </div>
12
11
  );
13
12
  }
@@ -1,13 +1,13 @@
1
- import { createSignal, html } from '@fluixi/core';
1
+ import { html } from '@fluixi/core';
2
2
 
3
3
  export function App() {
4
- const [count, setCount] = createSignal(0);
4
+ const count = $signal(0);
5
5
  return html`
6
6
  <div class="app">
7
7
  <h1>Fluixi</h1>
8
- <button @click=${() => setCount(count() - 1)}>-</button>
8
+ <button @click=${() => count.set(count() - 1)}>-</button>
9
9
  <span>${count()}</span>
10
- <button @click=${() => setCount(count() + 1)}>+</button>
10
+ <button @click=${() => count.set(count() + 1)}>+</button>
11
11
  </div>
12
12
  `;
13
13
  }
@@ -1,4 +1,4 @@
1
- import { Router, Outlet, createMemoryHistory } from '@fluixi/start/router';
1
+ import { Router, Outlet, Link, createMemoryHistory } from '@fluixi/start/router';
2
2
  import { Suspense } from '@fluixi/core';
3
3
  import { routes } from '@fluixi/core/routes';
4
4
 
@@ -8,9 +8,9 @@ function RootLayout() {
8
8
  return (
9
9
  <div class="app">
10
10
  <nav>
11
- <a href="/">home</a>
11
+ <Link href="/">home</Link>
12
12
  {' · '}
13
- <a href="/about">about</a>
13
+ <Link href="/about">about</Link>
14
14
  </nav>
15
15
  <main>
16
16
  <Suspense fallback={<p>loading…</p>}>
@@ -1,4 +1,3 @@
1
- import { createResource } from '@fluixi/reactive/signal';
2
1
  import { seo } from '@fluixi/start/head';
3
2
 
4
3
  const tick = <T,>(v: T, ms = 30) => new Promise<T>((r) => setTimeout(() => r(v), ms));
@@ -6,7 +5,7 @@ const tick = <T,>(v: T, ms = 30) => new Promise<T>((r) => setTimeout(() => r(v),
6
5
  // A data-gated route, like a real page: the resource is awaited during SSR so the
7
6
  // content (not the spinner) is server-rendered, then hydrated.
8
7
  export default function Home() {
9
- const [data] = createResource(() => tick('Hello from Fluixi Start 👋'));
8
+ const data = $resource(() => tick('Hello from Fluixi Start 👋'));
10
9
 
11
10
  // Per-route document head: rendered into <head> on the server (great for crawlers/links)
12
11
  // and kept reactive on the client. The head engine is always on; seo() is optional, call it
@@ -1,4 +1,4 @@
1
- import { Router, Outlet, createMemoryHistory } from '@fluixi/start/router';
1
+ import { Router, Outlet, createMemoryHistory, Link } from '@fluixi/start/router';
2
2
  import { Suspense } from '@fluixi/core';
3
3
  import { html } from '@fluixi/start';
4
4
  import { routes } from '@fluixi/core/routes';
@@ -10,7 +10,7 @@ function RootLayout() {
10
10
  return html`
11
11
  <div class="app">
12
12
  <nav>
13
- <a href="/">home</a> · <a href="/about">about</a>
13
+ <${Link} href="/">home<//> · <${Link} href="/about">about<//>
14
14
  </nav>
15
15
  <main>
16
16
  <${Suspense} fallback=${html`<p>loading…</p>`}>
@@ -1,4 +1,3 @@
1
- import { createResource } from '@fluixi/reactive/signal';
2
1
  import { seo } from '@fluixi/start/head';
3
2
  import { html } from '@fluixi/start';
4
3
 
@@ -7,7 +6,7 @@ const tick = <T,>(v: T, ms = 30) => new Promise<T>((r) => setTimeout(() => r(v),
7
6
  // A data-gated route, like a real page: the resource is awaited during SSR so the
8
7
  // content (not the spinner) is server-rendered, then hydrated.
9
8
  export default function Home() {
10
- const [data] = createResource(() => tick('Hello from Fluixi Start 👋'));
9
+ const data = $resource(() => tick('Hello from Fluixi Start 👋'));
11
10
 
12
11
  // Per-route document head: rendered into <head> on the server (great for crawlers/links)
13
12
  // and kept reactive on the client. seo() is optional; call it where you want metadata.