@monkeyplus/flow 6.0.11 → 6.0.13

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
@@ -16,10 +16,26 @@
16
16
 
17
17
  El workspace consume la API fuente del root para mantener DX local, mientras que `dist/` genera un artefacto publicable con exports ya compilados.
18
18
 
19
+ ## Guia de uso
20
+
21
+ La guia detallada de authoring vive en `docs/authoring.md` e incluye:
22
+
23
+ - como registrar y configurar modulos
24
+ - como definir paginas estaticas y dinamicas
25
+ - como organizar templates, layouts y `*.context.ts`
26
+ - como trabajar con `FlowIsland` y `client/islands/*`
27
+
28
+ Documentacion complementaria:
29
+
30
+ - `docs/modules.md`: referencia de modulos builtin
31
+ - `docs/recipes.md`: recetas basadas en el playground actual
32
+
33
+ Si vas a crear o mantener una app en Flow, ese archivo deberia ser el punto de entrada principal.
34
+
19
35
  ## API pública
20
36
 
21
37
  ```ts
22
- import { defineFlowConfig, definePage } from '@monkeyplus/flow';
38
+ import { defineFlowConfig, defineLayoutContext, definePage } from '@monkeyplus/flow';
23
39
  import { FlowIsland } from '@monkeyplus/flow/components';
24
40
  import { getClientHead } from '@monkeyplus/flow/head';
25
41
  import sitemapModule from '@monkeyplus/flow/modules/sitemap';
@@ -29,6 +45,52 @@ import { createFlowViteConfig } from '@monkeyplus/flow/vite';
29
45
  import { installFlowVuePlugins } from '@monkeyplus/flow/vue';
30
46
  ```
31
47
 
48
+ ## Contexto de layout
49
+
50
+ Los layouts pueden tener un handler de contexto colocalizado usando `views/layouts/<Layout>.context.ts`.
51
+
52
+ Opcionalmente puedes definir `views/layouts/global.context.ts` para compartir datos entre todos los layouts. Flow fusiona primero el contexto global y luego el contexto especifico del layout, por lo que el layout especifico tiene prioridad si repite una clave.
53
+
54
+ ```ts
55
+ import type { GlobalLayoutContextValue } from './global.context';
56
+ // views/layouts/Default.context.ts
57
+ import { defineLayoutContext } from '@monkeyplus/flow';
58
+
59
+ const defaultLayoutContext = defineLayoutContext({
60
+ async setup(ctx) {
61
+ return {
62
+ menuName: 'main',
63
+ localeCode: ctx.locale.code,
64
+ };
65
+ },
66
+ });
67
+
68
+ export type DefaultLayoutContextValue = GlobalLayoutContextValue & Awaited<ReturnType<typeof defaultLayoutContext.setup>>;
69
+
70
+ export default defaultLayoutContext;
71
+ ```
72
+
73
+ Ese resultado se expone en el layout bajo `context.layout`.
74
+
75
+ ```vue
76
+ <script setup lang="ts">
77
+ import type { DefaultLayoutContextValue } from './Default.context';
78
+
79
+ const props = defineProps<{
80
+ context?: {
81
+ layout?: DefaultLayoutContextValue
82
+ }
83
+ }>();
84
+ </script>
85
+
86
+ <template>
87
+ <slot />
88
+ <small>{{ props.context?.layout?.menuName }}</small>
89
+ </template>
90
+ ```
91
+
92
+ Esto reemplaza el patrón anterior de `shared/contexts` para casos que pertenecen al layout. Si una lógica debe compartirse entre varios layouts, puedes ponerla en `views/layouts/global.context.ts` o extraerla a un helper `.ts` reutilizable y llamarlo desde cada `*.context.ts`.
93
+
32
94
  ## Playground
33
95
 
34
96
  La app de referencia vive en `playground/` y ya no en la raíz. Eso permite mantener el root enfocado en la distribución del paquete sin perder el flujo de desarrollo existente.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monkeyplus/flow",
3
- "version": "6.0.11",
3
+ "version": "6.0.13",
4
4
  "description": "@monkeyplus/flow package-first runtime with Vite, Nitro, Vue and a workspace playground.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -1,6 +1,8 @@
1
1
  import { createRequire } from "node:module";
2
+ import layoutContexts from "virtual:flow/layout-contexts";
2
3
  import pageDefinitions from "virtual:flow/pages";
