zuby 1.0.48 → 1.0.49

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/config.js CHANGED
@@ -12,6 +12,7 @@ import manifestPlugin from './plugins/manifestPlugin/index.js';
12
12
  import prerenderPlugin from './plugins/prerenderPlugin/index.js';
13
13
  import standaloneBuildPlugin from './plugins/dependenciesPlugin/index.js';
14
14
  import preloadPlugin from './plugins/preloadPlugin/index.js';
15
+ import { TEMPLATES } from './templates/types.js';
15
16
  let zubyInternalConfig;
16
17
  /**
17
18
  * Returns the path to the ZubyConfig file.
@@ -57,8 +58,57 @@ export const getZubyInternalConfig = async (configFilePath, cache = true) => {
57
58
  return zubyInternalConfig;
58
59
  const zubyConfig = await getZubyConfig(configFilePath);
59
60
  zubyConfig.configFilePath = configFilePath;
61
+ zubyConfig.templateFiles = zubyConfig.templateFiles || [];
62
+ zubyConfig.headElements = zubyConfig.headElements || [];
60
63
  // Run config setup hook
61
- await executePlugins(zubyConfig, PLUGIN_HOOKS.ZubyConfigSetup);
64
+ await executePlugins(zubyConfig, PLUGIN_HOOKS.ZubyConfigSetup, {
65
+ addEntryTemplate: (filename) => zubyConfig.templateFiles?.push({
66
+ filename,
67
+ path: '/:path*',
68
+ templateType: TEMPLATES.entry,
69
+ }),
70
+ addAppTemplate: (filename, path = '/:path*') => zubyConfig.templateFiles?.push({
71
+ filename,
72
+ path,
73
+ templateType: TEMPLATES.app,
74
+ }),
75
+ addLayoutTemplate: (filename, path = '/:path*') => zubyConfig.templateFiles?.push({
76
+ filename,
77
+ path,
78
+ templateType: TEMPLATES.layout,
79
+ }),
80
+ addInnerLayoutTemplate: (filename, path = '/:path*') => zubyConfig.templateFiles?.push({
81
+ filename,
82
+ path,
83
+ templateType: TEMPLATES.innerLayout,
84
+ }),
85
+ addPageTemplate: (filename, path = '/:path*') => zubyConfig.templateFiles?.push({
86
+ filename,
87
+ path,
88
+ templateType: TEMPLATES.page,
89
+ }),
90
+ addErrorTemplate: (filename, path = '/:path*') => zubyConfig.templateFiles?.push({
91
+ filename,
92
+ path,
93
+ templateType: TEMPLATES.error,
94
+ }),
95
+ addLoaderTemplate: (filename, path = '/:path*') => zubyConfig.templateFiles?.push({
96
+ filename,
97
+ path,
98
+ templateType: TEMPLATES.loader,
99
+ }),
100
+ addPage: (filename, path = '/:path*') => zubyConfig.templateFiles?.push({
101
+ filename,
102
+ path,
103
+ templateType: TEMPLATES.page,
104
+ }),
105
+ addHandler: (filename, path = '/:path*') => zubyConfig.templateFiles?.push({
106
+ filename,
107
+ path,
108
+ templateType: TEMPLATES.handler,
109
+ }),
110
+ addToHead: (element) => zubyConfig.headElements?.push(element),
111
+ });
62
112
  validateConfig(zubyConfig);
63
113
  zubyInternalConfig = await mergeDefaultConfig(zubyConfig);
64
114
  // Run config done hook
@@ -106,6 +156,11 @@ export const mergeDefaultConfig = async (config) => {
106
156
  config.minifyJS = config.minifyJS ?? true;
107
157
  // Build ID generator
108
158
  config.generateBuildId = config.generateBuildId ?? generateDefaultBuildId;
159
+ // Global props
160
+ config.props = config.props ?? {};
161
+ config.serverProps = config.serverProps ?? {};
162
+ // Head elements
163
+ config.headElements = config.headElements ?? [];
109
164
  // Add logger
110
165
  config.customLogger =
111
166
  config.customLogger ??
@@ -21,5 +21,8 @@ export declare class ZubyContext {
21
21
  defaultLocale: string;
22
22
  } | undefined;
23
23
  get buildId(): string | undefined;
24
+ get props(): Record<string, any> | undefined;
25
+ get serverProps(): Record<string, any> | undefined;
26
+ get headElements(): string[] | undefined;
24
27
  }
25
28
  export declare const getContext: () => ZubyContext;
package/context/index.js CHANGED
@@ -26,6 +26,15 @@ export class ZubyContext {
26
26
  get buildId() {
27
27
  return this.rawContext.buildId;
28
28
  }
29
+ get props() {
30
+ return this.rawContext.props;
31
+ }
32
+ get serverProps() {
33
+ return this.rawContext.serverProps;
34
+ }
35
+ get headElements() {
36
+ return this.rawContext.headElements;
37
+ }
29
38
  }
30
39
  const getRawContext = () => {
31
40
  return globalThis.ZubyRawContext;
@@ -50,4 +50,21 @@ export interface ZubyRawContext {
50
50
  locales: string[];
51
51
  defaultLocale: string;
52
52
  };
53
+ /**
54
+ * The global props for the site
55
+ * that will be passed to all pages
56
+ * on both client and server side.
57
+ */
58
+ props?: Record<string, any>;
59
+ /**
60
+ * The global server props for the site
61
+ * that will be passed to all pages
62
+ * only on server side.
63
+ */
64
+ serverProps?: Record<string, any>;
65
+ /**
66
+ * Additional elements that should be added
67
+ * to the head of the page.
68
+ */
69
+ headElements?: string[];
53
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuby",
3
- "version": "1.0.48",
3
+ "version": "1.0.49",
4
4
  "description": "Zuby.js is framework for building SPA apps using Vite",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -10,6 +10,8 @@ export declare class ZubyPageContext {
10
10
  protected _headers: Headers;
11
11
  protected _cache: number;
12
12
  protected _props: Record<string, any>;
13
+ protected _serverProps: Record<string, any>;
14
+ protected _headElements: (string | (() => any))[];
13
15
  protected _zubyContext: ZubyContext;
14
16
  constructor(options: {
15
17
  url?: URL;
@@ -94,6 +96,24 @@ export declare class ZubyPageContext {
94
96
  */
95
97
  get props(): Record<string, any>;
96
98
  set props(value: Record<string, any>);
99
+ /**
100
+ * The server props that are available only in server-side
101
+ * environment and are not shared with the client.
102
+ * You can use this property to pass data from handler to layout template.
103
+ * Hint: You should not read them in page because the hydration would fail,
104
+ * but you can write them on page and read them in layout.
105
+ * @example { my: 'secret-value' }
106
+ */
107
+ get serverProps(): Record<string, any>;
108
+ set serverProps(value: Record<string, any>);
109
+ /**
110
+ * The global props from the ZubyConfig
111
+ */
112
+ get globalProps(): Record<string, any> | undefined;
113
+ /**
114
+ * The global server props from the ZubyConfig
115
+ */
116
+ get globalServerProps(): Record<string, any> | undefined;
97
117
  /**
98
118
  * The current status code that will be returned to the client.
99
119
  * This property has only effect in server-side.
@@ -177,4 +197,16 @@ export declare class ZubyPageContext {
177
197
  * @example ecdf1a94cc9b4f4c
178
198
  */
179
199
  get buildId(): string | undefined;
200
+ /**
201
+ * Adds the given HTML string
202
+ * or Jsx Component to the head of the page
203
+ * when it's rendered on the server.
204
+ */
205
+ addToHead(element: string | (() => any)): void;
206
+ /**
207
+ * Returns array of all elements as string
208
+ * that should be added to the head of the page.
209
+ * @private
210
+ */
211
+ getHeadElements(): Promise<string[]>;
180
212
  }
@@ -9,9 +9,11 @@ export class ZubyPageContext {
9
9
  this._clientAddress = options?.clientAddress;
10
10
  this._statusCode = options?.statusCode || 200;
11
11
  this._props = options?.props || {};
12
+ this._serverProps = {};
12
13
  this._cache = 0;
13
14
  this._headers = options?.headers || new Headers();
14
15
  this._zubyContext = options?.zubyContext || getContext();
16
+ this._headElements = [...(this._zubyContext.headElements || [])];
15
17
  }
16
18
  /**
17
19
  * The current URL of the page.
@@ -114,6 +116,35 @@ export class ZubyPageContext {
114
116
  set props(value) {
115
117
  this._props = value || {};
116
118
  }
119
+ /**
120
+ * The server props that are available only in server-side
121
+ * environment and are not shared with the client.
122
+ * You can use this property to pass data from handler to layout template.
123
+ * Hint: You should not read them in page because the hydration would fail,
124
+ * but you can write them on page and read them in layout.
125
+ * @example { my: 'secret-value' }
126
+ */
127
+ get serverProps() {
128
+ return {
129
+ ...(this._zubyContext.serverProps || {}),
130
+ ...(this._serverProps || {}),
131
+ };
132
+ }
133
+ set serverProps(value) {
134
+ this._serverProps = value || {};
135
+ }
136
+ /**
137
+ * The global props from the ZubyConfig
138
+ */
139
+ get globalProps() {
140
+ return this._zubyContext.props;
141
+ }
142
+ /**
143
+ * The global server props from the ZubyConfig
144
+ */
145
+ get globalServerProps() {
146
+ return this._zubyContext.serverProps;
147
+ }
117
148
  /**
118
149
  * The current status code that will be returned to the client.
119
150
  * This property has only effect in server-side.
@@ -232,4 +263,25 @@ export class ZubyPageContext {
232
263
  get buildId() {
233
264
  return this._zubyContext.buildId;
234
265
  }
266
+ /**
267
+ * Adds the given HTML string
268
+ * or Jsx Component to the head of the page
269
+ * when it's rendered on the server.
270
+ */
271
+ addToHead(element) {
272
+ this._headElements.push(element);
273
+ }
274
+ /**
275
+ * Returns array of all elements as string
276
+ * that should be added to the head of the page.
277
+ * @private
278
+ */
279
+ async getHeadElements() {
280
+ return Promise.all(this._headElements.map(element => {
281
+ if (typeof element === 'function') {
282
+ return this._zubyContext?.renderToString?.(element()) || '';
283
+ }
284
+ return element;
285
+ }));
286
+ }
235
287
  }
@@ -25,7 +25,7 @@ export default function index() {
25
25
  };
26
26
  }
27
27
  export async function generateCompileTimeContextCode(ssr) {
28
- const { site, i18n, buildId } = await getZubyInternalConfig();
28
+ const { site, i18n, buildId, props, serverProps, headElements } = await getZubyInternalConfig();
29
29
  const { version } = await getZubyPackageConfig();
30
30
  return `globalThis.ZubyRawContext = {
31
31
  ...(globalThis.ZubyRawContext || {}),
@@ -35,6 +35,9 @@ export async function generateCompileTimeContextCode(ssr) {
35
35
  generator: 'Zuby.js ${version}',
36
36
  version: '${version}',
37
37
  buildId: '${buildId}',
38
+ props: ${JSON.stringify(props)},
39
+ serverProps: ${JSON.stringify(ssr ? serverProps : {})},
40
+ headElements: ${JSON.stringify(ssr ? headElements : [])},
38
41
  i18n: ${JSON.stringify(i18n)},
39
42
  };`;
40
43
  }
package/server/index.js CHANGED
@@ -2091,6 +2091,15 @@ var ZubyContext = class {
2091
2091
  get buildId() {
2092
2092
  return this.rawContext.buildId;
2093
2093
  }
2094
+ get props() {
2095
+ return this.rawContext.props;
2096
+ }
2097
+ get serverProps() {
2098
+ return this.rawContext.serverProps;
2099
+ }
2100
+ get headElements() {
2101
+ return this.rawContext.headElements;
2102
+ }
2094
2103
  };
2095
2104
  var getRawContext = () => {
2096
2105
  return globalThis.ZubyRawContext;
@@ -2113,9 +2122,11 @@ var ZubyPageContext = class {
2113
2122
  this._clientAddress = options?.clientAddress;
2114
2123
  this._statusCode = options?.statusCode || 200;
2115
2124
  this._props = options?.props || {};
2125
+ this._serverProps = {};
2116
2126
  this._cache = 0;
2117
2127
  this._headers = options?.headers || new Headers();
2118
2128
  this._zubyContext = options?.zubyContext || getContext();
2129
+ this._headElements = [...this._zubyContext.headElements || []];
2119
2130
  }
2120
2131
  /**
2121
2132
  * The current URL of the page.
@@ -2218,6 +2229,35 @@ var ZubyPageContext = class {
2218
2229
  set props(value) {
2219
2230
  this._props = value || {};
2220
2231
  }
2232
+ /**
2233
+ * The server props that are available only in server-side
2234
+ * environment and are not shared with the client.
2235
+ * You can use this property to pass data from handler to layout template.
2236
+ * Hint: You should not read them in page because the hydration would fail,
2237
+ * but you can write them on page and read them in layout.
2238
+ * @example { my: 'secret-value' }
2239
+ */
2240
+ get serverProps() {
2241
+ return {
2242
+ ...this._zubyContext.serverProps || {},
2243
+ ...this._serverProps || {}
2244
+ };
2245
+ }
2246
+ set serverProps(value) {
2247
+ this._serverProps = value || {};
2248
+ }
2249
+ /**
2250
+ * The global props from the ZubyConfig
2251
+ */
2252
+ get globalProps() {
2253
+ return this._zubyContext.props;
2254
+ }
2255
+ /**
2256
+ * The global server props from the ZubyConfig
2257
+ */
2258
+ get globalServerProps() {
2259
+ return this._zubyContext.serverProps;
2260
+ }
2221
2261
  /**
2222
2262
  * The current status code that will be returned to the client.
2223
2263
  * This property has only effect in server-side.
@@ -2336,6 +2376,29 @@ var ZubyPageContext = class {
2336
2376
  get buildId() {
2337
2377
  return this._zubyContext.buildId;
2338
2378
  }
2379
+ /**
2380
+ * Adds the given HTML string
2381
+ * or Jsx Component to the head of the page
2382
+ * when it's rendered on the server.
2383
+ */
2384
+ addToHead(element) {
2385
+ this._headElements.push(element);
2386
+ }
2387
+ /**
2388
+ * Returns array of all elements as string
2389
+ * that should be added to the head of the page.
2390
+ * @private
2391
+ */
2392
+ async getHeadElements() {
2393
+ return Promise.all(
2394
+ this._headElements.map((element) => {
2395
+ if (typeof element === "function") {
2396
+ return this._zubyContext?.renderToString?.(element()) || "";
2397
+ }
2398
+ return element;
2399
+ })
2400
+ );
2401
+ }
2339
2402
  };
2340
2403
 
2341
2404
  // src/server/zubyRenderer.ts
@@ -8721,6 +8784,8 @@ var ZubyRenderer = class {
8721
8784
  jsImports.forEach((imp) => {
8722
8785
  html = html.replace(/(<\/head>)/, `<script type="module" src="${imp}" defer></script>$1`);
8723
8786
  });
8787
+ const headElements = await pageContext.getHeadElements();
8788
+ html = html.replace(/(<\/head>)/, headElements.join("") + "$1");
8724
8789
  return this.injectHeaders(
8725
8790
  new Response(html, {
8726
8791
  status: pageContext.statusCode || 200,
@@ -149,6 +149,8 @@ export default class ZubyRenderer {
149
149
  jsImports.forEach((imp) => {
150
150
  html = html.replace(/(<\/head>)/, `<script type="module" src="${imp}" defer></script>$1`);
151
151
  });
152
+ const headElements = await pageContext.getHeadElements();
153
+ html = html.replace(/(<\/head>)/, headElements.join('') + '$1');
152
154
  return this.injectHeaders(new Response(html, {
153
155
  status: pageContext.statusCode || 200,
154
156
  headers: {
@@ -1,4 +1,4 @@
1
- import { Template, TemplateType } from './types.js';
1
+ import { Template, TemplateFile, TemplateType } from './types.js';
2
2
  /**
3
3
  * Returns the array of pages with static path.
4
4
  */
@@ -42,6 +42,11 @@ export declare function getDefaultTemplate(filename: string, templateType: Templ
42
42
  * If i18n config is defined, the pages will be localized and duplicated for each locale.
43
43
  */
44
44
  export declare function getTemplates(cache?: boolean): Promise<Template[]>;
45
+ /**
46
+ * Collects all template files from the pages directory and the templateFiles config
47
+ * and returns them as an array of TemplateFile objects.
48
+ */
49
+ export declare function getTemplateFiles(): Promise<TemplateFile[]>;
45
50
  /**
46
51
  * Calculates the weight of each template
47
52
  * and sorts them from the least dynamic path to the most dynamic.
@@ -103,26 +103,12 @@ export function getDefaultTemplate(filename, templateType) {
103
103
  export async function getTemplates(cache = true) {
104
104
  if (cache && templatesCache)
105
105
  return templatesCache;
106
- const { srcDir, i18n, templateExtensions } = await getZubyInternalConfig();
107
- const pagesDir = normalizePath(join(srcDir, 'pages'));
108
- const files = await glob(`${pagesDir}/**/*.{${templateExtensions.join(',')}}`);
106
+ const { i18n } = await getZubyInternalConfig();
109
107
  const locales = (i18n?.locales || []).filter(locale => locale !== i18n?.defaultLocale);
110
- const templates = files
111
- .map(filename => {
112
- // Normalize the path and slashes to unix style (needed for windows)
113
- filename = normalizePath(filename);
108
+ const templateFiles = await getTemplateFiles();
109
+ const templates = templateFiles
110
+ .map(({ path, filename, templateType }) => {
114
111
  const fileNameWithoutExtension = basename(filename).replace(/\.(.+)$/, '');
115
- const templateType = getTemplateType(filename);
116
- // Remove the pagesDir prefix from the filename
117
- // and transform it to a valid wouter path
118
- let path = toPath(filename.replace(pagesDir, ''));
119
- // Create matching path for base templates
120
- if (Object.values(BASE_TEMPLATES).includes(templateType)) {
121
- path = `${path}/:path*`.replace(/\/+/g, '/');
122
- }
123
- // Calculate the relative path from
124
- // the ZubyRouter component file to the page file
125
- filename = normalizePath(resolve(filename));
126
112
  return {
127
113
  filename,
128
114
  path,
@@ -148,6 +134,40 @@ export async function getTemplates(cache = true) {
148
134
  }));
149
135
  return (templatesCache = sortTemplates(templates));
150
136
  }
137
+ /**
138
+ * Collects all template files from the pages directory and the templateFiles config
139
+ * and returns them as an array of TemplateFile objects.
140
+ */
141
+ export async function getTemplateFiles() {
142
+ const { srcDir, templateExtensions, templateFiles } = await getZubyInternalConfig();
143
+ const pagesDir = normalizePath(join(srcDir, 'pages'));
144
+ const files = await glob(`${pagesDir}/**/*.{${templateExtensions.join(',')}}`);
145
+ const configTemplateFiles = templateFiles.map(({ templateType, filename, path }) => {
146
+ return {
147
+ filename: normalizePath(resolve(filename)),
148
+ path: toPath(path),
149
+ templateType,
150
+ };
151
+ });
152
+ const folderTemplateFiles = files.map(filename => {
153
+ // Normalize the path and slashes to unix style (needed for windows)
154
+ filename = normalizePath(filename);
155
+ const templateType = getTemplateType(filename);
156
+ // Remove the pagesDir prefix from the filename
157
+ // and transform it to a valid wouter path
158
+ let path = toPath(filename.replace(pagesDir, ''));
159
+ // Create matching path for base templates
160
+ if (Object.values(BASE_TEMPLATES).includes(templateType)) {
161
+ path = `${path}/:path*`.replace(/\/+/g, '/');
162
+ }
163
+ return {
164
+ filename: normalizePath(resolve(filename)),
165
+ path,
166
+ templateType,
167
+ };
168
+ });
169
+ return [...configTemplateFiles, ...folderTemplateFiles];
170
+ }
151
171
  /**
152
172
  * Calculates the weight of each template
153
173
  * and sorts them from the least dynamic path to the most dynamic.
@@ -47,3 +47,8 @@ export declare const SYNC_TEMPLATES: {
47
47
  loader: string;
48
48
  };
49
49
  export type TemplateType = (typeof TEMPLATES)[keyof typeof TEMPLATES];
50
+ export interface TemplateFile {
51
+ path: string;
52
+ filename: string;
53
+ templateType: TemplateType;
54
+ }
@@ -1,4 +1,3 @@
1
- // Pages
2
1
  export const PATH_TYPES = {
3
2
  static: 'static',
4
3
  dynamic: 'dynamic',
package/types.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  import type { UserConfig as ViteUserConfig, InlineConfig as ViteInlineConfig, PluginOption as VitePluginOption, Plugin as VitePlugin } from 'vite';
3
3
  import { ZubyLogger } from './logger/types.js';
4
4
  import ReadableStream = NodeJS.ReadableStream;
5
- import { PathParamsType, Template } from './templates/types.js';
5
+ import { PathParamsType, Template, TemplateFile } from './templates/types.js';
6
6
  import ZubyDevServer from './server/zubyDevServer.js';
7
7
  export interface ZubyConfig {
8
8
  /**
@@ -127,6 +127,34 @@ export interface ZubyConfig {
127
127
  * you can use this option to generate consistent build IDs.
128
128
  */
129
129
  generateBuildId?: () => string | Promise<string>;
130
+ /**
131
+ * Template files that are used to
132
+ * create the templates during the build process.
133
+ * @private
134
+ */
135
+ templateFiles?: TemplateFile[];
136
+ /**
137
+ * The global props for the site
138
+ * that will be passed to all pages, handlers etc...
139
+ * on both client and server side.
140
+ * They can be retrieved using PageContext.globalProps method.
141
+ * @default {}
142
+ */
143
+ props?: Record<string, any>;
144
+ /**
145
+ * The global server props for the site
146
+ * that will be passed to all pages, handlers etc...
147
+ * only on server side.
148
+ * They can be retrieved using PageContext.globalServerProps method.
149
+ * @default {}
150
+ */
151
+ serverProps?: Record<string, any>;
152
+ /**
153
+ * Additional HTML elements
154
+ * that will be injected into the head of the page.
155
+ * @default []
156
+ */
157
+ headElements?: string[];
130
158
  }
131
159
  export interface ZubyInternalConfig extends Required<ZubyConfig> {
132
160
  /**
@@ -321,6 +349,7 @@ export interface ZubyConfigSetupHookParams {
321
349
  addLoaderTemplate: (loaderFile: string, path?: string) => void;
322
350
  addPage: (pageFile: string, path?: string) => void;
323
351
  addHandler: (handlerFile: string, path?: string) => void;
352
+ addToHead: (element: string) => void;
324
353
  }
325
354
  export interface ZubyConfigDoneHookParams extends ZubyConfigSetupHookParams {
326
355
  config: ZubyInternalConfig;