@blinkk/root 1.4.4 → 1.4.6

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/core.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/config.ts","../src/core/components/Body.tsx","../src/core/components/Head.ts","../src/core/components/Script.ts","../src/core/hooks/useStringParams.ts","../src/core/hooks/useTranslations.ts"],"sourcesContent":["import {UserConfig as ViteUserConfig} from 'vite';\nimport {HtmlMinifyOptions} from '../render/html-minify.js';\nimport {HtmlPrettyOptions} from '../render/html-pretty.js';\nimport {Plugin} from './plugin.js';\nimport {RequestMiddleware} from './types.js';\n\nexport interface RootUserConfig {\n /**\n * Canonical domain the website will serve on. Useful for things like the\n * sitemap, SEO tags, etc.\n */\n domain?: string;\n\n /**\n * The base URL path that the site will serve on. Defaults to `/`;\n */\n base?: string;\n\n /**\n * Config for auto-injecting custom element dependencies.\n */\n elements?: {\n /**\n * A list of directories to use to look for custom elements. The dir path\n * should be relative to the project dir, e.g. \"path/to/elements\".\n */\n include?: string[];\n\n /**\n * A list of RegEx patterns to exclude. The string passed to the RegEx is\n * the file URL relative to the project root, e.g. \"/elements/foo/foo.ts\".\n */\n exclude?: RegExp[];\n };\n\n /**\n * Config options for localization and internationalization.\n */\n i18n?: RootI18nConfig;\n\n /**\n * Config options for the Root.js express server.\n */\n server?: RootServerConfig;\n\n /**\n * Build options for the `root build` command.\n */\n build?: RootBuildConfig;\n\n /**\n * Vite config.\n * @see {@link https://vitejs.dev/config/} for more information.\n */\n vite?: ViteUserConfig;\n\n /**\n * Whether to automatically minify HTML output. This is enabled by default,\n * in order to disable, pass `minifyHtml: false` to root.config.ts.\n */\n minifyHtml?: boolean;\n\n /**\n * Options to pass to html-minifier-terser.\n */\n minifyHtmlOptions?: HtmlMinifyOptions;\n\n /**\n * Whether to pretty print HTML output.\n */\n prettyHtml?: boolean;\n\n /**\n * Options to pass to js-beautify.\n */\n prettyHtmlOptions?: HtmlPrettyOptions;\n\n /**\n * Whether to include a sitemap.xml file to the build output.\n */\n sitemap?: boolean;\n\n /**\n * Plugins.\n */\n plugins?: Plugin[];\n\n /**\n * Experimental config options. Note: these are subject to change at any time.\n */\n experiments?: {\n /** Whether to render `<script>` tags with `async`. */\n enableScriptAsync?: boolean;\n };\n}\n\nexport type RootConfig = RootUserConfig & {\n rootDir: string;\n};\n\nexport interface LocaleGroup {\n label?: string;\n locales: string[];\n}\n\nexport interface RootI18nConfig {\n /**\n * Locales enabled for the site.\n */\n locales?: string[];\n\n /**\n * The default locale to use. Defaults is `en`.\n */\n defaultLocale?: string;\n\n /**\n * URL format for localized content. Default is `/[locale]/[base]/[path]`.\n */\n urlFormat?: string;\n\n /**\n * Localization groups, to help UIs (like Root.js CMS) logically group\n * locales.\n */\n groups?: Record<string, LocaleGroup>;\n}\n\nexport interface RootBuildConfig {\n /**\n * Excludes the `/intl/{defaultLocale}/...` path from the SSG build.\n */\n excludeDefaultLocaleFromIntlPaths?: boolean;\n}\n\nexport interface RootRedirectConfig {\n source: string;\n destination: string;\n type?: number;\n}\n\nexport interface RootHeaderConfig {\n /** A glob pattern match (regex not supported yet). */\n source: string;\n headers: Array<{\n key: string;\n value: string;\n }>;\n}\n\nexport interface ContentSecurityPolicyConfig {\n directives?: Record<string, string[]>;\n reportOnly?: boolean;\n}\n\nexport interface XFrameOptionsConfig {\n action: 'DENY' | 'SAMEORIGIN';\n}\n\nexport interface RootSecurityConfig {\n /**\n * Content-Security-Policy config. If enabled, a nonce is auto-generated\n * for every request and appended to script and stylesheet tags. You can\n * validate your CSP headers using a tool like {@link https://csp-evaluator.withgoogle.com/}.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP}\n */\n contentSecurityPolicy?: ContentSecurityPolicyConfig | boolean;\n\n /**\n * Strict-Transport-Security config. When enabled, the header value is set\n * to `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload`.\n */\n strictTransportSecurity?: boolean;\n\n /**\n * X-Content-Type-Options config. When enabled, the header value is set to\n * `X-Content-Type-Options: nosniff`.\n */\n xContentTypeOptions?: boolean;\n\n /**\n * X-Frame-Options config. Setting this value to `true` will default the\n * header value to `X-Frame-Options: SAMEORIGIN`.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options}\n */\n xFrameOptions?: 'DENY' | 'SAMEORIGIN' | boolean;\n\n /**\n * X-XSS-Protection config. When enabled, the header value is set to\n * `X-XSS-Protection: 1; mode=block`.\n */\n xXssProtection?: boolean;\n}\n\nexport interface RootServerConfig {\n /**\n * An array of middleware to add to the express server. These middleware are\n * added to the beginning of the express app.\n */\n middlewares?: RequestMiddleware[];\n\n /**\n * The `trailingSlash` config allows you to control how the server handles\n * trailing slashes. This config only affects URLs that do not have a file\n * extension (i.e. HTML paths).\n *\n * - When `true`, the server redirects URLs to add a trailing slash\n * - When `false`, the server redirects URLs to remove a trailing slash\n * - When unspecified, the server allows URLs with and without trailing slash\n */\n trailingSlash?: boolean;\n\n /**\n * Cookie secret for the session middleware.\n */\n sessionCookieSecret?: string | string[];\n\n /**\n * List of redirects.\n */\n redirects?: RootRedirectConfig[];\n\n /**\n * HTTP headers to add to a response.\n */\n headers?: RootHeaderConfig[];\n\n /**\n * HTTP security settings. By default, all security settings are enabled with\n * commonly used default values.\n */\n security?: RootSecurityConfig;\n}\n\nexport function defineConfig(config: RootUserConfig): RootUserConfig {\n return config;\n}\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\nimport {HTML_CONTEXT} from './Html.js';\n\nexport type BodyProps = preact.JSX.HTMLAttributes<HTMLBodyElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The `<Body>` component can be used to update attrs in the `<body>` tag.\n *\n * Usage:\n *\n * ```tsx\n * <Body className=\"body\">\n * <h1>Hello world</h1>\n * </Body>\n * ```\n *\n * Output:\n *\n * ```html\n * <body class=\"body\">\n * <h1>Hello world</h1>\n * </body>\n */\nexport const Body: FunctionalComponent<BodyProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Body> component'\n );\n }\n context.bodyAttrs = attrs;\n return <>{children}</>;\n};\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\nimport {HTML_CONTEXT} from './Html.js';\n\nexport type HeadProps = preact.JSX.HTMLAttributes<HTMLHeadElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The <Head> component can be used for injecting elements into the HTML head\n * tag from any part of a page. The <Head> can be added via any component or\n * sub-component and will automatically be hoisted to the `<head>` element.\n *\n * Usage:\n *\n * ```tsx\n * <Head>\n * <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n * </Head>\n * ```\n */\nexport const Head: FunctionalComponent<HeadProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Head> component'\n );\n }\n context.headComponents.push(children);\n context.headAttrs = attrs;\n return null;\n};\n","import {FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\nimport {HTML_CONTEXT} from './Html.js';\n\nexport type ScriptProps = preact.JSX.HTMLAttributes<HTMLScriptElement> & {\n // Cast the \"src\" attr to string. See:\n // https://github.com/blinkk/rootjs/issues/519\n src?: string;\n};\n\n/**\n * The <Script> component is used for rendering any custom script modules. At\n * the moment, the system only pre-renders and bundles files that are in the\n * `/bundles` folder at the root of the project.\n *\n * Usage:\n *\n * ```tsx\n * <Script src=\"/bundles/main.ts\" />\n * ```\n */\nexport const Script: FunctionalComponent<ScriptProps> = (props) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Script> component'\n );\n }\n context.scriptDeps.push(props);\n return null;\n};\n","import {createContext} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nexport type StringParamsContext = Record<string, string>;\n\nexport const STRING_PARAMS_CONTEXT = createContext<StringParamsContext | null>(\n null\n);\n\nexport const StringParamsProvider = STRING_PARAMS_CONTEXT.Provider;\n\n/**\n * A hook that returns a map of string params, configured via the\n * `StringParamsProvider` context provider. These params are automatically\n * applied to the `useTranslations()` hook.\n *\n *\n * Usage:\n *\n * ```\n * export default function Page() {\n * return (\n * <StringParamsProvider value={{name: 'Alice'}}>\n * <SayHello />\n * </StringParamsProvider>\n * );\n * }\n *\n * function SayHello() {\n * const t = useTranslations();\n * return <h1>{t('Hello, {name}!')}</h1>;\n * }\n * ```\n *\n * This should render `<h1>Hello, Alice!</h1>`.\n */\nexport function useStringParams() {\n return useContext(STRING_PARAMS_CONTEXT) || {};\n}\n","import {I18nContext, useI18nContext} from './useI18nContext.js';\nimport {useStringParams} from './useStringParams.js';\n\n/**\n * A hook that returns a function that can be used to translate a string, and\n * optionally replace any parameterized values that are surrounded in curly\n * braces.\n *\n * Usage:\n *\n * ```ts\n * const t = useTranslations();\n * t('Hello {name}', {name: 'Bob'});\n * // => 'Bounjour Bob'\n */\nexport function useTranslations() {\n // Ignore I18nContext not found error when used with client-side rehydration.\n let i18nContext: I18nContext | null = null;\n try {\n i18nContext = useI18nContext();\n } catch (err) {\n console.warn('I18nContext not found');\n }\n const translations = i18nContext?.translations || {};\n const stringParams = useStringParams();\n const t = (str: string, params?: Record<string, string | number>) => {\n const key = normalizeString(str);\n let translation = translations[key] ?? key ?? '';\n const allParams = {...stringParams, ...params};\n for (const paramName of Object.keys(allParams)) {\n const paramValue = String(allParams[paramName] ?? '');\n translation = translation.replaceAll(`{${paramName}}`, paramValue);\n }\n return translation;\n };\n return t;\n}\n\n/**\n * Cleans a string that's used for translations. Performs the following:\n * - Removes any leading/trailing whitespace\n * - Removes spaces at the end of any line\n */\nexport function normalizeString(str: string) {\n const lines = String(str)\n .trim()\n .split('\\n')\n .map((line) => removeTrailingWhitespace(line));\n return lines.join('\\n');\n}\n\nfunction removeTrailingWhitespace(str: string) {\n return String(str)\n .trimEnd()\n .replace(/&nbsp;$/, '');\n}\n"],"mappings":";;;;;;;;;;;;;AA4OO,SAAS,aAAa,QAAwC;AACnE,SAAO;AACT;;;AC7OA,SAAQ,kBAAiB;AAiChB;AARF,IAAM,OAAuC,CAAC,EAAC,UAAU,GAAG,MAAK,MAAM;AAC5E,QAAM,UAAU,WAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,YAAY;AACpB,SAAO,gCAAG,UAAS;AACrB;;;AClCA,SAAQ,cAAAA,mBAAiB;AAoBlB,IAAM,OAAuC,CAAC,EAAC,UAAU,GAAG,MAAK,MAAM;AAC5E,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,eAAe,KAAK,QAAQ;AACpC,UAAQ,YAAY;AACpB,SAAO;AACT;;;AC9BA,SAAQ,cAAAC,mBAAiB;AAoBlB,IAAM,SAA2C,CAAC,UAAU;AACjE,QAAM,UAAUC,YAAW,YAAY;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,UAAQ,WAAW,KAAK,KAAK;AAC7B,SAAO;AACT;;;AC9BA,SAAQ,qBAAoB;AAC5B,SAAQ,cAAAC,mBAAiB;AAIlB,IAAM,wBAAwB;AAAA,EACnC;AACF;AAEO,IAAM,uBAAuB,sBAAsB;AA2BnD,SAAS,kBAAkB;AAChC,SAAOA,YAAW,qBAAqB,KAAK,CAAC;AAC/C;;;ACvBO,SAAS,kBAAkB;AAEhC,MAAI,cAAkC;AACtC,MAAI;AACF,kBAAc,eAAe;AAAA,EAC/B,SAAS,KAAK;AACZ,YAAQ,KAAK,uBAAuB;AAAA,EACtC;AACA,QAAM,eAAe,aAAa,gBAAgB,CAAC;AACnD,QAAM,eAAe,gBAAgB;AACrC,QAAM,IAAI,CAAC,KAAa,WAA6C;AACnE,UAAM,MAAM,gBAAgB,GAAG;AAC/B,QAAI,cAAc,aAAa,GAAG,KAAK,OAAO;AAC9C,UAAM,YAAY,EAAC,GAAG,cAAc,GAAG,OAAM;AAC7C,eAAW,aAAa,OAAO,KAAK,SAAS,GAAG;AAC9C,YAAM,aAAa,OAAO,UAAU,SAAS,KAAK,EAAE;AACpD,oBAAc,YAAY,WAAW,IAAI,SAAS,KAAK,UAAU;AAAA,IACnE;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOO,SAAS,gBAAgB,KAAa;AAC3C,QAAM,QAAQ,OAAO,GAAG,EACrB,KAAK,EACL,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,yBAAyB,IAAI,CAAC;AAC/C,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,yBAAyB,KAAa;AAC7C,SAAO,OAAO,GAAG,EACd,QAAQ,EACR,QAAQ,WAAW,EAAE;AAC1B;","names":["useContext","useContext","useContext","useContext","useContext"]}
1
+ {"version":3,"sources":["../src/core/config.ts","../src/core/components/Body.tsx","../src/core/components/Head.ts","../src/core/components/Script.ts","../src/core/hooks/useStringParams.tsx","../src/core/hooks/useTranslationsMiddleware.tsx","../src/core/hooks/useTranslations.ts"],"sourcesContent":["import {UserConfig as ViteUserConfig} from 'vite';\nimport {HtmlMinifyOptions} from '../render/html-minify.js';\nimport {HtmlPrettyOptions} from '../render/html-pretty.js';\nimport {Plugin} from './plugin.js';\nimport {RequestMiddleware} from './types.js';\n\nexport interface RootUserConfig {\n /**\n * Canonical domain the website will serve on. Useful for things like the\n * sitemap, SEO tags, etc.\n */\n domain?: string;\n\n /**\n * The base URL path that the site will serve on. Defaults to `/`;\n */\n base?: string;\n\n /**\n * Config for auto-injecting custom element dependencies.\n */\n elements?: {\n /**\n * A list of directories to use to look for custom elements. The dir path\n * should be relative to the project dir, e.g. \"path/to/elements\".\n */\n include?: string[];\n\n /**\n * A list of RegEx patterns to exclude. The string passed to the RegEx is\n * the file URL relative to the project root, e.g. \"/elements/foo/foo.ts\".\n */\n exclude?: RegExp[];\n };\n\n /**\n * Config options for localization and internationalization.\n */\n i18n?: RootI18nConfig;\n\n /**\n * Config options for the Root.js express server.\n */\n server?: RootServerConfig;\n\n /**\n * Build options for the `root build` command.\n */\n build?: RootBuildConfig;\n\n /**\n * Vite config.\n * @see {@link https://vitejs.dev/config/} for more information.\n */\n vite?: ViteUserConfig;\n\n /**\n * Whether to automatically minify HTML output. This is enabled by default,\n * in order to disable, pass `minifyHtml: false` to root.config.ts.\n */\n minifyHtml?: boolean;\n\n /**\n * Options to pass to html-minifier-terser.\n */\n minifyHtmlOptions?: HtmlMinifyOptions;\n\n /**\n * Whether to pretty print HTML output.\n */\n prettyHtml?: boolean;\n\n /**\n * Options to pass to js-beautify.\n */\n prettyHtmlOptions?: HtmlPrettyOptions;\n\n /**\n * Whether to include a sitemap.xml file to the build output.\n */\n sitemap?: boolean;\n\n /**\n * Plugins.\n */\n plugins?: Plugin[];\n\n /**\n * Experimental config options. Note: these are subject to change at any time.\n */\n experiments?: {\n /** Whether to render `<script>` tags with `async`. */\n enableScriptAsync?: boolean;\n };\n}\n\nexport type RootConfig = RootUserConfig & {\n rootDir: string;\n};\n\nexport interface LocaleGroup {\n label?: string;\n locales: string[];\n}\n\nexport interface RootI18nConfig {\n /**\n * Locales enabled for the site.\n */\n locales?: string[];\n\n /**\n * The default locale to use. Defaults is `en`.\n */\n defaultLocale?: string;\n\n /**\n * URL format for localized content. Default is `/[locale]/[base]/[path]`.\n */\n urlFormat?: string;\n\n /**\n * Localization groups, to help UIs (like Root.js CMS) logically group\n * locales.\n */\n groups?: Record<string, LocaleGroup>;\n}\n\nexport interface RootBuildConfig {\n /**\n * Excludes the `/intl/{defaultLocale}/...` path from the SSG build.\n */\n excludeDefaultLocaleFromIntlPaths?: boolean;\n}\n\nexport interface RootRedirectConfig {\n /**\n * The source path to redirect. Accepts placeholders in the format\n * `[key]` or `[...key]`. Use `[key]` for single segments and\n * `[...key]` for multi-segment wildcards.\n * @example \"/old-path/[id]\" or \"/old-path/[...wildcard]\"\n */\n source: string;\n\n /**\n * The destination to redirect to. Placeholders from the source can\n * optionally be inserted into the destination using the same\n * placeholder format.\n * @example \"/new-path/[id]\" or \"/new-path/[...wildcard]\"\n */\n destination: string;\n\n /**\n * The redirect type (`301` = permanent, `302` = temporary). If unspecified,\n * defaults to `302` (temporary).\n */\n type?: 301 | 302;\n}\n\nexport interface RootHeaderConfig {\n /** A glob pattern match (regex not supported yet). */\n source: string;\n headers: Array<{\n key: string;\n value: string;\n }>;\n}\n\nexport interface ContentSecurityPolicyConfig {\n directives?: Record<string, string[]>;\n reportOnly?: boolean;\n}\n\nexport interface XFrameOptionsConfig {\n action: 'DENY' | 'SAMEORIGIN';\n}\n\nexport interface RootSecurityConfig {\n /**\n * Content-Security-Policy config. If enabled, a nonce is auto-generated\n * for every request and appended to script and stylesheet tags. You can\n * validate your CSP headers using a tool like {@link https://csp-evaluator.withgoogle.com/}.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP}\n */\n contentSecurityPolicy?: ContentSecurityPolicyConfig | boolean;\n\n /**\n * Strict-Transport-Security config. When enabled, the header value is set\n * to `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload`.\n */\n strictTransportSecurity?: boolean;\n\n /**\n * X-Content-Type-Options config. When enabled, the header value is set to\n * `X-Content-Type-Options: nosniff`.\n */\n xContentTypeOptions?: boolean;\n\n /**\n * X-Frame-Options config. Setting this value to `true` will default the\n * header value to `X-Frame-Options: SAMEORIGIN`.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options}\n */\n xFrameOptions?: 'DENY' | 'SAMEORIGIN' | boolean;\n\n /**\n * X-XSS-Protection config. When enabled, the header value is set to\n * `X-XSS-Protection: 1; mode=block`.\n */\n xXssProtection?: boolean;\n}\n\nexport interface RootServerConfig {\n /**\n * An array of middleware to add to the express server. These middleware are\n * added to the beginning of the express app.\n */\n middlewares?: RequestMiddleware[];\n\n /**\n * The `trailingSlash` config allows you to control how the server handles\n * trailing slashes. This config only affects URLs that do not have a file\n * extension (i.e. HTML paths).\n *\n * - When `true`, the server redirects URLs to add a trailing slash\n * - When `false`, the server redirects URLs to remove a trailing slash\n * - When unspecified, the server allows URLs with and without trailing slash\n */\n trailingSlash?: boolean;\n\n /**\n * Cookie secret for the session middleware.\n */\n sessionCookieSecret?: string | string[];\n\n /**\n * List of redirects. Supports optional wildcards.\n *\n * @example\n * ```ts\n * redirects: [\n * {\n * source: '/old-path/[id]',\n * destination: '/new-path/[id]',\n * type: 301,\n * },\n * {\n * source: '/old-path/[...wildcard]',\n * destination: '/new-path/[...wildcard]',\n * },\n * ]\n * ```\n */\n redirects?: RootRedirectConfig[];\n\n /**\n * HTTP headers to add to a response.\n */\n headers?: RootHeaderConfig[];\n\n /**\n * HTTP security settings. By default, all security settings are enabled with\n * commonly used default values.\n */\n security?: RootSecurityConfig;\n}\n\nexport function defineConfig(config: RootUserConfig): RootUserConfig {\n return config;\n}\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\nimport {HTML_CONTEXT} from './Html.js';\n\nexport type BodyProps = preact.JSX.HTMLAttributes<HTMLBodyElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The `<Body>` component can be used to update attrs in the `<body>` tag.\n *\n * Usage:\n *\n * ```tsx\n * <Body className=\"body\">\n * <h1>Hello world</h1>\n * </Body>\n * ```\n *\n * Output:\n *\n * ```html\n * <body class=\"body\">\n * <h1>Hello world</h1>\n * </body>\n */\nexport const Body: FunctionalComponent<BodyProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Body> component'\n );\n }\n context.bodyAttrs = attrs;\n return <>{children}</>;\n};\n","import {ComponentChildren, FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\nimport {HTML_CONTEXT} from './Html.js';\n\nexport type HeadProps = preact.JSX.HTMLAttributes<HTMLHeadElement> & {\n children?: ComponentChildren;\n};\n\n/**\n * The <Head> component can be used for injecting elements into the HTML head\n * tag from any part of a page. The <Head> can be added via any component or\n * sub-component and will automatically be hoisted to the `<head>` element.\n *\n * Usage:\n *\n * ```tsx\n * <Head>\n * <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n * </Head>\n * ```\n */\nexport const Head: FunctionalComponent<HeadProps> = ({children, ...attrs}) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Head> component'\n );\n }\n context.headComponents.push(children);\n context.headAttrs = attrs;\n return null;\n};\n","import {FunctionalComponent} from 'preact';\nimport {useContext} from 'preact/hooks';\nimport {HTML_CONTEXT} from './Html.js';\n\nexport type ScriptProps = preact.JSX.HTMLAttributes<HTMLScriptElement> & {\n // Cast the \"src\" attr to string. See:\n // https://github.com/blinkk/rootjs/issues/519\n src?: string;\n};\n\n/**\n * The <Script> component is used for rendering any custom script modules. At\n * the moment, the system only pre-renders and bundles files that are in the\n * `/bundles` folder at the root of the project.\n *\n * Usage:\n *\n * ```tsx\n * <Script src=\"/bundles/main.ts\" />\n * ```\n */\nexport const Script: FunctionalComponent<ScriptProps> = (props) => {\n const context = useContext(HTML_CONTEXT);\n if (!context) {\n throw new Error(\n 'HTML_CONTEXT not found, double-check usage of the <Script> component'\n );\n }\n context.scriptDeps.push(props);\n return null;\n};\n","import {ComponentChildren, FunctionalComponent, createContext} from 'preact';\nimport {useContext} from 'preact/hooks';\n\nexport type StringParamsContext = Record<string, string>;\n\nexport const STRING_PARAMS_CONTEXT = createContext<StringParamsContext | null>(\n null\n);\n\nexport interface StringParamsProviderProps {\n value?: StringParamsContext;\n children?: ComponentChildren;\n}\n\nexport const StringParamsProvider: FunctionalComponent<StringParamsProviderProps> = (props) => {\n // Allow for nested param values from parent content providers.\n const parent = useContext(STRING_PARAMS_CONTEXT) || {};\n const merged = {...parent, ...props.value};\n return (\n <STRING_PARAMS_CONTEXT.Provider value={merged}>\n {props.children}\n </STRING_PARAMS_CONTEXT.Provider>\n );\n};\n\n/**\n * A hook that returns a map of string params, configured via the\n * `StringParamsProvider` context provider. These params are automatically\n * applied to the `useTranslations()` hook.\n *\n *\n * Usage:\n *\n * ```\n * export default function Page() {\n * return (\n * <StringParamsProvider value={{name: 'Alice'}}>\n * <SayHello />\n * </StringParamsProvider>\n * );\n * }\n *\n * function SayHello() {\n * const t = useTranslations();\n * return <h1>{t('Hello, {name}!')}</h1>;\n * }\n * ```\n *\n * This should render `<h1>Hello, Alice!</h1>`.\n */\nexport function useStringParams() {\n return useContext(STRING_PARAMS_CONTEXT) || {};\n}\n","import {ComponentChildren, FunctionalComponent, createContext} from 'preact';\nimport {useContext} from 'preact/hooks';\n\ntype TransformFn = (str: string) => string;\n\nexport interface TranslationMiddleware {\n /** Transform the string before translation lookup. */\n beforeTranslate?: TransformFn;\n /** Transform the string after translation lookup. */\n afterTranslate?: TransformFn;\n /** Transform the string before `{param}` values are replaced. */\n beforeReplaceParams?: TransformFn;\n /** Transform the string after `{param}` values are replaced. */\n afterReplaceParams?: TransformFn;\n}\n\nexport interface TranslationMiddlewareContext {\n beforeTranslateFns: TransformFn[];\n afterTranslateFns: TransformFn[];\n beforeReplaceParamsFns: TransformFn[];\n afterReplaceParamsFns: TransformFn[];\n}\n\nconst TRANSLATION_MIDDLEWARE_CONTEXT =\n createContext<TranslationMiddlewareContext | null>(null);\n\nexport interface TranslationMiddlewareProviderProps {\n value?: TranslationMiddleware;\n children?: ComponentChildren;\n}\n\nexport const TranslationMiddlewareProvider: FunctionalComponent<\n TranslationMiddlewareProviderProps\n> = (props) => {\n const parent = useContext(TRANSLATION_MIDDLEWARE_CONTEXT) || {\n beforeTranslateFns: [],\n afterTranslateFns: [],\n beforeReplaceParamsFns: [],\n afterReplaceParamsFns: [],\n };\n const merged: TranslationMiddlewareContext = {\n beforeTranslateFns: [...parent.beforeTranslateFns],\n afterTranslateFns: [...parent.afterTranslateFns],\n beforeReplaceParamsFns: [...parent.beforeReplaceParamsFns],\n afterReplaceParamsFns: [...parent.afterReplaceParamsFns],\n };\n if (props.value?.beforeTranslate) {\n merged.beforeTranslateFns.push(props.value.beforeTranslate);\n }\n if (props.value?.afterTranslate) {\n merged.afterTranslateFns.push(props.value.afterTranslate);\n }\n if (props.value?.beforeReplaceParams) {\n merged.beforeReplaceParamsFns.push(props.value.beforeReplaceParams);\n }\n if (props.value?.afterReplaceParams) {\n merged.afterReplaceParamsFns.push(props.value.afterReplaceParams);\n }\n return (\n <TRANSLATION_MIDDLEWARE_CONTEXT.Provider value={merged}>\n {props.children}\n </TRANSLATION_MIDDLEWARE_CONTEXT.Provider>\n );\n};\n\nexport function useTranslationMiddleware(): TranslationMiddlewareContext {\n return (\n useContext(TRANSLATION_MIDDLEWARE_CONTEXT) || {\n beforeTranslateFns: [],\n afterTranslateFns: [],\n beforeReplaceParamsFns: [],\n afterReplaceParamsFns: [],\n }\n );\n}\n","import {I18nContext, useI18nContext} from './useI18nContext.js';\nimport {useStringParams} from './useStringParams.js';\nimport {useTranslationMiddleware} from './useTranslationsMiddleware.js';\n\n/**\n * A hook that returns a function that can be used to translate a string, and\n * optionally replace any parameterized values that are surrounded in curly\n * braces.\n *\n * Usage:\n *\n * ```ts\n * const t = useTranslations();\n * t('Hello {name}', {name: 'Bob'});\n * // => 'Bounjour Bob'\n */\nexport function useTranslations() {\n // Ignore I18nContext not found error when used with client-side rehydration.\n let i18nContext: I18nContext | null = null;\n try {\n i18nContext = useI18nContext();\n } catch (err) {\n console.warn('I18nContext not found');\n }\n const translations = i18nContext?.translations || {};\n const stringParams = useStringParams();\n const middleware = useTranslationMiddleware();\n const t = (str: string, params?: Record<string, string | number>) => {\n let input = normalizeString(str);\n middleware.beforeTranslateFns.forEach((fn) => {\n input = fn(input);\n });\n let translation = translations[input] ?? input ?? '';\n middleware.afterTranslateFns.forEach((fn) => {\n translation = fn(translation);\n });\n\n // Replace string params, e.g. \"Hello, {name}\".\n middleware.beforeReplaceParamsFns.forEach((fn) => {\n translation = fn(translation);\n });\n if (testHasStringParams(translation)) {\n translation = replaceStringParams(translation, {\n ...stringParams,\n ...params,\n });\n }\n middleware.afterReplaceParamsFns.forEach((fn) => {\n translation = fn(translation);\n });\n\n return translation;\n };\n return t;\n}\n\n/**\n * Cleans a string that's used for translations. Performs the following:\n * - Removes any leading/trailing whitespace\n * - Removes spaces at the end of any line\n */\nexport function normalizeString(str: string) {\n const lines = String(str)\n .trim()\n .split('\\n')\n .map((line) => removeTrailingWhitespace(line));\n return lines.join('\\n');\n}\n\nfunction removeTrailingWhitespace(str: string) {\n return String(str)\n .trimEnd()\n .replace(/&nbsp;$/, '');\n}\n\nfunction testHasStringParams(str: string) {\n return str.includes('{') && str.includes('}');\n}\n\n/**\n * Replaces string placeholder params, e.g.\n *\n * ```\n * replaceStringParams('Hello, {name}!', {name: 'Joe'})\n * // => 'Hello, Joe!'\n * ```\n */\nfunction replaceStringParams(\n str: string,\n params: Record<string, string | number>\n): string {\n return str.replace(/{([^}]+)}/g, (match, key) => {\n if (key in params) {\n return String(params[key]);\n }\n return match;\n });\n}\n"],"mappings":"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","names":["useContext","useContext","useContext","useContext","useContext","jsx","createContext","useContext","jsx"]}
package/dist/functions.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createPreviewServer,
3
3
  createProdServer