3
4
  import { getFlowImageRuntimeUtils } from "../../modules/images/runtime/server.mjs";
5
+ import { mergeLayoutContextValues } from "../../src/runtime/layout-context.mjs";
4
6
  import { localizeRoutePattern, normalizePath, toPublicRoute } from "../../src/runtime/locale-routing.mjs";
5
7
  const dynamicRouteCache = /* @__PURE__ */ new Map();
6
8
  let runtimeConfigRequire;
@@ -88,6 +90,24 @@ function createContext(definition, locale, localePage, pathname, params, dynamic
88
90
  }
89
91
  };
90
92
  }
93
+ function asContextRecord(value) {
94
+ return typeof value === "object" && value !== null ? value : {};
95
+ }
96
+ async function resolveLayoutContext(definition, ctx) {
97
+ const layoutKey = (definition.view.layout || "default").toLowerCase();
98
+ const globalLayoutContext = layoutContexts.global;
99
+ const layoutContext = layoutKey === "global" ? void 0 : layoutContexts[layoutKey];
100
+ const resolvedGlobalContext = globalLayoutContext?.setup ? asContextRecord(await globalLayoutContext.setup(ctx)) : void 0;
101
+ const resolvedLayoutContext = layoutContext?.setup ? asContextRecord(await layoutContext.setup(ctx)) : void 0;
102
+ return mergeLayoutContextValues(resolvedGlobalContext, resolvedLayoutContext);
103
+ }
104
+ function mergeResolvedContext(context, layoutContext, dynamic, assign) {
105
+ const resolvedContext = assign && dynamic?.context ? { ...context, [assign]: dynamic.context, dynamic } : dynamic ? { ...context, dynamic } : context;
106
+ return {
107
+ ...resolvedContext,
108
+ layout: layoutContext
109
+ };
110
+ }
91
111
  function isDynamicPattern(pattern) {
92
112
  return pattern.includes("/:") || pattern.includes("/**");
93
113
  }
