@ressjs/assets 0.6.0-experimental.0 → 0.6.0-rc.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/LICENSE ADDED
@@ -0,0 +1,69 @@
1
+ ress.js Proprietary License
2
+
3
+ Copyright (c) 2025-2026 gabrielspisso. All rights reserved.
4
+
5
+ This license applies to the ress.js packages (the "Software"), published on npm
6
+ under the @ressjs and @gzzy scopes, in every version released from 0.6.0-rc.0
7
+ onwards. Earlier versions were released under the MIT License and remain
8
+ available under those terms.
9
+
10
+ 1. GRANT
11
+ Subject to this license, the Licensor grants you a worldwide, non-exclusive,
12
+ non-transferable, non-sublicensable, royalty-free license to install and use
13
+ the Software, as distributed by the Licensor, to build, run and deploy your
14
+ own applications and services, including commercial ones, and to charge your
15
+ own customers for them.
16
+
17
+ 2. RESTRICTIONS
18
+ You may not:
19
+ a) fork, copy, or redistribute the Software, in whole or in part, except for
20
+ the portions that are necessarily included in the build output of your own
21
+ application in order to run it;
22
+ b) publish, distribute or make available a modified version of the Software
23
+ or any work derived from it. Modifications for your own internal use are
24
+ allowed, but they remain subject to this license;
25
+ c) use the Software, or any part of it, to build, offer or support a
26
+ framework, library, tool or service that competes with the Software;
27
+ d) sell, sublicense, rent or host the Software itself as a product or service
28
+ for third parties, as opposed to hosting your own application built with
29
+ it;
30
+ e) remove or alter copyright, license or attribution notices;
31
+ f) use the name "ress.js" or the Licensor's names or marks to endorse or
32
+ promote your products without written permission.
33
+
34
+ 3. SCAFFOLDING OUTPUT
35
+ Files that @ressjs/create-ress-app copies into your project (templates and
36
+ generated configuration) are yours: you may use, modify and distribute them
37
+ without restriction. This does not extend to the Software packages that the
38
+ generated project installs as dependencies.
39
+
40
+ 4. SUPPORT
41
+ The Software is provided without any obligation of support, maintenance or
42
+ updates. The Licensor may offer support, maintenance, training or consulting
43
+ under a separate paid agreement; that agreement does not change this license
44
+ unless it says so explicitly and is signed by the Licensor.
45
+
46
+ 5. OWNERSHIP
47
+ The Software is licensed, not sold. The Licensor retains all rights not
48
+ expressly granted here. Feedback and suggestions you send may be used by the
49
+ Licensor without obligation to you.
50
+
51
+ 6. TERMINATION
52
+ This license ends automatically if you breach it. On termination you must
53
+ stop using the Software and delete your copies. Sections 4 to 8 survive
54
+ termination.
55
+
56
+ 7. NO WARRANTY
57
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
58
+ IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
59
+ PARTICULAR PURPOSE AND NON-INFRINGEMENT.
60
+
61
+ 8. LIMITATION OF LIABILITY
62
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE LICENSOR WILL NOT BE LIABLE FOR
63
+ ANY INDIRECT, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES, OR FOR LOSS OF
64
+ PROFITS, DATA OR BUSINESS, ARISING FROM THE USE OF OR INABILITY TO USE THE
65
+ SOFTWARE. THE LICENSOR'S TOTAL LIABILITY IS LIMITED TO THE AMOUNT YOU PAID
66
+ FOR THE SOFTWARE, WHICH MAY BE ZERO.
67
+
68
+ For other uses, including exceptions to section 2, contact the Licensor to
69
+ agree a separate license.
package/dist/catalog.js CHANGED
@@ -21,7 +21,10 @@ exports.SUPPORTED_STYLE_EXTENSIONS = Object.freeze(['css', 'scss', 'sass', 'less
21
21
  * (CSS antes que SCSS). Así el resultado no depende del orden del filesystem.
22
22
  */
23
23
  async function discoverStyleVariants(base, registry = platform_1.defaultPlatformRegistry) {
24
- const files = await (0, fast_glob_1.default)(exports.SUPPORTED_STYLE_EXTENSIONS.flatMap((ext) => [`${base}.${ext}`, `${base}.*.${ext}`]), { absolute: false });
24
+ // `base` es una ruta, no un patrón: `(shop)` o `[...slug]` son nombres de
25
+ // página y se escapan para que el glob los lea literalmente.
26
+ const pattern = fast_glob_1.default.escapePath(base.replace(/\\/g, '/'));
27
+ const files = await (0, fast_glob_1.default)(exports.SUPPORTED_STYLE_EXTENSIONS.flatMap((ext) => [`${pattern}.${ext}`, `${pattern}.*.${ext}`]), { absolute: false });
25
28
  const priority = new Map(exports.SUPPORTED_STYLE_EXTENSIONS.map((ext, index) => [ext, index]));
26
29
  const ordered = files.sort((a, b) => {
27
30
  const extA = extensionOf(a);
@@ -1,3 +1,3 @@
1
- /** ESM browser entry reusing the single server/client component implementation. */
2
- export { Script, Style } from '../components.js';
1
+ export { Head, Script, Style } from '../components.js';
3
2
  export type { ScriptProps, StyleProps } from '../components.js';
3
+ export type { HeadProps } from '../head.js';
@@ -1,2 +1,179 @@
1
- /** ESM browser entry reusing the single server/client component implementation. */
2
- export { Script, Style } from '../components.js';
1
+ // src/components.ts
2
+ import {
3
+ Children,
4
+ Fragment,
5
+ createContext,
6
+ createElement,
7
+ isValidElement,
8
+ useContext,
9
+ useEffect
10
+ } from "react";
11
+ var VALID_HEAD_TAGS = /* @__PURE__ */ new Set(["title", "meta", "link", "script", "style", "base", "noscript"]);
12
+ var HEAD_CONTEXT_KEY = /* @__PURE__ */ Symbol.for("@ressjs/assets/head-context");
13
+ var sharedGlobal = globalThis;
14
+ var HeadCollectorContext = sharedGlobal[HEAD_CONTEXT_KEY] ??= createContext(null);
15
+ function HeadCollectorProvider(props) {
16
+ return createElement(HeadCollectorContext.Provider, { value: props.collector }, props.children);
17
+ }
18
+ function Head({ children }) {
19
+ const collector = useContext(HeadCollectorContext);
20
+ const tags = collectHeadChildren(children);
21
+ if (collector) collector.push(...tags);
22
+ const signature = collector ? "" : headTagsSignature(tags);
23
+ useEffect(() => {
24
+ if (collector || typeof document === "undefined") return;
25
+ return applyHeadToDocument(tags);
26
+ }, [collector, signature]);
27
+ return null;
28
+ }
29
+ function headTagsSignature(tags) {
30
+ return JSON.stringify(tags);
31
+ }
32
+ function collectHeadChildren(children) {
33
+ const tags = [];
34
+ Children.forEach(children, (child) => {
35
+ if (!isValidElement(child)) return;
36
+ if (child.type === Fragment) {
37
+ tags.push(...collectHeadChildren(child.props.children));
38
+ return;
39
+ }
40
+ if (typeof child.type !== "string" || !VALID_HEAD_TAGS.has(child.type)) return;
41
+ const raw = child.props;
42
+ const attrs = {};
43
+ for (const [name, value] of Object.entries(raw)) {
44
+ if (name === "children" || name === "dangerouslySetInnerHTML") continue;
45
+ attrs[htmlAttributeName(name)] = value;
46
+ }
47
+ const content = textContent(raw.children) ?? innerHtml(raw.dangerouslySetInnerHTML);
48
+ tags.push({
49
+ type: child.type,
50
+ attrs,
51
+ ...content !== void 0 ? { content } : {}
52
+ });
53
+ });
54
+ return tags;
55
+ }
56
+ function clientSafeHeadTags(tags) {
57
+ return tags.filter((tag) => tag.type !== "script" || isJsonLdScript(tag));
58
+ }
59
+ function isJsonLdScript(tag) {
60
+ return String(tag.attrs.type ?? "").toLowerCase().trim() === "application/ld+json";
61
+ }
62
+ function applyHeadToDocument(tags) {
63
+ const inserted = [];
64
+ const displaced = [];
65
+ for (const tag of clientSafeHeadTags(tags)) {
66
+ const existing = findExistingHeadTag(tag);
67
+ if (existing) {
68
+ if (elementMatchesTag(existing, tag)) continue;
69
+ displaced.push({ node: existing, next: existing.nextSibling });
70
+ existing.remove();
71
+ }
72
+ const element = document.createElement(tag.type);
73
+ for (const [name, value] of Object.entries(tag.attrs)) {
74
+ if (value == null || value === false) continue;
75
+ if (value === true) element.setAttribute(name, "");
76
+ else element.setAttribute(name, String(value));
77
+ }
78
+ if (tag.content !== void 0) element.textContent = tag.content;
79
+ document.head.appendChild(element);
80
+ inserted.push(element);
81
+ }
82
+ return () => {
83
+ for (const element of inserted) element.remove();
84
+ for (const { node, next } of displaced.reverse()) document.head.insertBefore(node, next);
85
+ };
86
+ }
87
+ function elementMatchesTag(element, tag) {
88
+ if (element.localName !== tag.type) return false;
89
+ if ((tag.content ?? "") !== (element.textContent ?? "")) return false;
90
+ const expectedNames = Object.entries(tag.attrs).filter(([, value]) => value != null && value !== false).map(([name]) => name);
91
+ const actualNames = element.getAttributeNames?.() ?? [];
92
+ if (expectedNames.length !== actualNames.length) return false;
93
+ return expectedNames.every((name) => {
94
+ const value = tag.attrs[name];
95
+ const expected = value === true ? "" : String(value);
96
+ return element.getAttribute(name) === expected;
97
+ });
98
+ }
99
+ function textContent(value) {
100
+ if (value == null) return void 0;
101
+ if (typeof value === "string" || typeof value === "number") return String(value);
102
+ if (Array.isArray(value)) {
103
+ const parts = value.map(textContent);
104
+ return parts.every((part) => part !== void 0) ? parts.join("") : void 0;
105
+ }
106
+ return void 0;
107
+ }
108
+ function innerHtml(value) {
109
+ if (!value || typeof value !== "object") return void 0;
110
+ const html = value.__html;
111
+ return html == null ? void 0 : String(html);
112
+ }
113
+ function htmlAttributeName(name) {
114
+ const known = {
115
+ charSet: "charset",
116
+ className: "class",
117
+ crossOrigin: "crossorigin",
118
+ httpEquiv: "http-equiv",
119
+ noModule: "nomodule"
120
+ };
121
+ return known[name] ?? name;
122
+ }
123
+ function findExistingHeadTag(tag) {
124
+ const candidates = Array.from(document.head.children);
125
+ if (tag.type === "title" || tag.type === "base") {
126
+ return candidates.find((element) => element.localName === tag.type) ?? null;
127
+ }
128
+ if (tag.type === "meta") {
129
+ if (tag.attrs.name != null) return findByAttribute(candidates, "meta", "name", tag.attrs.name);
130
+ if (tag.attrs.property != null) return findByAttribute(candidates, "meta", "property", tag.attrs.property);
131
+ if (tag.attrs.charset != null) {
132
+ return candidates.find((element) => element.localName === "meta" && element.hasAttribute("charset")) ?? null;
133
+ }
134
+ }
135
+ if (tag.type === "script" && tag.attrs.src != null) {
136
+ return findByAttribute(candidates, "script", "src", tag.attrs.src);
137
+ }
138
+ if (tag.type === "link" && String(tag.attrs.rel).toLowerCase() === "canonical") {
139
+ return candidates.find(
140
+ (element) => element.localName === "link" && element.getAttribute("rel")?.toLowerCase() === "canonical"
141
+ ) ?? null;
142
+ }
143
+ return null;
144
+ }
145
+ function findByAttribute(elements, tag, name, value) {
146
+ return elements.find(
147
+ (element) => element.localName === tag && element.getAttribute(name) === String(value)
148
+ ) ?? null;
149
+ }
150
+ function Style({ children, media, nonce, title, id }) {
151
+ return createElement("style", {
152
+ ...media ? { media } : {},
153
+ ...nonce ? { nonce } : {},
154
+ ...title ? { title } : {},
155
+ ...id ? { id } : {},
156
+ "data-ress-style": "inline"
157
+ }, children);
158
+ }
159
+ function Script({ children, src, type, nonce, id, async, defer }) {
160
+ return createElement("script", {
161
+ ...src ? { src } : {},
162
+ ...type ? { type } : {},
163
+ ...nonce ? { nonce } : {},
164
+ ...id ? { id } : {},
165
+ ...async ? { async: true } : {},
166
+ ...defer ? { defer: true } : {},
167
+ "data-ress-script": children != null ? "inline" : "external"
168
+ }, children);
169
+ }
170
+ export {
171
+ Head,
172
+ HeadCollectorProvider,
173
+ Script,
174
+ Style,
175
+ applyHeadToDocument,
176
+ clientSafeHeadTags,
177
+ collectHeadChildren,
178
+ headTagsSignature
179
+ };
@@ -1,2 +1,3 @@
1
- export { Script, Style } from '../components';
1
+ export { Head, Script, Style } from '../components';
2
2
  export type { ScriptProps, StyleProps } from '../components';
3
+ export type { HeadProps } from '../head';
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Style = exports.Script = void 0;
3
+ exports.Style = exports.Script = exports.Head = void 0;
4
4
  var components_1 = require("../components");
5
+ Object.defineProperty(exports, "Head", { enumerable: true, get: function () { return components_1.Head; } });
5
6
  Object.defineProperty(exports, "Script", { enumerable: true, get: function () { return components_1.Script; } });
6
7
  Object.defineProperty(exports, "Style", { enumerable: true, get: function () { return components_1.Style; } });
@@ -1,4 +1,32 @@
1
- import { type ReactNode } from 'react';
1
+ import React, { type ReactNode } from 'react';
2
+ import type { CollectedHeadTag, HeadProps } from './head';
3
+ /** Internal provider used by the server renderer to isolate every request. */
4
+ export declare function HeadCollectorProvider(props: {
5
+ collector: CollectedHeadTag[];
6
+ children?: ReactNode;
7
+ }): React.FunctionComponentElement<React.ProviderProps<CollectedHeadTag[] | null>>;
8
+ /** Declares document-head elements next to the component that owns them. */
9
+ export declare function Head({ children }: HeadProps): null;
10
+ /**
11
+ * Firma estable del contenido de los tags. Se usa como dependencia del efecto
12
+ * de arriba en lugar de `children`, que es un objeto nuevo en cada render.
13
+ */
14
+ export declare function headTagsSignature(tags: readonly CollectedHeadTag[]): string;
15
+ export declare function collectHeadChildren(children: ReactNode): CollectedHeadTag[];
16
+ /**
17
+ * Qué tags de un `<Head>` del cliente pueden aplicarse tal cual llegan.
18
+ *
19
+ * El servidor filtra `<script>` según `security.head.allowScriptTags` (ver
20
+ * `renderHeadTags` en `@ressjs/vite-router`), salvo JSON-LD, que no ejecuta
21
+ * nada. El `<Head>` del cliente no conoce esa configuración —no le llega al
22
+ * bundle del navegador—, así que en vez de asumir un default cualquiera toma
23
+ * la opción segura: nunca crea un `<script>` ejecutable. Si el SSR ya insertó
24
+ * uno legítimo, queda intacto porque `applyHeadToDocument` no lo toca; lo que
25
+ * se evita es que el propio cliente cree uno nuevo (o lo recree) al hidratar
26
+ * o re-renderizar.
27
+ */
28
+ export declare function clientSafeHeadTags(tags: readonly CollectedHeadTag[]): CollectedHeadTag[];
29
+ export declare function applyHeadToDocument(tags: readonly CollectedHeadTag[]): () => void;
2
30
  export interface StyleProps {
3
31
  children?: ReactNode;
4
32
  media?: string;
@@ -6,7 +34,7 @@ export interface StyleProps {
6
34
  title?: string;
7
35
  id?: string;
8
36
  }
9
- export declare function Style({ children, media, nonce, title, id }: StyleProps): import("react").DetailedReactHTMLElement<{
37
+ export declare function Style({ children, media, nonce, title, id }: StyleProps): React.DetailedReactHTMLElement<{
10
38
  'data-ress-style': string;
11
39
  id?: string | undefined;
12
40
  title?: string | undefined;
@@ -22,7 +50,7 @@ export interface ScriptProps {
22
50
  async?: boolean;
23
51
  defer?: boolean;
24
52
  }
25
- export declare function Script({ children, src, type, nonce, id, async, defer }: ScriptProps): import("react").DetailedReactHTMLElement<{
53
+ export declare function Script({ children, src, type, nonce, id, async, defer }: ScriptProps): React.DetailedReactHTMLElement<{
26
54
  'data-ress-script': string;
27
55
  defer?: boolean | undefined;
28
56
  async?: boolean | undefined;
@@ -1,8 +1,205 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HeadCollectorProvider = HeadCollectorProvider;
4
+ exports.Head = Head;
5
+ exports.headTagsSignature = headTagsSignature;
6
+ exports.collectHeadChildren = collectHeadChildren;
7
+ exports.clientSafeHeadTags = clientSafeHeadTags;
8
+ exports.applyHeadToDocument = applyHeadToDocument;
3
9
  exports.Style = Style;
4
10
  exports.Script = Script;
5
11
  const react_1 = require("react");
12
+ const VALID_HEAD_TAGS = new Set(['title', 'meta', 'link', 'script', 'style', 'base', 'noscript']);
13
+ const HEAD_CONTEXT_KEY = Symbol.for('@ressjs/assets/head-context');
14
+ const sharedGlobal = globalThis;
15
+ // Vite can bundle the page and router separately during SSR. A global symbol
16
+ // lets both module instances find the collector for the current React tree.
17
+ const HeadCollectorContext = sharedGlobal[HEAD_CONTEXT_KEY] ??=
18
+ (0, react_1.createContext)(null);
19
+ /** Internal provider used by the server renderer to isolate every request. */
20
+ function HeadCollectorProvider(props) {
21
+ return (0, react_1.createElement)(HeadCollectorContext.Provider, { value: props.collector }, props.children);
22
+ }
23
+ /** Declares document-head elements next to the component that owns them. */
24
+ function Head({ children }) {
25
+ const collector = (0, react_1.useContext)(HeadCollectorContext);
26
+ const tags = collectHeadChildren(children);
27
+ if (collector)
28
+ collector.push(...tags);
29
+ // `children` es un objeto nuevo en cada render aunque su contenido sea el
30
+ // mismo. Si el efecto dependiera de `children` (o de `tags`, otro array
31
+ // nuevo por render), un <Head> dentro de un componente con estado limpiaría
32
+ // y volvería a insertar sus tags en CADA re-render — y un <script> creado
33
+ // con `document.createElement` se ejecuta de nuevo cada vez que se inserta.
34
+ // La firma serializada del contenido sí es estable mientras no cambie nada.
35
+ const signature = collector ? '' : headTagsSignature(tags);
36
+ (0, react_1.useEffect)(() => {
37
+ if (collector || typeof document === 'undefined')
38
+ return;
39
+ return applyHeadToDocument(tags);
40
+ // La firma reemplaza a `children`/`tags` a propósito: sólo importa si el
41
+ // CONTENIDO cambió, no la identidad del array que arma cada render.
42
+ // eslint-disable-next-line react-hooks/exhaustive-deps
43
+ }, [collector, signature]);
44
+ return null;
45
+ }
46
+ /**
47
+ * Firma estable del contenido de los tags. Se usa como dependencia del efecto
48
+ * de arriba en lugar de `children`, que es un objeto nuevo en cada render.
49
+ */
50
+ function headTagsSignature(tags) {
51
+ return JSON.stringify(tags);
52
+ }
53
+ function collectHeadChildren(children) {
54
+ const tags = [];
55
+ react_1.Children.forEach(children, (child) => {
56
+ if (!(0, react_1.isValidElement)(child))
57
+ return;
58
+ if (child.type === react_1.Fragment) {
59
+ tags.push(...collectHeadChildren(child.props.children));
60
+ return;
61
+ }
62
+ if (typeof child.type !== 'string' || !VALID_HEAD_TAGS.has(child.type))
63
+ return;
64
+ const raw = child.props;
65
+ const attrs = {};
66
+ for (const [name, value] of Object.entries(raw)) {
67
+ if (name === 'children' || name === 'dangerouslySetInnerHTML')
68
+ continue;
69
+ attrs[htmlAttributeName(name)] = value;
70
+ }
71
+ const content = textContent(raw.children) ?? innerHtml(raw.dangerouslySetInnerHTML);
72
+ tags.push({
73
+ type: child.type,
74
+ attrs,
75
+ ...(content !== undefined ? { content } : {}),
76
+ });
77
+ });
78
+ return tags;
79
+ }
80
+ /**
81
+ * Qué tags de un `<Head>` del cliente pueden aplicarse tal cual llegan.
82
+ *
83
+ * El servidor filtra `<script>` según `security.head.allowScriptTags` (ver
84
+ * `renderHeadTags` en `@ressjs/vite-router`), salvo JSON-LD, que no ejecuta
85
+ * nada. El `<Head>` del cliente no conoce esa configuración —no le llega al
86
+ * bundle del navegador—, así que en vez de asumir un default cualquiera toma
87
+ * la opción segura: nunca crea un `<script>` ejecutable. Si el SSR ya insertó
88
+ * uno legítimo, queda intacto porque `applyHeadToDocument` no lo toca; lo que
89
+ * se evita es que el propio cliente cree uno nuevo (o lo recree) al hidratar
90
+ * o re-renderizar.
91
+ */
92
+ function clientSafeHeadTags(tags) {
93
+ return tags.filter((tag) => tag.type !== 'script' || isJsonLdScript(tag));
94
+ }
95
+ function isJsonLdScript(tag) {
96
+ return String(tag.attrs.type ?? '').toLowerCase().trim() === 'application/ld+json';
97
+ }
98
+ function applyHeadToDocument(tags) {
99
+ const inserted = [];
100
+ const displaced = [];
101
+ for (const tag of clientSafeHeadTags(tags)) {
102
+ const existing = findExistingHeadTag(tag);
103
+ if (existing) {
104
+ // Si ya está exactamente lo que se quiere insertar, no se toca: para un
105
+ // <script src> ya renderizado en el SSR, sacarlo y volver a ponerlo lo
106
+ // ejecuta una segunda vez.
107
+ if (elementMatchesTag(existing, tag))
108
+ continue;
109
+ displaced.push({ node: existing, next: existing.nextSibling });
110
+ existing.remove();
111
+ }
112
+ const element = document.createElement(tag.type);
113
+ for (const [name, value] of Object.entries(tag.attrs)) {
114
+ if (value == null || value === false)
115
+ continue;
116
+ if (value === true)
117
+ element.setAttribute(name, '');
118
+ else
119
+ element.setAttribute(name, String(value));
120
+ }
121
+ if (tag.content !== undefined)
122
+ element.textContent = tag.content;
123
+ document.head.appendChild(element);
124
+ inserted.push(element);
125
+ }
126
+ return () => {
127
+ for (const element of inserted)
128
+ element.remove();
129
+ for (const { node, next } of displaced.reverse())
130
+ document.head.insertBefore(node, next);
131
+ };
132
+ }
133
+ /** Compara un elemento del `<head>` con el tag que se quiere insertar. */
134
+ function elementMatchesTag(element, tag) {
135
+ if (element.localName !== tag.type)
136
+ return false;
137
+ if ((tag.content ?? '') !== (element.textContent ?? ''))
138
+ return false;
139
+ const expectedNames = Object.entries(tag.attrs)
140
+ .filter(([, value]) => value != null && value !== false)
141
+ .map(([name]) => name);
142
+ const actualNames = element.getAttributeNames?.() ?? [];
143
+ if (expectedNames.length !== actualNames.length)
144
+ return false;
145
+ return expectedNames.every((name) => {
146
+ const value = tag.attrs[name];
147
+ const expected = value === true ? '' : String(value);
148
+ return element.getAttribute(name) === expected;
149
+ });
150
+ }
151
+ function textContent(value) {
152
+ if (value == null)
153
+ return undefined;
154
+ if (typeof value === 'string' || typeof value === 'number')
155
+ return String(value);
156
+ if (Array.isArray(value)) {
157
+ const parts = value.map(textContent);
158
+ return parts.every((part) => part !== undefined) ? parts.join('') : undefined;
159
+ }
160
+ return undefined;
161
+ }
162
+ function innerHtml(value) {
163
+ if (!value || typeof value !== 'object')
164
+ return undefined;
165
+ const html = value.__html;
166
+ return html == null ? undefined : String(html);
167
+ }
168
+ function htmlAttributeName(name) {
169
+ const known = {
170
+ charSet: 'charset',
171
+ className: 'class',
172
+ crossOrigin: 'crossorigin',
173
+ httpEquiv: 'http-equiv',
174
+ noModule: 'nomodule',
175
+ };
176
+ return known[name] ?? name;
177
+ }
178
+ function findExistingHeadTag(tag) {
179
+ const candidates = Array.from(document.head.children);
180
+ if (tag.type === 'title' || tag.type === 'base') {
181
+ return candidates.find((element) => element.localName === tag.type) ?? null;
182
+ }
183
+ if (tag.type === 'meta') {
184
+ if (tag.attrs.name != null)
185
+ return findByAttribute(candidates, 'meta', 'name', tag.attrs.name);
186
+ if (tag.attrs.property != null)
187
+ return findByAttribute(candidates, 'meta', 'property', tag.attrs.property);
188
+ if (tag.attrs.charset != null) {
189
+ return candidates.find((element) => element.localName === 'meta' && element.hasAttribute('charset')) ?? null;
190
+ }
191
+ }
192
+ if (tag.type === 'script' && tag.attrs.src != null) {
193
+ return findByAttribute(candidates, 'script', 'src', tag.attrs.src);
194
+ }
195
+ if (tag.type === 'link' && String(tag.attrs.rel).toLowerCase() === 'canonical') {
196
+ return candidates.find((element) => element.localName === 'link' && element.getAttribute('rel')?.toLowerCase() === 'canonical') ?? null;
197
+ }
198
+ return null;
199
+ }
200
+ function findByAttribute(elements, tag, name, value) {
201
+ return elements.find((element) => element.localName === tag && element.getAttribute(name) === String(value)) ?? null;
202
+ }
6
203
  function Style({ children, media, nonce, title, id }) {
7
204
  return (0, react_1.createElement)('style', {
8
205
  ...(media ? { media } : {}),
package/dist/head.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react';
2
+ export type HeadAttributes = Record<string, string | number | boolean | null | undefined>;
3
+ export type HeadTagName = 'title' | 'meta' | 'link' | 'script' | 'style' | 'base' | 'noscript';
4
+ export interface CollectedHeadTag {
5
+ type: HeadTagName;
6
+ attrs: HeadAttributes;
7
+ content?: string;
8
+ }
9
+ export interface HeadProps {
10
+ children?: ReactNode;
11
+ }
package/dist/head.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { SUPPORTED_STYLE_EXTENSIONS, discoverStyleVariants } from './catalog';
2
2
  export { resolveAssetVariant } from './resolve';
3
3
  export { resolvePageAssets } from './page-assets';
4
- export { Script, Style } from './components';
4
+ export { Head, HeadCollectorProvider, Script, Style, applyHeadToDocument, clientSafeHeadTags, collectHeadChildren, headTagsSignature, } from './components';
5
5
  export type { AssetCatalog, AssetDefinition, AssetEntry, AssetIndex, AssetResolverOptions, AssetVariant, ResolvedAssetLinks } from './types';
6
6
  export type { ScriptProps, StyleProps } from './components';
7
+ export type { CollectedHeadTag, HeadAttributes, HeadProps, HeadTagName } from './head';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Style = exports.Script = exports.resolvePageAssets = exports.resolveAssetVariant = exports.discoverStyleVariants = exports.SUPPORTED_STYLE_EXTENSIONS = void 0;
3
+ exports.headTagsSignature = exports.collectHeadChildren = exports.clientSafeHeadTags = exports.applyHeadToDocument = exports.Style = exports.Script = exports.HeadCollectorProvider = exports.Head = exports.resolvePageAssets = exports.resolveAssetVariant = exports.discoverStyleVariants = exports.SUPPORTED_STYLE_EXTENSIONS = void 0;
4
4
  var catalog_1 = require("./catalog");
5
5
  Object.defineProperty(exports, "SUPPORTED_STYLE_EXTENSIONS", { enumerable: true, get: function () { return catalog_1.SUPPORTED_STYLE_EXTENSIONS; } });
6
6
  Object.defineProperty(exports, "discoverStyleVariants", { enumerable: true, get: function () { return catalog_1.discoverStyleVariants; } });
@@ -9,5 +9,11 @@ Object.defineProperty(exports, "resolveAssetVariant", { enumerable: true, get: f
9
9
  var page_assets_1 = require("./page-assets");
10
10
  Object.defineProperty(exports, "resolvePageAssets", { enumerable: true, get: function () { return page_assets_1.resolvePageAssets; } });
11
11
  var components_1 = require("./components");
12
+ Object.defineProperty(exports, "Head", { enumerable: true, get: function () { return components_1.Head; } });
13
+ Object.defineProperty(exports, "HeadCollectorProvider", { enumerable: true, get: function () { return components_1.HeadCollectorProvider; } });
12
14
  Object.defineProperty(exports, "Script", { enumerable: true, get: function () { return components_1.Script; } });
13
15
  Object.defineProperty(exports, "Style", { enumerable: true, get: function () { return components_1.Style; } });
16
+ Object.defineProperty(exports, "applyHeadToDocument", { enumerable: true, get: function () { return components_1.applyHeadToDocument; } });
17
+ Object.defineProperty(exports, "clientSafeHeadTags", { enumerable: true, get: function () { return components_1.clientSafeHeadTags; } });
18
+ Object.defineProperty(exports, "collectHeadChildren", { enumerable: true, get: function () { return components_1.collectHeadChildren; } });
19
+ Object.defineProperty(exports, "headTagsSignature", { enumerable: true, get: function () { return components_1.headTagsSignature; } });
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@ressjs/assets",
3
- "version": "0.6.0-experimental.0",
3
+ "version": "0.6.0-rc.0",
4
4
  "description": "Assets de ress.js: variantes de plataforma, Style y Script",
5
+ "license": "SEE LICENSE IN LICENSE",
5
6
  "types": "dist/index.d.ts",
6
7
  "main": "dist/index.js",
7
8
  "exports": {
@@ -11,17 +12,22 @@
11
12
  "import": "./dist/index.js"
12
13
  },
13
14
  "./client": {
14
- "types": "./dist/client/entry.d.mts",
15
- "require": "./dist/client/index.js",
16
- "import": "./dist/client/entry.mjs"
15
+ "import": {
16
+ "types": "./dist/client/entry.d.mts",
17
+ "default": "./dist/client/entry.mjs"
18
+ },
19
+ "require": {
20
+ "types": "./dist/client/index.d.ts",
21
+ "default": "./dist/client/index.js"
22
+ }
17
23
  }
18
24
  },
19
25
  "files": [
20
26
  "dist"
21
27
  ],
22
28
  "scripts": {
23
- "build": "tsc",
24
- "dev": "tsc --watch",
29
+ "build": "tsc && esbuild src/components.ts --bundle --platform=browser --format=esm --external:react --outfile=dist/client/entry.mjs",
30
+ "dev": "node scripts/watch.mjs",
25
31
  "clean": "rm -rf dist",
26
32
  "test": "vitest run",
27
33
  "prepublishOnly": "npm run build"
@@ -30,12 +36,15 @@
30
36
  "access": "public"
31
37
  },
32
38
  "dependencies": {
33
- "@ressjs/platform": "0.6.0-experimental.0",
39
+ "@ressjs/platform": "0.6.0-rc.0",
34
40
  "fast-glob": "^3.3.3"
35
41
  },
36
42
  "peerDependencies": {
37
43
  "react": "^19.0.0"
38
44
  },
45
+ "devDependencies": {
46
+ "esbuild": "^0.28.2"
47
+ },
39
48
  "engines": {
40
49
  "node": ">=20.19.0"
41
50
  }