4
- } from "./chunk-E4VJEBX4.js";
4
+ } from "./chunk-QACG35TR.js";
5
5
  import "./chunk-DISVDCLA.js";
6
6
  import "./chunk-HTBDTUFE.js";
7
7
  import "./chunk-MA6MSEP4.js";
@@ -1,5 +1,5 @@
1
- import { b as RootConfig, r as Request, s as Response, N as NextFunction } from './types-ddh7SQTZ.js';
2
- export { z as SESSION_COOKIE, B as SaveSessionOptions, E as Session, A as SessionMiddlewareOptions, D as sessionMiddleware } from './types-ddh7SQTZ.js';
1
+ import { b as RootConfig, r as Request, s as Response, N as NextFunction } from './types-ZCQ3--KU.js';
2
+ export { B as SESSION_COOKIE, E as SaveSessionOptions, I as Session, D as SessionMiddlewareOptions, F as sessionMiddleware } from './types-ZCQ3--KU.js';
3
3
  import { RequestHandler } from 'express';
4
4
  import 'preact';
5
5
  import 'vite';
package/dist/node.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { b as RootConfig } from './types-ddh7SQTZ.js';
1
+ import { b as RootConfig } from './types-ZCQ3--KU.js';
2
2
  import { PackageInfo } from 'workspace-tools';