@@ -110,6 +130,26 @@ function replacePath(pattern, url) {
110
130
  function combineName(name, dynamicName) {
111
131
  return `${name.replace(/\/*$/, "")}/${dynamicName.replace(/^\/*/, "")}`;
112
132
  }
133
+ function resolveUrlTarget(namePage, explicitDynamicName) {
134
+ if (explicitDynamicName || !namePage.includes("/")) {
135
+ return {
136
+ namePage,
137
+ dynamicName: explicitDynamicName
138
+ };
139
+ }
140
+ const match = pageDefinitions.filter((candidate) => candidate.name && namePage.startsWith(`${candidate.name}/`)).sort((left, right) => right.name.length - left.name.length)[0];
141
+ if (!match) {
142
+ return {
143
+ namePage,
144
+ dynamicName: explicitDynamicName
145
+ };
146
+ }
147
+ const dynamicName = namePage.slice(match.name.length + 1);
148
+ return {
149
+ namePage: match.name,
150
+ dynamicName: dynamicName || explicitDynamicName
151
+ };
152
+ }
113
153
  function getEnabledLocaleCodes() {
114
154
  const runtimeConfig = getFlowRuntimeConfig();
115
155
  return runtimeConfig.flow?.locale?.locales || [];
@@ -143,8 +183,9 @@ export async function getUrl(namePage, localeCode, options = {}) {
143
183
  const enabledLocales = getEnabledLocaleCodes();
144
184
  const targetLocale = localeCode || enabledLocales[0];
145
185
  const runtimeConfig = getFlowRuntimeConfig();
186
+ const target = resolveUrlTarget(namePage, options.dynamicName);
146
187
  for (const definition of pageDefinitions) {
147
- if (definition.name !== namePage) {
188
+ if (definition.name !== target.namePage) {
148
189
  continue;
149
190
  }
150
191
  const localeEntries = targetLocale ? [targetLocale] : Object.keys(definition.locales);
@@ -160,11 +201,11 @@ export async function getUrl(namePage, localeCode, options = {}) {
160
201
  if (options.params) {
161
202
  return replacePath(localizedRoute, options.params);
162
203
  }
163
- if (localePage.dynamic && options.dynamicName) {
204
+ if (localePage.dynamic && target.dynamicName) {
164
205
  const locale = createLocale(code);
165
206
  const ctx = createContext(definition, locale, localePage, toPublicRoute(runtimeConfig.flow, code, localePage.url), {});
166
207
  const entries = await getDynamicEntries(definition, code, ctx);
167
- const entry = entries.find((candidate) => candidate.name === options.dynamicName);
208
+ const entry = entries.find((candidate) => candidate.name === target.dynamicName);
168
209
  if (!entry) {
169
210
  return void 0;
170
211
  }
@@ -235,9 +276,10 @@ export async function resolvePage(pathname) {
235
276
  }
236
277
  const ctx = createContext(match.definition, locale, match.localePage, pathname, match.params, dynamic);
237
278
  const head = match.localePage.head ? await match.localePage.head(ctx) : match.localePage.seo ? await match.localePage.seo(ctx) : {};
279
+ const layoutContext = await resolveLayoutContext(match.definition, ctx);
238
280
  const context = match.localePage.context ? await match.localePage.context(ctx) : {};
239
281
  const assign = match.localePage.dynamic?.assign;
240
- const resolvedContext = assign && dynamic?.context ? { ...context, [assign]: dynamic.context, dynamic } : dynamic ? { ...context, dynamic } : context;
282
+ const resolvedContext = mergeResolvedContext(context, layoutContext, dynamic, assign);
241
283
  return {
242
284
  definition: match.definition,
243
285
  locale,
@@ -268,6 +310,7 @@ export async function resolvePageByName(name, pathname) {
268
310
  const params = {};
269
311
  const ctx = createContext(definition, locale, localePage, pathname, params);
270
312
  const head = localePage.head ? await localePage.head(ctx) : localePage.seo ? await localePage.seo(ctx) : {};
313
+ const layoutContext = await resolveLayoutContext(definition, ctx);
271
314
  const context = localePage.context ? await localePage.context(ctx) : {};
272
315
  return {
273
316
  definition,
@@ -278,7 +321,7 @@ export async function resolvePageByName(name, pathname) {
278
321
  params,
279
322
  head,
280
323
  seo: head,
281
- context
324
+ context: mergeResolvedContext(context, layoutContext)
282
325
  };
283
326
  }
284
327
  return void 0;
@@ -2,6 +2,8 @@ export type { ContentDirectoryNode, ContentEntry, ContentFileNode, ContentTreeNo
2
2
  export type { FlowBootPayload } from '../runtime/boot.ts';
3
3
  export { defineFlowConfig, defineFlowModule, resolveFlowConfig } from '../runtime/config.ts';
4
4
  export type { FlowConfig, UserFlowConfig, } from '../runtime/config.ts';
5
+ export { defineLayoutContext } from '../runtime/layout-context.ts';
6
+ export type { LayoutContextDefinition, LayoutContextValue } from '../runtime/layout-context.ts';
5
7
  export { definePage } from '../runtime/pages.ts';
6
8
  export type { FlowHydrationMode, FlowLocale, HeadDefinition, PageContextInput, PageDefinition, PageLocaleDefinition, PageViewDefinition, } from '../runtime/pages.ts';
7
9
  export { queryContent } from './query-content.ts';
@@ -1,3 +1,4 @@
1
1
  export { defineFlowConfig, defineFlowModule, resolveFlowConfig } from "../runtime/config.mjs";
2
+ export { defineLayoutContext } from "../runtime/layout-context.mjs";
2
3
  export { definePage } from "../runtime/pages.mjs";
3
4
  export { queryContent } from "./query-content.mjs";
@@ -6,6 +6,7 @@ import { createFlowBuildHooks } from "../runtime/ssg.mjs";
6
6
  import {
7
7
  createVirtualBaseTemplatesModule,
8
8
  createVirtualIslandsModule,
9
+ createVirtualLayoutContextsModule,
9
10
  createVirtualLayoutsModule,
10
11
  createVirtualPagesModule,
11
12
  createVirtualTemplatesModule
@@ -50,6 +51,7 @@ export function createFlowNitroConfig(options = {}) {
50
51
  "virtual:flow/islands": createVirtualIslandsModule(projectRoot),
51
52
  "virtual:flow/templates": createVirtualTemplatesModule(projectRoot),
52
53
  "virtual:flow/layouts": createVirtualLayoutsModule(projectRoot),
54
+ "virtual:flow/layout-contexts": createVirtualLayoutContextsModule(projectRoot),
53
55
  "virtual:flow/bases": createVirtualBaseTemplatesModule(projectRoot),
54
56
  ...flowModules.nitro.virtual
55
57
  },
@@ -14,6 +14,7 @@ import {
14
14
  createVirtualClientPageAssetsModule,
15
15
  createVirtualClientPagesModule,
16
16
  createVirtualIslandsModule,
17
+ createVirtualLayoutContextsModule,
17
18
  createVirtualLayoutsModule,
18
19
  createVirtualPagesModule,
19
20
  createVirtualTemplatesModule
@@ -32,12 +33,14 @@ const flowRestartPatterns = [
32
33
  ];
33
34
  const flowFullReloadPatterns = [
34
35
  /^views\/.+\.vue$/,
36
+ /^views\/layouts\/.+\.context\.(ts|js|mjs|mts)$/,
35
37
  /^client\/pages\/.+\.ts$/,
36
38
  /^client\/islands\/.+\.ts$/
37
39
  ];
38
40
  const flowStructureReloadPatterns = [
39
41
  /^pages\/.+\.ts$/,
40
42
  /^views\/.+\.vue$/,
43
+ /^views\/layouts\/.+\.context\.(ts|js|mjs|mts)$/,
41
44
  /^views\/templates\/.+\.vue$/,
42
45
  /^views\/layouts\/.+\.vue$/,
43
46
  /^views\/base\/.+\.html$/,
@@ -57,6 +60,7 @@ function getVirtualModuleIdsForPath(projectPath) {
57
60
  }
58
61
  if (projectPath.startsWith("views/layouts/")) {
59
62
  ids.add("virtual:flow/layouts");
63
+ ids.add("virtual:flow/layout-contexts");
60
64
  }
61
65
  if (projectPath.startsWith("views/base/")) {
62
66
  ids.add("virtual:flow/bases");
@@ -179,6 +183,7 @@ function createFlowVirtualServerModules(projectRoot, flowConfig) {
179
183
  ["virtual:flow/islands", () => createVirtualIslandsModule(projectRoot)],
180
184
  ["virtual:flow/templates", () => createVirtualTemplatesModule(projectRoot)],
181
185
  ["virtual:flow/layouts", () => createVirtualLayoutsModule(projectRoot)],
186
+ ["virtual:flow/layout-contexts", () => createVirtualLayoutContextsModule(projectRoot)],
182
187
  ["virtual:flow/bases", () => createVirtualBaseTemplatesModule(projectRoot)]
183
188
  ]);
184
189
  return {
@@ -0,0 +1,7 @@
1
+ import type { MaybePromise, PageContextInput } from './pages.ts';
2
+ export interface LayoutContextDefinition {
3
+ setup: (ctx: PageContextInput) => MaybePromise<Record<string, unknown>>;
4
+ }
5
+ export type LayoutContextValue<T extends LayoutContextDefinition> = Awaited<ReturnType<T['setup']>>;
6
+ export declare function defineLayoutContext(context: LayoutContextDefinition): LayoutContextDefinition;
7
+ export declare function mergeLayoutContextValues(...contexts: Array<Record<string, unknown> | undefined>): Record<string, unknown>;
@@ -0,0 +1,14 @@
1
+ export function defineLayoutContext(context) {
2
+ return context;
3
+ }
4
+ export function mergeLayoutContextValues(...contexts) {
5
+ return contexts.reduce((result, context) => {
6
+ if (!context) {
7
+ return result;
8
+ }
9
+ return {
10
+ ...result,
11
+ ...context
12
+ };
13
+ }, {});
14
+ }
@@ -53,6 +53,26 @@ function replacePath(pattern, url) {
53
53
  function combineName(name, dynamicName) {
54
54
  return `${name.replace(/\/*$/, "")}/${dynamicName.replace(/^\/*/, "")}`;
55
55
  }
56
+ function resolveUrlTarget(definitions, namePage, explicitDynamicName) {
57
+ if (explicitDynamicName || !namePage.includes("/")) {
58
+ return {
59
+ namePage,
60
+ dynamicName: explicitDynamicName
61
+ };
62
+ }
63
+ const match = definitions.filter((candidate) => candidate.name && namePage.startsWith(`${candidate.name}/`)).sort((left, right) => right.name.length - left.name.length)[0];
64
+ if (!match) {
65
+ return {
66
+ namePage,
67
+ dynamicName: explicitDynamicName
68
+ };
69
+ }
70
+ const dynamicName = namePage.slice(match.name.length + 1);
71
+ return {
72
+ namePage: match.name,
73
+ dynamicName: dynamicName || explicitDynamicName
74
+ };
75
+ }
56
76
  function createContext(definitions, flowConfig, definition, locale, localePage, pathname, params, dynamic) {
57
77
  return {
58
78
  dynamic,
@@ -111,7 +131,8 @@ export async function loadPageDefinitions(projectRoot, flowConfig) {
111
131
  return pages;
112
132
  }
113
133
  export async function getUrlFromDefinitions(definitions, flowConfig, namePage, localeCode = flowConfig.locale.locales[0], options = {}) {
114
- const definition = definitions.find((candidate) => candidate.name === namePage);
134
+ const target = resolveUrlTarget(definitions, namePage, options.dynamicName);
135
+ const definition = definitions.find((candidate) => candidate.name === target.namePage);
115
136
  if (!definition) {
116
137
  return void 0;
117
138
  }
@@ -126,9 +147,9 @@ export async function getUrlFromDefinitions(definitions, flowConfig, namePage, l
126
147
  if (options.params) {
127
148
  return replacePath(localizedRoute, options.params);
128
149
  }
129
- if (localePage.dynamic && options.dynamicName) {
150
+ if (localePage.dynamic && target.dynamicName) {
130
151
  const entries = await getDynamicEntries(definitions, flowConfig, definition, localeCode, localePage);
131
- const entry = entries.find((candidate) => candidate.name === options.dynamicName);
152
+ const entry = entries.find((candidate) => candidate.name === target.dynamicName);
132
153
  if (!entry) {
133
154
  return void 0;
134
155
  }
@@ -2,6 +2,7 @@ import type { FlowConfig } from './config';
2
2
  export declare function createVirtualPagesModule(projectRoot: string, flowConfig: FlowConfig): string;
3
3
  export declare function createVirtualTemplatesModule(projectRoot: string): string;
4
4
  export declare function createVirtualLayoutsModule(projectRoot: string): string;
5
+ export declare function createVirtualLayoutContextsModule(projectRoot: string): string;
5
6
  export declare function createVirtualBaseTemplatesModule(projectRoot: string): string;
6
7
  export declare function createVirtualIslandsModule(projectRoot: string): string;
7
8
  export declare function createVirtualClientPagesModule(projectRoot: string): string;
@@ -89,6 +89,27 @@ export function createVirtualLayoutsModule(projectRoot) {
89
89
  const files = existsSync(layoutsDir) ? collectFiles(layoutsDir, [".vue"]) : [];
90
90
  return createVirtualVueModuleSource(files, "layouts");
91
91
  }
92
+ export function createVirtualLayoutContextsModule(projectRoot) {
93
+ const layoutsDir = resolve(projectRoot, "views/layouts");
94
+ const files = existsSync(layoutsDir) ? collectFiles(layoutsDir, [".context.ts", ".context.js", ".context.mjs", ".context.mts"]) : [];
95
+ const imports = files.map((filePath, index) => {
96
+ return `import item_${index} from ${JSON.stringify(toAbsoluteImport(filePath))};`;
97
+ });
98
+ const entries = files.map((filePath, index) => {
99
+ const key = basename(filePath, extname(filePath)).replace(/\.context$/i, "").toLowerCase();
100
+ return `${JSON.stringify(key)}: item_${index}`;
101
+ });
102
+ return [
103
+ ...imports,
104
+ "",
105
+ "const layoutContexts = {",
106
+ ...entries.map((entry) => ` ${entry},`),
107
+ "};",
108
+ "",
109
+ "export default layoutContexts;",
110
+ ""
111
+ ].join("\n");
112
+ }
92
113
  export function createVirtualBaseTemplatesModule(projectRoot) {
93
114
  const baseDir = resolve(projectRoot, "views/base");
94
115
  const files = existsSync(baseDir) ? collectFiles(baseDir, [".html"]) : [];
@@ -113,6 +113,12 @@ declare module 'virtual:flow/layouts' {
113
113
  export default layouts;
114
114
  }
115
115
 
116
+ declare module 'virtual:flow/layout-contexts' {
117
+ const layoutContexts: Record<string, import('./layout-context').LayoutContextDefinition>;
118
+
119
+ export default layoutContexts;
120
+ }
121
+
116
122
  declare module 'virtual:flow/bases' {
117
123
  const bases: Record<string, string>;
118
124