create-fluixi 0.1.0-alpha.10 → 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,25 +140,92 @@ 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)));
146
192
  }
147
193
  /**
148
- * The stylesheet the app imports, assembled from what was chosen.
194
+ * The app's own stylesheet, in the shape the chosen css engine expects.
149
195
  *
150
- * Tokens first: the kit's components read those custom properties, so a framework layer
151
- * loaded before them would be overridden by nothing and a component would render unstyled.
196
+ * The component tokens are not here: they are a stylesheet the package ships and the
197
+ * entry imports directly, so this file only holds what the engine has to read, the dark
198
+ * variant, where to scan, and the theme the app defines.
152
199
  */
153
200
  function styleSheet(ui, css) {
154
- const lines = [];
155
- if (ui === 'fluixi-ui')
156
- lines.push(`@import '@fluixi-ui/tokens/css';`);
157
- if (css === 'tailwind')
158
- lines.push(`@import 'tailwindcss';`);
159
- if (css === 'fluixi-css')
160
- lines.push(`@import '@fluixi-css/core';`);
161
- return lines.join('\n') + '\n';
201
+ // Dark from the attribute @fluixi/core's createTheme sets, not the OS preference, so
202
+ // a theme toggle in the app actually drives it.
203
+ const darkVariant = `@custom-variant dark (&:where([data-theme='dark'], [data-theme='dark'] *));`;
204
+ // Where to look for utility classes. Relative to this file.
205
+ const sources = `@source '.';\n@source '../index.html';`;
206
+ const theme = `@theme {\n /* Design tokens. A --color-* scale here drives the whole app. */\n}`;
207
+ if (css === 'fluixi-css') {
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');
222
+ }
223
+ if (css === 'tailwind') {
224
+ return [`@import 'tailwindcss';`, '', darkVariant, '', theme, ''].join('\n');
225
+ }
226
+ // No css engine: the component tokens are imported from the entry like any other
227
+ // stylesheet, so there is nothing for this file to hold.
228
+ return '';
162
229
  }
163
230
  /** Where the component resolver is configured, which differs by mode. */
164
231
  const configFor = (mode) => mode === 'ssr' ? 'fluixi.config.ts' : 'vite.config.ts';
@@ -199,15 +266,29 @@ function addVitePlugin(target, mode, importLine, call) {
199
266
  : src.replace('plugins: [', `plugins: [\n ${call},`);
200
267
  writeFileSync(file, src);
201
268
  }
202
- /** Import the stylesheet from whichever file boots the client. */
203
- function importStyles(target, mode, format) {
269
+ /**
270
+ * Import the stylesheets from whichever file boots the client.
271
+ *
272
+ * The component tokens come first and from the entry rather than from `styles.css`:
273
+ * they are a stylesheet the package ships, not something the app's css engine should be
274
+ * asked to resolve, and the components below need them defined before their own rules
275
+ * land. `emitUiTokens` does not replace this, that bridge covers a subset.
276
+ */
277
+ function importStyles(target, mode, ui, css) {
204
278
  const base = mode === 'ssr' ? 'entry-client' : 'main';
205
279
  const file = ['tsx', 'ts']
206
280
  .map((ext) => join(target, 'src', `${base}.${ext}`))
207
281
  .find((p) => existsSync(p));
208
282
  if (!file)
209
283
  return;
210
- writeFileSync(file, `import './styles.css';\n` + readFileSync(file, 'utf8'));
284
+ const head = [
285
+ // First, before anything that defines a layer.
286
+ ...(css === 'fluixi-css' ? [`import './layers.css';`] : []),
287
+ ...(ui === 'fluixi-ui' ? [`import '@fluixi-ui/tokens/tokens.css';`] : []),
288
+ `import './styles.css';`,
289
+ '',
290
+ ].join('\n');
291
+ writeFileSync(file, head + readFileSync(file, 'utf8'));
211
292
  }
212
293
  async function main() {
213
294
  const args = argv.slice(2);
@@ -267,6 +348,13 @@ async function main() {
267
348
  const gi = join(target, '_gitignore');
268
349
  if (existsSync(gi))
269
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);
270
358
  const pkgPath = join(target, 'package.json');
271
359
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
272
360
  pkg.name = name;
@@ -286,11 +374,20 @@ async function main() {
286
374
  // The bridge only matters with the component kit installed: it maps the theme
287
375
  // scales onto the `--flx-ui-*` custom properties those components read.
288
376
  `fluixiCss({ reset: true${ui === 'fluixi-ui' ? ', emitUiTokens: true' : ''} })`);
289
- 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.
290
380
  }
291
381
  if (ui === 'fluixi-ui' || css !== 'none') {
292
- writeFileSync(join(target, 'src', 'styles.css'), styleSheet(ui, css));
293
- importStyles(target, mode, format);
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);
386
+ const sheet = styleSheet(ui, css);
387
+ // An empty sheet still gets written: it is where an app puts its own css, and the
388
+ // entry already imports it.
389
+ writeFileSync(join(target, 'src', 'styles.css'), sheet);
390
+ importStyles(target, mode, ui, css);
294
391
  }
295
392
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
296
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.10",
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.