3
3
  import { ViteDevServer } from 'vite';
4
4
  import 'express';
package/dist/render.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { F as Renderer } from './types-ddh7SQTZ.js';
1
+ export { J as Renderer } from './types-ZCQ3--KU.js';
2
2
  import 'express';
3
3
  import 'preact';
4
4
  import 'vite';
package/dist/render.js CHANGED
@@ -13,6 +13,7 @@ import {
13
13
 
14
14
  // src/render/render.tsx
15
15
  import crypto from "node:crypto";
16
+ import path2 from "node:path";
16
17
  import {
17
18
  options as preactOptions
18
19
  } from "preact";
@@ -12949,12 +12950,13 @@ var Router = class {
12949
12950
  }
12950
12951
  async getAllPathsForRoute(urlPathFormat, route) {
12951
12952
  const routeModule = route.module;
12952
- if (!routeModule.default) {
12953
+ if (!routeModule.default && !routeModule.getStaticContent) {
12953
12954
  return [];
12954
12955
  }
12955
12956
  const trailingSlash = this.rootConfig.server?.trailingSlash;
12956
12957
  const urlPaths = [];
12957
- if (routeModule.getStaticPaths) {
12958
+ const pathHasParams = testPathHasParams(urlPathFormat);
12959
+ if (pathHasParams && routeModule.getStaticPaths) {
12958
12960
  const staticPaths = await routeModule.getStaticPaths({
12959
12961
  rootConfig: this.rootConfig
12960
12962
  });
@@ -12965,9 +12967,9 @@ var Router = class {
12965
12967
  urlPathFormat,
12966
12968
  pathParams.params || {}
12967
12969
  );
12968
- if (pathContainsPlaceholders(urlPath)) {
12970
+ if (testPathHasParams(urlPath)) {
12969
12971
  console.warn(
12970
- `path contains placeholders: ${urlPathFormat}, double check getStaticPaths() and ensure all params are returned. more info: https://rootjs.dev/guide/routes#getStaticPaths`
12972
+ `path contains params: ${urlPathFormat}, double check getStaticPaths() and ensure all params are returned. more info: https://rootjs.dev/guide/routes#getStaticPaths`
12971
12973
  );
12972
12974
  } else {
12973
12975
  urlPaths.push({
@@ -12978,14 +12980,14 @@ var Router = class {
12978
12980
  }
12979
12981
  );
12980
12982
  }
12981
- } else if (routeModule.getStaticProps && !pathContainsPlaceholders(urlPathFormat)) {
12983
+ } else if (routeModule.getStaticProps && !pathHasParams) {
12982
12984
  urlPaths.push({ urlPath: normalizeUrlPath(urlPathFormat), params: {} });
12983
- } else if (!routeModule.handle && !pathContainsPlaceholders(urlPathFormat)) {
12985
+ } else if (!routeModule.handle && !pathHasParams) {
12984
12986
  urlPaths.push({ urlPath: normalizeUrlPath(urlPathFormat), params: {} });
12985
- } else if (pathContainsPlaceholders(urlPathFormat) && !routeModule.handle && !routeModule.getStaticPaths) {
12987
+ } else if (pathHasParams && !routeModule.handle && !routeModule.getStaticPaths) {
12986
12988
  console.warn(
12987
12989
  [
12988
- `warning: path contains placeholders: ${urlPathFormat}.`,
12990
+ `warning: path contains params: ${urlPathFormat}.`,
12989
12991
  `define either ssg getStaticPaths() or ssr handle() for route: ${route.src}.`,
12990
12992
  "more info: https://rootjs.dev/guide/routes"
12991
12993
  ].join("\n")
@@ -13009,8 +13011,8 @@ function replaceParams(urlPathFormat, params) {
13009
13011
  }
13010
13012
  function normalizeUrlPath(urlPath, options) {
13011
13013
  urlPath = urlPath.replace(/\/+/g, "/");
13012
- if (options?.trailingSlash === false && urlPath !== "/" && urlPath.endsWith("/")) {
13013
- urlPath = urlPath.replace(/\/*$/g, "");
13014
+ if (testPathHasFileExt(urlPath) || options?.trailingSlash === false && urlPath !== "/" && urlPath.endsWith("/")) {
13015
+ urlPath = removeTrailingSlash(urlPath);
13014
13016
  }
13015
13017
  if (urlPath.endsWith("/index")) {
13016
13018
  urlPath = urlPath.slice(0, -6);
@@ -13018,19 +13020,29 @@ function normalizeUrlPath(urlPath, options) {
13018
13020
  if (!urlPath.startsWith("/")) {
13019
13021
  urlPath = `/${urlPath}`;
13020
13022
  }
13021
- if (options?.trailingSlash && !urlPath.endsWith("/")) {
13023
+ if (options?.trailingSlash && !testPathHasFileExt(urlPath) && !urlPath.endsWith("/")) {
13022
13024
  urlPath = `${urlPath}/`;
13023
13025
  }
13024
13026
  return urlPath;
13025
13027
  }
13026
- function pathContainsPlaceholders(urlPath) {
13028
+ function testPathHasParams(urlPath) {
13027
13029
  const segments = urlPath.split("/");
13028
13030
  return segments.some((segment) => {
13029
- return segment.startsWith("[") && segment.endsWith("]");
13031
+ return segment.startsWith("[") && segment.includes("]");
13030
13032
  });
13031
13033
  }
13034
+ function testPathHasFileExt(urlPath) {
13035
+ const basename = path.basename(removeTrailingSlash(urlPath));
13036
+ return basename.includes(".");
13037
+ }
13032
13038
  function removeSlashes(str) {
13033
- return str.replace(/^\/*/g, "").replace(/\/*$/g, "");
13039
+ return removeTrailingSlash(removeLeadingSlash(str));
13040
+ }
13041
+ function removeLeadingSlash(str) {
13042
+ return str.replace(/^\/*/g, "");
13043
+ }
13044
+ function removeTrailingSlash(str) {
13045
+ return str.replace(/\/*$/g, "");
13034
13046
  }
13035
13047
  function toSquareBrackets(str) {
13036
13048
  if (str.includes("{") || str.includes("}")) {
@@ -13043,6 +13055,31 @@ function toSquareBrackets(str) {
13043
13055
 
13044
13056
  // src/render/render.tsx
13045
13057
  import { jsx as jsx4, jsxs as jsxs4 } from "preact/jsx-runtime";
13058
+ var CONTENT_TYPES = {
13059
+ "html": "text/html",
13060
+ "htm": "text/html",
13061
+ "css": "text/css",
13062
+ "js": "application/javascript",
13063
+ "json": "application/json",
13064
+ "png": "image/png",
13065
+ "jpg": "image/jpeg",
13066
+ "jpeg": "image/jpeg",
13067
+ "gif": "image/gif",
13068
+ "svg": "image/svg+xml",
13069
+ "txt": "text/plain",
13070
+ "xml": "application/xml",
13071
+ "pdf": "application/pdf",
13072
+ "zip": "application/zip",
13073
+ "mp4": "video/mp4",
13074
+ "webm": "video/webm",
13075
+ "mp3": "audio/mpeg",
13076
+ "wav": "audio/wav",
13077
+ "woff": "font/woff",
13078
+ "woff2": "font/woff2",
13079
+ "ttf": "font/ttf",
13080
+ "otf": "font/otf",
13081
+ "wasm": "application/wasm"
13082
+ };
13046
13083
  var Renderer = class {
13047
13084
  rootConfig;
13048
13085
  // private routes: RouteTrie<Route>;
@@ -13143,6 +13180,34 @@ var Renderer = class {
13143
13180
  });
13144
13181
  res.end(html);
13145
13182
  };
13183
+ if (route.module.getStaticContent) {
13184
+ let props2;
13185
+ if (route.module.getStaticProps) {
13186
+ props2 = await route.module.getStaticProps({
13187
+ rootConfig: this.rootConfig,
13188
+ params: routeParams
13189
+ });
13190
+ } else {
13191
+ props2 = { rootConfig: this.rootConfig, params: routeParams };
13192
+ }
13193
+ const result = await route.module.getStaticContent(props2);
13194
+ let body;
13195
+ let contentType;
13196
+ if (typeof result === "string" || Buffer.isBuffer(result)) {
13197
+ body = result;
13198
+ } else if (result && typeof result === "object") {
13199
+ body = result.body;
13200
+ contentType = result.contentType;
13201
+ } else {
13202
+ body = "";
13203
+ }
13204
+ res.status(200);
13205
+ const ext = path2.extname(route.routePath);
13206
+ res.set({
13207
+ "Content-Type": contentType || guessContentType(ext)
13208
+ });
13209
+ return res.end(body);
13210
+ }
13146
13211
  if (route.module.handle) {
13147
13212
  const handlerContext = {
13148
13213
  route,
@@ -13619,6 +13684,10 @@ function sortLocales(a, b) {
13619
13684
  }
13620
13685
  return a.localeCompare(b);
13621
13686
  }
13687
+ function guessContentType(ext) {
13688
+ const normalized = ext.trim().toLowerCase().replace(/^\./, "");
13689
+ return CONTENT_TYPES[normalized] || "application/octet-stream";
13690
+ }
13622
13691
  export {
13623
13692
  Renderer
13624
13693
  };