├── .github ├── FUNDING.yml ├── workflows │ ├── test.yml │ └── release.yml └── pull_request_template.md ├── examples ├── angular │ ├── src │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── counter │ │ │ │ ├── counter.component.css │ │ │ │ ├── counter.component.html │ │ │ │ ├── counter.component.ts │ │ │ │ └── counter.component.spec.ts │ │ │ ├── app.routes.ts │ │ │ ├── app.routes.server.ts │ │ │ ├── app.config.client.ts │ │ │ ├── app.config.ts │ │ │ ├── app.config.server.ts │ │ │ ├── app.component.spec.ts │ │ │ └── app.component.ts │ │ ├── styles.css │ │ ├── index.html │ │ ├── main.ts │ │ └── main.server.ts │ ├── public │ │ └── favicon.ico │ ├── tsconfig.spec.json │ ├── tsconfig.app.json │ └── tsconfig.json ├── vite-ssr-react-ts │ ├── src │ │ ├── SchemaOrg.tsx │ │ ├── vite-env.d.ts │ │ ├── entry-client.tsx │ │ ├── entry-server.tsx │ │ └── App.css │ ├── vite.config.ts │ ├── _gitignore │ ├── index.html │ ├── tsconfig.node.json │ ├── tsconfig.json │ └── package.json ├── vite-ssr-ts │ ├── src │ │ ├── vite-env.d.ts │ │ ├── entry-client.ts │ │ ├── counter.ts │ │ └── entry-server.ts │ ├── _gitignore │ ├── index.html │ ├── tsconfig.json │ └── package.json ├── vite-ssr-svelte │ ├── src │ │ ├── vite-env.d.ts │ │ ├── lib │ │ │ ├── SchemaOrg.svelte │ │ │ └── Counter.svelte │ │ ├── entry-client.ts │ │ └── entry-server.ts │ ├── svelte.config.js │ ├── vite.config.ts │ ├── index.html │ ├── tsconfig.node.json │ ├── tsconfig.json │ └── package.json ├── vite-ssr-vue-prerender │ ├── src │ │ ├── assets │ │ │ ├── logo.png │ │ │ └── fonts │ │ │ │ ├── Inter-Italic.woff │ │ │ │ └── Inter-Italic.woff2 │ │ ├── pages │ │ │ ├── transform.vue │ │ │ ├── Red.vue │ │ │ ├── Home.vue │ │ │ ├── schema-org.vue │ │ │ ├── custom-script.vue │ │ │ ├── xss.vue │ │ │ ├── timer-script.vue │ │ │ ├── timer-script2.vue │ │ │ ├── error-script.vue │ │ │ ├── stripe.vue │ │ │ ├── idle-script.vue │ │ │ ├── stripe-pricing-table.vue │ │ │ ├── manual-script.vue │ │ │ ├── ref-trigger.vue │ │ │ ├── js-confetti.vue │ │ │ └── fathom.vue │ │ ├── entry-client.js │ │ ├── main.js │ │ └── router.js │ ├── public │ │ └── myScript.js │ ├── index.html │ ├── vite.config.noexternal.js │ └── package.json ├── vite-ssr-vue │ ├── src │ │ ├── vite-env.d.ts │ │ ├── entry-client.ts │ │ ├── main.ts │ │ ├── assets │ │ │ └── vue.svg │ │ ├── entry-server.ts │ │ ├── App.vue │ │ └── components │ │ │ └── HelloWorld.vue │ ├── _gitignore │ ├── vite.config.ts │ ├── index.html │ ├── tsconfig.node.json │ ├── tsconfig.json │ └── package.json └── vite-ssr-vue-streaming │ ├── index.html │ ├── src │ ├── entry-client.js │ ├── assets │ │ └── vue.svg │ ├── main.js │ ├── components │ │ ├── SlowComponent.vue │ │ └── HelloWorld.vue │ └── router.js │ └── vite.config.noexternal.js ├── docs ├── head │ ├── 7.api │ │ └── .navigation.yml │ └── 1.guides │ │ └── .navigation.yml ├── 0.solid-js │ └── .navigation.yml ├── schema-org │ ├── 5.api │ │ ├── .navigation.yml │ │ └── 9.schema │ │ │ ├── .navigation.yml │ │ │ └── item-list.md │ └── 2.guides │ │ ├── .navigation.yml │ │ └── 4.recipes │ │ └── .navigation.yml ├── 0.nuxt │ ├── head │ │ └── guides │ │ │ ├── 0.get-started │ │ │ ├── 1.installation.md │ │ │ └── 1.migration.md │ │ │ └── 1.core-concepts │ │ │ └── 1.components.md │ └── schema-org │ │ └── guides │ │ └── 0.get-started │ │ └── 0.installation.md ├── 0.angular │ └── head │ │ └── guides │ │ └── 1.core-concepts │ │ └── 1.components.md └── 0.vue │ └── head │ └── guides │ └── 1.core-concepts │ └── 1.components.md ├── packages ├── angular │ ├── client │ │ ├── ng-package.json │ │ └── src │ │ │ ├── public_api.ts │ │ │ └── client.ts │ ├── server │ │ ├── ng-package.json │ │ └── src │ │ │ ├── public_api.ts │ │ │ └── server.ts │ ├── client.d.ts │ ├── server.d.ts │ ├── ng-package.json │ ├── src │ │ ├── context.ts │ │ └── public-api.ts │ ├── tsconfig.lib.prod.json │ ├── tsconfig.spec.json │ ├── tsconfig.lib.json │ ├── vitest.config.ts │ ├── vitest.setup.ts │ ├── angular.json │ └── tsconfig.json ├── react │ ├── types.d.ts │ ├── utils.d.ts │ ├── client.d.ts │ ├── plugins.d.ts │ ├── server.d.ts │ ├── src │ │ ├── utils.ts │ │ ├── plugins.ts │ │ ├── autoImports.ts │ │ ├── context.ts │ │ ├── index.ts │ │ ├── server.ts │ │ └── client.ts │ ├── .attw.json │ ├── tsconfig.json │ ├── vitest.config.ts │ ├── test │ │ ├── fixtures │ │ │ └── ReactiveTitle.tsx │ │ └── ReactiveTitle.test.tsx │ └── build.config.ts ├── vue │ ├── server.d.ts │ ├── types.d.ts │ ├── utils.d.ts │ ├── client.d.ts │ ├── legacy.d.ts │ ├── plugins.d.ts │ ├── scripts.d.ts │ ├── src │ │ ├── plugins.ts │ │ ├── autoImports.ts │ │ ├── resolver.ts │ │ ├── utils.ts │ │ ├── scripts │ │ │ └── index.ts │ │ ├── install.ts │ │ ├── index.ts │ │ ├── VueHeadMixin.ts │ │ ├── client.ts │ │ ├── server.ts │ │ └── types │ │ │ ├── index.ts │ │ │ └── util.ts │ ├── tsconfig.json │ ├── .attw.json │ ├── vitest.config.ts │ ├── test │ │ └── unit │ │ │ ├── ssr │ │ │ ├── innerContent.test.ts │ │ │ ├── asyncSetup.test.ts │ │ │ └── optionsApi.test.ts │ │ │ └── dom │ │ │ └── events.test.ts │ └── build.config.ts ├── addons │ ├── vite.d.ts │ ├── webpack.d.ts │ ├── .attw.json │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ ├── constants.ts │ │ └── unplugin │ │ │ ├── types.ts │ │ │ ├── webpack.ts │ │ │ └── vite.ts │ └── build.config.ts ├── schema-org │ ├── vue.d.ts │ ├── react.d.ts │ ├── svelte.d.ts │ ├── solid-js.d.ts │ ├── .attw.json │ ├── tsconfig.json │ ├── src │ │ ├── vue │ │ │ ├── runtime │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── core │ │ │ ├── define.ts │ │ │ └── index.ts │ │ ├── nodes │ │ │ ├── Question │ │ │ │ └── Answer │ │ │ │ │ └── index.ts │ │ │ ├── HowTo │ │ │ │ └── HowToStepDirection │ │ │ │ │ └── index.ts │ │ │ ├── WebPage │ │ │ │ └── ReadAction │ │ │ │ │ └── index.ts │ │ │ ├── VirtualLocation │ │ │ │ └── index.ts │ │ │ ├── TVSeason │ │ │ │ └── index.test.ts │ │ │ ├── PodcastSeason │ │ │ │ └── index.test.ts │ │ │ ├── Image │ │ │ │ └── index.test.ts │ │ │ ├── TVEpisode │ │ │ │ └── index.test.ts │ │ │ ├── Place │ │ │ │ └── index.ts │ │ │ ├── Service │ │ │ │ └── index.test.ts │ │ │ ├── Video │ │ │ │ └── index.test.ts │ │ │ ├── TVSeries │ │ │ │ └── index.test.ts │ │ │ ├── MusicPlaylist │ │ │ │ └── index.test.ts │ │ │ ├── ShippingDeliveryTime │ │ │ │ └── index.ts │ │ │ ├── DefinedRegion │ │ │ │ └── index.ts │ │ │ ├── MusicAlbum │ │ │ │ └── index.test.ts │ │ │ ├── PostalAddress │ │ │ │ └── index.ts │ │ │ ├── Comment │ │ │ │ └── index.test.ts │ │ │ ├── MusicRecording │ │ │ │ └── index.test.ts │ │ │ ├── PodcastSeries │ │ │ │ └── index.test.ts │ │ │ ├── PodcastEpisode │ │ │ │ └── index.test.ts │ │ │ └── Dataset │ │ │ │ └── index.test.ts │ │ └── imports.ts │ ├── vitest.config.ts │ └── build.config.ts ├── solid-js │ ├── client.d.ts │ ├── server.d.ts │ ├── src │ │ ├── utils.ts │ │ ├── plugins.ts │ │ ├── index.ts │ │ ├── autoImports.ts │ │ ├── context.ts │ │ ├── server.ts │ │ └── client.ts │ ├── types.d.ts │ ├── utils.d.ts │ ├── plugins.d.ts │ ├── tsconfig.json │ ├── .attw.json │ ├── vitest.config.ts │ └── build.config.ts ├── svelte │ ├── src │ │ ├── utils.ts │ │ ├── plugins.ts │ │ ├── context.ts │ │ ├── autoImports.ts │ │ ├── index.ts │ │ ├── server.ts │ │ └── client.ts │ ├── utils.d.ts │ ├── client.d.ts │ ├── plugins.d.ts │ ├── .attw.json │ ├── tsconfig.json │ ├── test │ │ └── fixtures │ │ │ ├── ScriptProxy.svelte │ │ │ ├── Counter.svelte │ │ │ ├── Safe.svelte │ │ │ ├── SeoTest.svelte │ │ │ └── ScriptLoading.svelte │ ├── vitest.config.ts │ └── build.config.ts └── unhead │ ├── types.d.ts │ ├── utils.d.ts │ ├── client.d.ts │ ├── legacy.d.ts │ ├── parser.d.ts │ ├── plugins.d.ts │ ├── scripts.d.ts │ ├── server.d.ts │ ├── .attw.json │ ├── tsconfig.json │ ├── src │ ├── scripts │ │ ├── index.ts │ │ └── utils.ts │ ├── types │ │ ├── schema │ │ │ ├── attributes │ │ │ │ └── data.ts │ │ │ ├── shared.ts │ │ │ ├── index.ts │ │ │ ├── noscript.ts │ │ │ ├── base.ts │ │ │ ├── htmlAttributes.ts │ │ │ ├── struct │ │ │ │ └── blocking.ts │ │ │ └── style.ts │ │ ├── index.ts │ │ ├── plugins.ts │ │ └── util.ts │ ├── plugins │ │ ├── defineHeadPlugin.ts │ │ ├── index.ts │ │ ├── flatMeta.ts │ │ ├── deprecations.ts │ │ ├── promises.ts │ │ └── aliasSorting.ts │ ├── index.ts │ ├── client │ │ ├── index.ts │ │ ├── util.ts │ │ └── createHead.ts │ ├── server │ │ ├── util │ │ │ ├── index.ts │ │ │ ├── extractUnheadInputFromHtml.ts │ │ │ ├── propsToString.ts │ │ │ └── ssrRenderTags.ts │ │ ├── index.ts │ │ └── renderSSRHead.ts │ └── utils │ │ ├── index.ts │ │ └── walkResolver.ts │ ├── vitest.config.ts │ ├── test │ ├── unit │ │ ├── client │ │ │ ├── state.test.ts │ │ │ ├── position.test.ts │ │ │ ├── eventHandlers.test.ts │ │ │ ├── delayedDom.test.ts │ │ │ └── order.test.ts │ │ ├── scripts │ │ │ └── use.test.ts │ │ ├── server │ │ │ └── eventHandlers.test.ts │ │ └── fuzz.test.ts │ └── schema-org-utils.ts │ └── build.config.ts ├── .markdownlint.json ├── packages-aliased ├── dom │ ├── src │ │ └── index.ts │ ├── build.config.ts │ └── package.json ├── ssr │ ├── src │ │ └── index.ts │ ├── build.config.ts │ └── package.json ├── shared │ ├── src │ │ └── index.ts │ ├── build.config.ts │ ├── README.md │ └── package.json └── schema │ ├── src │ └── index.ts │ ├── README.md │ ├── build.config.ts │ └── package.json ├── test ├── exports │ └── addons.yaml └── exports.test.ts ├── renovate.json ├── eslint.config.js ├── .editorconfig ├── .gitignore ├── bench ├── bundle │ ├── last.json │ ├── src │ │ ├── client │ │ │ └── minimal.ts │ │ ├── vue-client │ │ │ └── minimal.ts │ │ ├── server │ │ │ └── minimal.ts │ │ ├── vue-server │ │ │ └── minimal.ts │ │ └── schema-org │ │ │ └── minimal.ts │ ├── client-build.config.ts │ └── server-build.config.ts ├── use-seo-meta-perf.bench.ts └── templateParams.bench.ts ├── vitest.config.ts ├── SECURITY.md └── LICENSE /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [harlan-zw] 2 | -------------------------------------------------------------------------------- /examples/angular/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/head/7.api/.navigation.yml: -------------------------------------------------------------------------------- 1 | title: API 2 | -------------------------------------------------------------------------------- /examples/vite-ssr-react-ts/src/SchemaOrg.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/angular/client/ng-package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /packages/angular/server/ng-package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD013": false 3 | } 4 | -------------------------------------------------------------------------------- /docs/0.solid-js/.navigation.yml: -------------------------------------------------------------------------------- 1 | title: Solid.js 2 | -------------------------------------------------------------------------------- /docs/schema-org/5.api/.navigation.yml: -------------------------------------------------------------------------------- 1 | title: API 2 | -------------------------------------------------------------------------------- /docs/head/1.guides/.navigation.yml: -------------------------------------------------------------------------------- 1 | title: User Guides 2 | -------------------------------------------------------------------------------- /examples/angular/src/app/counter/counter.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/react/types.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/types' 2 | -------------------------------------------------------------------------------- /packages/react/utils.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/utils' 2 | -------------------------------------------------------------------------------- /packages/vue/server.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/server' 2 | -------------------------------------------------------------------------------- /packages/vue/types.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/types' 2 | -------------------------------------------------------------------------------- /packages/vue/utils.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/utils' 2 | -------------------------------------------------------------------------------- /docs/schema-org/2.guides/.navigation.yml: -------------------------------------------------------------------------------- 1 | title: User Guides 2 | -------------------------------------------------------------------------------- /docs/schema-org/5.api/9.schema/.navigation.yml: -------------------------------------------------------------------------------- 1 | title: Nodes 2 | -------------------------------------------------------------------------------- /packages/addons/vite.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/client' 2 | -------------------------------------------------------------------------------- /packages/addons/webpack.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/plugins' 2 | -------------------------------------------------------------------------------- /packages/angular/client.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/client' 2 | -------------------------------------------------------------------------------- /packages/angular/server.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/server' 2 | -------------------------------------------------------------------------------- /packages/react/client.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/client' 2 | -------------------------------------------------------------------------------- /packages/react/plugins.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/plugins' 2 | -------------------------------------------------------------------------------- /packages/react/server.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/server' 2 | -------------------------------------------------------------------------------- /packages/react/src/utils.ts: -------------------------------------------------------------------------------- 1 | export * from 'unhead/utils' 2 | -------------------------------------------------------------------------------- /packages/schema-org/vue.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/vue' 2 | -------------------------------------------------------------------------------- /packages/solid-js/client.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/client' 2 | -------------------------------------------------------------------------------- /packages/solid-js/server.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/server' 2 | -------------------------------------------------------------------------------- /packages/solid-js/src/utils.ts: -------------------------------------------------------------------------------- 1 | export * from 'unhead/utils' 2 | -------------------------------------------------------------------------------- /packages/solid-js/types.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/types' 2 | -------------------------------------------------------------------------------- /packages/solid-js/utils.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/utils' 2 | -------------------------------------------------------------------------------- /packages/svelte/src/utils.ts: -------------------------------------------------------------------------------- 1 | export * from 'unhead/utils' 2 | -------------------------------------------------------------------------------- /packages/svelte/utils.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/utils.js' 2 | -------------------------------------------------------------------------------- /packages/unhead/types.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/types' 2 | -------------------------------------------------------------------------------- /packages/unhead/utils.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/utils' 2 | -------------------------------------------------------------------------------- /packages/vue/client.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/client.js' 2 | -------------------------------------------------------------------------------- /packages/vue/legacy.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/legacy.js' 2 | -------------------------------------------------------------------------------- /packages/vue/plugins.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/plugins' 2 | -------------------------------------------------------------------------------- /packages/vue/scripts.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/scripts' 2 | -------------------------------------------------------------------------------- /packages/vue/src/plugins.ts: -------------------------------------------------------------------------------- 1 | export * from 'unhead/plugins' 2 | -------------------------------------------------------------------------------- /packages-aliased/dom/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from 'unhead/client' 2 | -------------------------------------------------------------------------------- /packages-aliased/ssr/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from 'unhead/server' 2 | -------------------------------------------------------------------------------- /packages/react/src/plugins.ts: -------------------------------------------------------------------------------- 1 | export * from 'unhead/plugins' 2 | -------------------------------------------------------------------------------- /packages/schema-org/react.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/svelte' 2 | -------------------------------------------------------------------------------- /packages/schema-org/svelte.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/svelte' 2 | -------------------------------------------------------------------------------- /packages/solid-js/plugins.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/plugins' 2 | -------------------------------------------------------------------------------- /packages/solid-js/src/plugins.ts: -------------------------------------------------------------------------------- 1 | export * from 'unhead/plugins' 2 | -------------------------------------------------------------------------------- /packages/svelte/client.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/client.js' 2 | -------------------------------------------------------------------------------- /packages/svelte/plugins.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/plugins.js' 2 | -------------------------------------------------------------------------------- /packages/svelte/src/plugins.ts: -------------------------------------------------------------------------------- 1 | export * from 'unhead/plugins' 2 | -------------------------------------------------------------------------------- /packages/unhead/client.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/client.mjs' 2 | -------------------------------------------------------------------------------- /packages/unhead/legacy.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/legacy.mjs' 2 | -------------------------------------------------------------------------------- /packages/unhead/parser.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/parser.mjs' 2 | -------------------------------------------------------------------------------- /packages/unhead/plugins.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/plugins.mjs' 2 | -------------------------------------------------------------------------------- /packages/unhead/scripts.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/scripts.mjs' 2 | -------------------------------------------------------------------------------- /packages/unhead/server.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/server.mjs' 2 | -------------------------------------------------------------------------------- /docs/schema-org/2.guides/4.recipes/.navigation.yml: -------------------------------------------------------------------------------- 1 | title: Recipes 2 | -------------------------------------------------------------------------------- /packages-aliased/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from 'unhead/utils' 2 | -------------------------------------------------------------------------------- /packages/schema-org/solid-js.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/solid-js' 2 | -------------------------------------------------------------------------------- /packages-aliased/schema/src/index.ts: -------------------------------------------------------------------------------- 1 | export type * from 'unhead/types' 2 | -------------------------------------------------------------------------------- /packages/vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/vite-ssr-ts/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/addons/.attw.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreRules": ["cjs-resolves-to-esm"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/addons/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/react/.attw.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreRules": ["cjs-resolves-to-esm"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/solid-js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/svelte/.attw.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreRules": ["cjs-resolves-to-esm"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/svelte/src/context.ts: -------------------------------------------------------------------------------- 1 | export const UnheadContextKey = Symbol('unhead') 2 | -------------------------------------------------------------------------------- /packages/svelte/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/unhead/.attw.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreRules": ["cjs-resolves-to-esm"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/unhead/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/vue/.attw.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreRules": ["cjs-resolves-to-esm"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/vite-ssr-react-ts/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/schema-org/.attw.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreRules": ["cjs-resolves-to-esm"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/schema-org/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/solid-js/.attw.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreRules": ["cjs-resolves-to-esm"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/schema-org/src/vue/runtime/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components' 2 | export * from './composables' 3 | -------------------------------------------------------------------------------- /examples/angular/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unjs/unhead/HEAD/examples/angular/public/favicon.ico -------------------------------------------------------------------------------- /examples/angular/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /examples/vite-ssr-svelte/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /packages/addons/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constants' 2 | 3 | export { InferSeoMetaPlugin } from 'unhead/plugins' 4 | -------------------------------------------------------------------------------- /examples/angular/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | export const routes: Routes = []; 4 | -------------------------------------------------------------------------------- /packages/angular/client/src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of unhead 3 | */ 4 | 5 | export * from './client' 6 | -------------------------------------------------------------------------------- /packages/angular/server/src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of unhead 3 | */ 4 | 5 | export * from './server' 6 | -------------------------------------------------------------------------------- /examples/angular/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unjs/unhead/HEAD/examples/vite-ssr-vue-prerender/src/assets/logo.png -------------------------------------------------------------------------------- /packages/solid-js/src/index.ts: -------------------------------------------------------------------------------- 1 | export { hookImports } from './autoImports' 2 | export { useHead, useHeadSafe, useScript, useSeoMeta, useUnhead } from './composables' 3 | -------------------------------------------------------------------------------- /examples/vite-ssr-svelte/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 2 | 3 | export default { 4 | preprocess: vitePreprocess(), 5 | } 6 | -------------------------------------------------------------------------------- /packages/unhead/src/scripts/index.ts: -------------------------------------------------------------------------------- 1 | export type * from './types' 2 | export { resolveScriptKey, useScript } from './useScript' 3 | export { createSpyProxy } from './utils' 4 | -------------------------------------------------------------------------------- /test/exports/addons.yaml: -------------------------------------------------------------------------------- 1 | .: 2 | DefaultCriticalTags: object 3 | InferSeoMetaPlugin: function 4 | ./vite: 5 | default: function 6 | ./webpack: 7 | default: function 8 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/assets/fonts/Inter-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unjs/unhead/HEAD/examples/vite-ssr-vue-prerender/src/assets/fonts/Inter-Italic.woff -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/assets/fonts/Inter-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unjs/unhead/HEAD/examples/vite-ssr-vue-prerender/src/assets/fonts/Inter-Italic.woff2 -------------------------------------------------------------------------------- /packages/unhead/src/types/schema/attributes/data.ts: -------------------------------------------------------------------------------- 1 | import type { Stringable } from '../../util' 2 | 3 | export interface DataKeys { 4 | [key: `data-${string}`]: Stringable 5 | } 6 | -------------------------------------------------------------------------------- /packages/react/src/autoImports.ts: -------------------------------------------------------------------------------- 1 | export const hookImports = { 2 | '@unhead/react': [ 3 | 'useUnhead', 4 | 'useHead', 5 | 'useSeoMeta', 6 | 'useHeadSafe', 7 | ], 8 | } 9 | -------------------------------------------------------------------------------- /packages/react/src/context.ts: -------------------------------------------------------------------------------- 1 | import type { Unhead } from 'unhead/types' 2 | import { createContext } from 'react' 3 | 4 | export const UnheadContext = createContext(null) 5 | -------------------------------------------------------------------------------- /packages/svelte/src/autoImports.ts: -------------------------------------------------------------------------------- 1 | export const autoImports = { 2 | '@unhead/svelte': [ 3 | 'useUnhead', 4 | 'useHead', 5 | 'useSeoMeta', 6 | 'useHeadSafe', 7 | ], 8 | } 9 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:best-practices", 5 | "github>unjs/renovate-config" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/solid-js/src/autoImports.ts: -------------------------------------------------------------------------------- 1 | export const hookImports = { 2 | '@unhead/solid': [ 3 | 'useUnhead', 4 | 'useHead', 5 | 'useSeoMeta', 6 | 'useHeadSafe', 7 | ], 8 | } 9 | -------------------------------------------------------------------------------- /packages/solid-js/src/context.ts: -------------------------------------------------------------------------------- 1 | import type { Unhead } from 'unhead/types' 2 | import { createContext } from 'solid-js' 3 | 4 | export const UnheadContext = createContext(null) 5 | -------------------------------------------------------------------------------- /packages/react/src/index.ts: -------------------------------------------------------------------------------- 1 | export { hookImports } from './autoImports' 2 | export { Head } from './components' 3 | export { useHead, useHeadSafe, useScript, useSeoMeta, useUnhead } from './composables' 4 | -------------------------------------------------------------------------------- /packages/svelte/src/index.ts: -------------------------------------------------------------------------------- 1 | export { autoImports } from './autoImports' 2 | export { useHead, useHeadSafe, useScript, useSeoMeta, useUnhead } from './composables' 3 | export { UnheadContextKey } from './context' 4 | -------------------------------------------------------------------------------- /packages/unhead/src/plugins/defineHeadPlugin.ts: -------------------------------------------------------------------------------- 1 | import type { HeadPluginInput } from '../types/head' 2 | 3 | export function defineHeadPlugin(plugin: HeadPluginInput): HeadPluginInput { 4 | return plugin 5 | } 6 | -------------------------------------------------------------------------------- /packages/schema-org/src/vue/index.ts: -------------------------------------------------------------------------------- 1 | export type { MetaInput, UserConfig } from '../' 2 | export { PluginSchemaOrg, SchemaOrgUnheadPlugin } from '../plugin' 3 | 4 | export * from './meta' 5 | export * from './runtime' 6 | -------------------------------------------------------------------------------- /packages/unhead/src/index.ts: -------------------------------------------------------------------------------- 1 | export { useHead, useHeadSafe, useScript, useSeoMeta, useServerHead, useServerHeadSafe, useServerSeoMeta } from './composables' 2 | export { createHeadCore, createUnhead } from './unhead' 3 | -------------------------------------------------------------------------------- /packages/vue/src/autoImports.ts: -------------------------------------------------------------------------------- 1 | export const unheadVueComposablesImports = { 2 | '@unhead/vue': ['injectHead', 'useHead', 'useSeoMeta', 'useHeadSafe', 'useServerHead', 'useServerSeoMeta', 'useServerHeadSafe'], 3 | } 4 | -------------------------------------------------------------------------------- /examples/angular/src/app/counter/counter.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

Counter Value: {{ counter() }}

5 |
6 | -------------------------------------------------------------------------------- /packages/schema-org/src/core/define.ts: -------------------------------------------------------------------------------- 1 | import type { SchemaOrgNodeDefinition, Thing } from '../types' 2 | 3 | export function defineSchemaOrgResolver(schema: SchemaOrgNodeDefinition) { 4 | return schema 5 | } 6 | -------------------------------------------------------------------------------- /examples/angular/src/app/app.routes.server.ts: -------------------------------------------------------------------------------- 1 | import { RenderMode, ServerRoute } from '@angular/ssr'; 2 | 3 | export const serverRoutes: ServerRoute[] = [ 4 | { 5 | path: '**', 6 | renderMode: RenderMode.Prerender 7 | } 8 | ]; 9 | -------------------------------------------------------------------------------- /examples/vite-ssr-svelte/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { svelte } from '@sveltejs/vite-plugin-svelte' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [svelte()], 7 | }) 8 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/public/myScript.js: -------------------------------------------------------------------------------- 1 | ((w, c) => { 2 | c.log('Script -- Loading') 3 | w.myScript = (arg) => { 4 | c.log(`Script -- Executed -- ${arg}`) 5 | } 6 | c.log('Script -- Loaded') 7 | })(window, console) 8 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, any> 6 | export default component 7 | } 8 | -------------------------------------------------------------------------------- /packages/angular/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "./dist", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | }, 7 | "allowedNonPeerDependencies": ["unhead"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/angular/src/context.ts: -------------------------------------------------------------------------------- 1 | import type { Unhead } from 'unhead/types' 2 | import { InjectionToken } from '@angular/core' 3 | 4 | export const headSymbol = 'usehead' 5 | 6 | export const UnheadInjectionToken = new InjectionToken(headSymbol) 7 | -------------------------------------------------------------------------------- /examples/vite-ssr-svelte/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-streaming/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages-aliased/dom/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | declaration: true, 6 | entries: [ 7 | { input: 'src/index', name: 'index' }, 8 | ], 9 | }) 10 | -------------------------------------------------------------------------------- /packages-aliased/ssr/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | declaration: true, 6 | entries: [ 7 | { input: 'src/index', name: 'index' }, 8 | ], 9 | }) 10 | -------------------------------------------------------------------------------- /packages-aliased/shared/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | declaration: true, 6 | entries: [ 7 | { input: 'src/index', name: 'index' }, 8 | ], 9 | }) 10 | -------------------------------------------------------------------------------- /packages/addons/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const DefaultCriticalTags = { 2 | htmlAttrs: { 3 | lang: 'en', 4 | }, 5 | meta: [ 6 | { charset: 'utf-8' }, 7 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 8 | ], 9 | } 10 | -------------------------------------------------------------------------------- /packages/react/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import { defineProject } from 'vitest/config' 4 | 5 | export default defineProject({ 6 | test: { 7 | globals: true, 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /packages/unhead/src/client/index.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | CreateClientHeadOptions, 3 | Unhead, 4 | } from '../types' 5 | export { createHead } from './createHead' 6 | export { renderDOMHead } from './renderDOMHead' 7 | export { createDebouncedFn } from './util' 8 | -------------------------------------------------------------------------------- /packages/vue/src/resolver.ts: -------------------------------------------------------------------------------- 1 | import type { PropResolver } from 'unhead/types' 2 | import { isRef, toValue } from 'vue' 3 | 4 | export const VueResolver: PropResolver = /* @__PURE__ */ (_, value: any) => { 5 | return isRef(value) ? toValue(value) : value 6 | } 7 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | 3 | export default antfu({ 4 | rules: { 5 | 'no-use-before-define': 'off', 6 | 'ts/ban-ts-comment': 'off', 7 | }, 8 | }, { 9 | ignores: [ 10 | 'examples/*', 11 | ], 12 | }) 13 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue/src/entry-client.ts: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | import { createApp } from './main' 3 | import { createHead } from '@unhead/vue/client' 4 | 5 | const { app } = createApp() 6 | const head = createHead() 7 | app.use(head) 8 | 9 | app.mount('#app') 10 | -------------------------------------------------------------------------------- /packages/unhead/src/server/util/index.ts: -------------------------------------------------------------------------------- 1 | export { extractUnheadInputFromHtml } from './extractUnheadInputFromHtml' 2 | export { propsToString } from './propsToString' 3 | export { ssrRenderTags } from './ssrRenderTags' 4 | export { escapeHtml, tagToString } from './tagToString' 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | *.log* 4 | dist 5 | coverage 6 | .profile 7 | .idea 8 | /integrations/nuxt/playground/.nuxt/ 9 | samples 10 | .vercel 11 | .env 12 | docs/.nuxt 13 | **/auto-imports.d.ts 14 | 15 | .data 16 | 17 | .angular 18 | 19 | .vite-inspect 20 | -------------------------------------------------------------------------------- /examples/vite-ssr-react-ts/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import Unhead from "@unhead/addons/vite"; 4 | 5 | // https://vite.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react(), Unhead()], 8 | }) 9 | -------------------------------------------------------------------------------- /packages/angular/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of unhead 3 | */ 4 | 5 | export { useHead, useHeadSafe, useScript, useSeoMeta, useUnhead } from './composables' 6 | export { headSymbol, UnheadInjectionToken } from './context' 7 | export { Head } from './head.component' 8 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/pages/transform.vue: -------------------------------------------------------------------------------- 1 | 7 | 10 | -------------------------------------------------------------------------------- /examples/angular/src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { config } from './app/app.config.client'; 3 | import { AppComponent } from './app/app.component'; 4 | 5 | bootstrapApplication(AppComponent, config) 6 | .catch((err) => console.error(err)); 7 | -------------------------------------------------------------------------------- /packages/unhead/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type * from '../scripts' 2 | export type * from './head' 3 | export type * from './hooks' 4 | export type * from './plugins' 5 | export type * from './safeSchema' 6 | export type * from './schema' 7 | export type * from './tags' 8 | export type * from './util' 9 | -------------------------------------------------------------------------------- /packages/unhead/src/types/schema/shared.ts: -------------------------------------------------------------------------------- 1 | export type ReferrerPolicy = '' 2 | | 'no-referrer' 3 | | 'no-referrer-when-downgrade' 4 | | 'origin' 5 | | 'origin-when-cross-origin' 6 | | 'same-origin' 7 | | 'strict-origin' 8 | | 'strict-origin-when-cross-origin' 9 | | 'unsafe-url' 10 | -------------------------------------------------------------------------------- /packages-aliased/shared/README.md: -------------------------------------------------------------------------------- 1 | # Deprecated: `@unhead/shared` 2 | 3 | Shared utils needed for Unhead. This is deprecated and only acts as alias of `unhead/utils`. 4 | 5 | ## Migration 6 | 7 | ```diff 8 | -import { Head } from '@unhead/shared' 9 | +import { Head } from 'unhead/utils' 10 | ``` 11 | -------------------------------------------------------------------------------- /packages/schema-org/src/core/index.ts: -------------------------------------------------------------------------------- 1 | export { defineSchemaOrgResolver } from './define' 2 | export type { SchemaOrgGraph } from './graph' 3 | export { createSchemaOrgGraph } from './graph' 4 | export { resolveMeta, resolveNode, resolveNodeId, resolveRelation } from './resolve' 5 | export { merge } from './util' 6 | -------------------------------------------------------------------------------- /bench/bundle/last.json: -------------------------------------------------------------------------------- 1 | { 2 | "client": { 3 | "size": 253, 4 | "gz": 198 5 | }, 6 | "server": { 7 | "size": 313, 8 | "gz": 234 9 | }, 10 | "vueClient": { 11 | "size": 270, 12 | "gz": 208 13 | }, 14 | "vueServer": { 15 | "size": 330, 16 | "gz": 244 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/solid-js/src/server.ts: -------------------------------------------------------------------------------- 1 | export { UnheadContext } from './context' 2 | 3 | export { createHead, extractUnheadInputFromHtml, renderSSRHead, transformHtmlTemplate } from 'unhead/server' 4 | 5 | export type { 6 | CreateServerHeadOptions, 7 | SSRHeadPayload, 8 | Unhead, 9 | } from 'unhead/types' 10 | -------------------------------------------------------------------------------- /packages/svelte/src/server.ts: -------------------------------------------------------------------------------- 1 | export { UnheadContextKey } from './context' 2 | 3 | export { createHead, extractUnheadInputFromHtml, renderSSRHead, transformHtmlTemplate } from 'unhead/server' 4 | 5 | export type { 6 | CreateServerHeadOptions, 7 | SSRHeadPayload, 8 | Unhead, 9 | } from 'unhead/types' 10 | -------------------------------------------------------------------------------- /examples/angular/src/main.server.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { AppComponent } from './app/app.component'; 3 | import { config } from './app/app.config.server'; 4 | 5 | const bootstrap = () => bootstrapApplication(AppComponent, config) 6 | 7 | export default bootstrap; 8 | -------------------------------------------------------------------------------- /packages-aliased/schema/README.md: -------------------------------------------------------------------------------- 1 | # Deprecated: `@unhead/schema` 2 | 3 | Typescript definitions for document ``. This is deprecated and only acts as alias of `unhead/types`. 4 | 5 | ## Migration 6 | 7 | ```diff 8 | -import { Head } from '@unhead/schema' 9 | +import { Head } from 'unhead/types' 10 | ``` 11 | -------------------------------------------------------------------------------- /packages-aliased/schema/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | declaration: true, 6 | entries: [ 7 | { input: 'src/index', name: 'index' }, 8 | ], 9 | externals: [ 10 | 'hookable', 11 | 'unhead/types', 12 | ], 13 | }) 14 | -------------------------------------------------------------------------------- /examples/vite-ssr-ts/src/entry-client.ts: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | import './typescript.svg' 3 | import { setupCounter } from './counter' 4 | import { createHead } from 'unhead/client' 5 | 6 | // @ts-expect-error untyped 7 | window.__UNHEAD__ = createHead() 8 | 9 | setupCounter(document.querySelector('#counter') as HTMLButtonElement) 10 | -------------------------------------------------------------------------------- /packages/unhead/src/client/util.ts: -------------------------------------------------------------------------------- 1 | export function createDebouncedFn(callee: () => void, delayer: (fn: () => void) => void) { 2 | let ctxId = 0 3 | 4 | return () => { 5 | const delayFnCtxId = ++ctxId 6 | 7 | delayer(() => { 8 | if (ctxId === delayFnCtxId) { 9 | callee() 10 | } 11 | }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/vite-ssr-svelte/src/lib/SchemaOrg.svelte: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /packages/angular/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "extends": "./tsconfig.lib.json", 5 | "compilerOptions": { 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /packages/unhead/src/types/plugins.ts: -------------------------------------------------------------------------------- 1 | import type { Unhead } from './head' 2 | 3 | export interface RenderDomHeadOptions { 4 | /** 5 | * Document to use for rendering. Allows stubbing for testing. 6 | */ 7 | document?: Document 8 | } 9 | 10 | export interface DomPluginOptions extends RenderDomHeadOptions { 11 | render: ((head: Unhead) => void) 12 | } 13 | -------------------------------------------------------------------------------- /examples/vite-ssr-ts/_gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue/_gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createSSRApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | // SSR requires a fresh app instance per request, therefore we export a function 5 | // that creates a fresh app instance. If using Vuex, we'd also be creating a 6 | // fresh store here. 7 | export function createApp() { 8 | const app = createSSRApp(App) 9 | return { app } 10 | } 11 | -------------------------------------------------------------------------------- /examples/vite-ssr-react-ts/_gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/vite-ssr-svelte/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "skipLibCheck": true, 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "strict": true, 9 | "noUncheckedSideEffectImports": true 10 | }, 11 | "include": ["vite.config.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/solid-js/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import solid from 'vite-plugin-solid' 2 | /// 3 | /// 4 | import { defineProject } from 'vitest/config' 5 | 6 | export default defineProject({ 7 | plugins: [solid()], 8 | resolve: { 9 | conditions: ['development', 'browser'], 10 | }, 11 | test: { 12 | globals: true, 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /packages/unhead/src/server/index.ts: -------------------------------------------------------------------------------- 1 | export type { CreateServerHeadOptions, SSRHeadPayload, Unhead } from '../types' 2 | export { createHead } from './createHead' 3 | export { renderSSRHead } from './renderSSRHead' 4 | export { transformHtmlTemplate, transformHtmlTemplateRaw } from './transformHtmlTemplate' 5 | export { escapeHtml, extractUnheadInputFromHtml, propsToString, ssrRenderTags, tagToString } from './util' 6 | -------------------------------------------------------------------------------- /packages/svelte/test/fixtures/ScriptProxy.svelte: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /examples/vite-ssr-svelte/src/entry-client.ts: -------------------------------------------------------------------------------- 1 | import './app.css' 2 | import { hydrate } from 'svelte' 3 | import App from './App.svelte' 4 | import { createHead, UnheadContextKey } from '@unhead/svelte/client' 5 | 6 | const unhead = createHead() 7 | const context = new Map() 8 | context.set(UnheadContextKey, unhead) 9 | 10 | hydrate(App, { 11 | target: document.getElementById('app')!, 12 | context: context 13 | }) 14 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vitest/config' 3 | 4 | export default defineConfig({ 5 | define: { 6 | __DEV__: true, 7 | __TEST__: true, 8 | __BROWSER__: true, 9 | }, 10 | test: { 11 | pool: 'threads', 12 | projects: ['packages/*', 'test/'], 13 | globals: true, 14 | reporters: 'dot', 15 | isolate: true, 16 | }, 17 | }) 18 | -------------------------------------------------------------------------------- /examples/angular/src/app/app.config.client.ts: -------------------------------------------------------------------------------- 1 | import { mergeApplicationConfig, ApplicationConfig } from '@angular/core'; 2 | import { appConfig } from './app.config'; 3 | import { provideClientHead } from '@unhead/angular/client' 4 | 5 | const clientConfig: ApplicationConfig = { 6 | providers: [ 7 | provideClientHead(), 8 | ] 9 | }; 10 | 11 | export const config = mergeApplicationConfig(appConfig, clientConfig); 12 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/entry-client.js: -------------------------------------------------------------------------------- 1 | import { createApp } from './main' 2 | import { VueHeadMixin, createHead } from "@unhead/vue/client"; 3 | 4 | const { app, router } = createApp() 5 | 6 | const head = createHead() 7 | app.use(head) 8 | app.mixin(VueHeadMixin) 9 | 10 | // wait until router is ready before mounting to ensure hydration match 11 | router.isReady().then(() => { 12 | app.mount('#app') 13 | }) 14 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-streaming/src/entry-client.js: -------------------------------------------------------------------------------- 1 | import { createApp } from './main' 2 | import { VueHeadMixin, createHead } from "@unhead/vue/client"; 3 | 4 | const { app, router } = createApp() 5 | 6 | const head = createHead() 7 | app.use(head) 8 | app.mixin(VueHeadMixin) 9 | 10 | // wait until router is ready before mounting to ensure hydration match 11 | router.isReady().then(() => { 12 | app.mount('#app') 13 | }) 14 | -------------------------------------------------------------------------------- /packages/svelte/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import { svelte } from '@sveltejs/vite-plugin-svelte' 4 | import { svelteTesting } from '@testing-library/svelte/vite' 5 | import { defineProject } from 'vitest/config' 6 | 7 | export default defineProject({ 8 | plugins: [svelte(), svelteTesting()], 9 | test: { 10 | environment: 'jsdom', 11 | }, 12 | }) 13 | -------------------------------------------------------------------------------- /packages/vue/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { walkResolver } from 'unhead/utils' 2 | import { VueResolver } from './resolver' 3 | 4 | export * from 'unhead/utils' 5 | 6 | /** 7 | * @deprecated Use head.resolveTags() instead 8 | */ 9 | /* @__NO_SIDE_EFFECTS__ */ 10 | export function resolveUnrefHeadInput>(input: T): T { 11 | return walkResolver(input, VueResolver) 12 | } 13 | 14 | export { VueResolver } 15 | -------------------------------------------------------------------------------- /packages/react/test/fixtures/ReactiveTitle.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { useHead } from '../../src' 3 | 4 | export function ReactiveTitle({ initialTitle = 'Test' }) { 5 | const [title, setTitle] = useState(initialTitle) 6 | useHead({ title }) 7 | return ( 8 |
9 | {title} 10 | 11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/pages/Red.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 20 | 21 | 26 | -------------------------------------------------------------------------------- /packages/unhead/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './const' 2 | export { dedupeKey, hashTag, isMetaArrayDupeKey } from './dedupe' 3 | export { resolveMetaKeyType, resolveMetaKeyValue, resolvePackedMetaObjectValue, unpackMeta } from './meta' 4 | export { normalizeEntryToTags, normalizeProps } from './normalize' 5 | export { sortTags, tagWeight } from './sort' 6 | export { processTemplateParams } from './templateParams' 7 | export { walkResolver } from './walkResolver' 8 | -------------------------------------------------------------------------------- /packages/unhead/src/server/util/extractUnheadInputFromHtml.ts: -------------------------------------------------------------------------------- 1 | import type { PreparedHtmlTemplate } from '../../parser' 2 | import { parseHtmlForUnheadExtraction } from '../../parser' 3 | 4 | /** 5 | * @deprecated use `parseHtmlForUnheadExtraction` from `unhead/parser` instead 6 | * @param html 7 | */ 8 | /* @__PURE__ */ 9 | export function extractUnheadInputFromHtml(html: string): PreparedHtmlTemplate { 10 | return parseHtmlForUnheadExtraction(html) 11 | } 12 | -------------------------------------------------------------------------------- /packages/react/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | declaration: true, 6 | externals: ['react'], 7 | entries: [ 8 | { input: 'src/index', name: 'index' }, 9 | { input: 'src/server', name: 'server' }, 10 | { input: 'src/client', name: 'client' }, 11 | { input: 'src/utils', name: 'utils' }, 12 | { input: 'src/plugins', name: 'plugins' }, 13 | ], 14 | }) 15 | -------------------------------------------------------------------------------- /packages/svelte/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | declaration: true, 6 | externals: ['svelte'], 7 | entries: [ 8 | { input: 'src/index', name: 'index' }, 9 | { input: 'src/server', name: 'server' }, 10 | { input: 'src/client', name: 'client' }, 11 | { input: 'src/utils', name: 'utils' }, 12 | { input: 'src/plugins', name: 'plugins' }, 13 | ], 14 | }) 15 | -------------------------------------------------------------------------------- /packages/unhead/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path' 2 | /// 3 | /// 4 | import { defineProject } from 'vitest/config' 5 | 6 | export default defineProject({ 7 | resolve: { 8 | alias: { 9 | '@unhead/ssr': resolve(__dirname, 'src/server'), 10 | '@unhead/dom': resolve(__dirname, 'src/client'), 11 | }, 12 | }, 13 | test: { 14 | globals: true, 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Vue + TS 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/solid-js/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | declaration: true, 6 | externals: ['solid-js'], 7 | entries: [ 8 | { input: 'src/index', name: 'index' }, 9 | { input: 'src/server', name: 'server' }, 10 | { input: 'src/client', name: 'client' }, 11 | { input: 'src/utils', name: 'utils' }, 12 | { input: 'src/plugins', name: 'plugins' }, 13 | ], 14 | }) 15 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import { unheadVueComposablesImports } from '@unhead/vue' 4 | import AutoImport from 'unplugin-auto-import/vite' 5 | 6 | // https://vite.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | AutoImport({ 10 | imports: [ 11 | unheadVueComposablesImports, 12 | 'vue', 13 | ], 14 | }), 15 | vue() 16 | ], 17 | }) 18 | -------------------------------------------------------------------------------- /examples/vite-ssr-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + TS 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/unhead/src/types/schema/index.ts: -------------------------------------------------------------------------------- 1 | import type { RawInput } from './head' 2 | 3 | export type * from './head' 4 | 5 | export type Base = RawInput<'base'> 6 | export type HtmlAttributes = RawInput<'htmlAttrs'> 7 | export type Noscript = RawInput<'noscript'> 8 | export type Style = RawInput<'style'> 9 | export type Meta = RawInput<'meta'> 10 | export type Script = RawInput<'script'> 11 | export type Link = RawInput<'link'> 12 | export type BodyAttributes = RawInput<'bodyAttrs'> 13 | -------------------------------------------------------------------------------- /docs/0.nuxt/head/guides/0.get-started/1.installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installing Unhead with Nuxt 3 | description: Learn how to start using Unhead with Nuxt. 4 | navigation: 5 | title: 'Installation' 6 | --- 7 | 8 | ## Introduction 9 | 10 | Unhead has first-class support for Nuxt. In fact much of the work gone into Unhead is a direct result of feedback from the Nuxt community. 11 | 12 | ::tip 13 | Nuxt is integrated out-of-the-box with Unhead so no installation is required. 14 | :: 15 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Vue + TS 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /bench/bundle/src/client/minimal.ts: -------------------------------------------------------------------------------- 1 | import { useHead } from 'unhead' 2 | import { createHead } from 'unhead/client' 3 | 4 | // Full usage with all core features 5 | const head = createHead() 6 | 7 | useHead(head, { 8 | title: 'Test', 9 | titleTemplate: '%s | Site', 10 | meta: [ 11 | { name: 'description', content: 'Test' }, 12 | ], 13 | link: [ 14 | { rel: 'icon', href: '/favicon.ico' }, 15 | ], 16 | script: [ 17 | { src: '/test.js', defer: true }, 18 | ], 19 | }) 20 | -------------------------------------------------------------------------------- /packages/schema-org/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path' 2 | /// 3 | /// 4 | import { defineProject } from 'vitest/config' 5 | 6 | export default defineProject({ 7 | resolve: { 8 | alias: { 9 | '@unhead/ssr': resolve(__dirname, '../unhead/src/server'), 10 | '@unhead/dom': resolve(__dirname, '../unhead/src/client'), 11 | }, 12 | }, 13 | test: { 14 | globals: true, 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /examples/vite-ssr-react-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/vue/src/scripts/index.ts: -------------------------------------------------------------------------------- 1 | import { createSpyProxy, resolveScriptKey } from 'unhead/scripts' 2 | import { useScript } from './useScript' 3 | 4 | export { 5 | createSpyProxy, 6 | resolveScriptKey, 7 | useScript, 8 | } 9 | 10 | export type { AsVoidFunctions, EventHandlerOptions, RecordingEntry, ScriptInstance, UseFunctionType, UseScriptContext, UseScriptInput, UseScriptOptions, UseScriptResolvedInput, UseScriptReturn, UseScriptStatus, VueScriptInstance, WarmupStrategy } from './useScript' 11 | -------------------------------------------------------------------------------- /examples/vite-ssr-react-ts/src/entry-client.tsx: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | import { StrictMode } from 'react' 3 | import { hydrateRoot } from 'react-dom/client' 4 | import App from './App' 5 | import { UnheadProvider, createHead } from '@unhead/react/client' 6 | 7 | const head = createHead({ /* config */ }) 8 | 9 | hydrateRoot( 10 | document.getElementById('root') as HTMLElement, 11 | 12 | 13 | 14 | 15 | , 16 | ) 17 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/angular/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "extends": "./tsconfig.json", 5 | "compilerOptions": { 6 | "types": [ 7 | "jasmine" 8 | ], 9 | "outDir": "./out-tsc/spec" 10 | }, 11 | "include": [ 12 | "**/*.spec.ts", 13 | "**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /bench/bundle/src/vue-client/minimal.ts: -------------------------------------------------------------------------------- 1 | import { useHead } from '@unhead/vue' 2 | import { createHead } from '@unhead/vue/client' 3 | 4 | // Full usage with all core features 5 | const head = createHead() 6 | 7 | useHead({ 8 | title: 'Test', 9 | titleTemplate: '%s | Site', 10 | meta: [ 11 | { name: 'description', content: 'Test' }, 12 | ], 13 | link: [ 14 | { rel: 'icon', href: '/favicon.ico' }, 15 | ], 16 | script: [ 17 | { src: '/test.js', defer: true }, 18 | ], 19 | }, { 20 | head, 21 | }) 22 | -------------------------------------------------------------------------------- /examples/vite-ssr-react-ts/src/entry-server.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { renderToString } from 'react-dom/server' 3 | import App from './App' 4 | import { createHead, UnheadProvider } from '@unhead/react/server' 5 | 6 | export function render(_url: string) { 7 | const head = createHead() 8 | const html = renderToString( 9 | 10 | 11 | 12 | 13 | , 14 | ) 15 | return { html, head } 16 | } 17 | -------------------------------------------------------------------------------- /examples/vite-ssr-svelte/src/entry-server.ts: -------------------------------------------------------------------------------- 1 | import { render as _render } from 'svelte/server' 2 | import App from './App.svelte' 3 | import { createHead, UnheadContextKey } from '@unhead/svelte/server' 4 | 5 | export function render(_url: string): { render: ReturnType, unhead: any } { 6 | const unhead = createHead() 7 | const context = new Map() 8 | context.set(UnheadContextKey, unhead) 9 | return { 10 | render: _render(App, { 11 | context, 12 | }), 13 | unhead, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/angular/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "extends": "./tsconfig.json", 5 | "compilerOptions": { 6 | "outDir": "./out-tsc/spec", 7 | "types": [ 8 | "jasmine" 9 | ] 10 | }, 11 | "include": [ 12 | "src/**/*.spec.ts", 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /examples/vite-ssr-ts/src/counter.ts: -------------------------------------------------------------------------------- 1 | import { useHead } from 'unhead' 2 | 3 | export function setupCounter(element: HTMLButtonElement) { 4 | let counter = 0 5 | const setCounter = (count: number) => { 6 | counter = count 7 | element.innerHTML = `count is ${counter}` 8 | // @ts-expect-error untyped 9 | useHead(window.__UNHEAD__, { 10 | title: () => counter ? `count is ${counter}` : null, 11 | }) 12 | } 13 | element.addEventListener('click', () => setCounter(counter + 1)) 14 | setCounter(0) 15 | } 16 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-streaming/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/addons/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | declaration: true, 6 | entries: [ 7 | { input: 'src/index', name: 'index' }, 8 | { input: 'src/unplugin/vite', name: 'vite' }, 9 | { input: 'src/unplugin/webpack', name: 'webpack' }, 10 | ], 11 | externals: [ 12 | 'vite', 13 | 'webpack', 14 | 'rollup', 15 | 'unhead', 16 | 'unplugin', 17 | 'unhead/plugins', 18 | 'unhead/utils', 19 | ], 20 | }) 21 | -------------------------------------------------------------------------------- /examples/angular/src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; 2 | import { provideRouter } from '@angular/router'; 3 | import { routes } from './app.routes'; 4 | import { provideClientHydration, withEventReplay, } from '@angular/platform-browser'; 5 | 6 | export const appConfig: ApplicationConfig = { 7 | providers: [ 8 | provideZoneChangeDetection({ eventCoalescing: true }), 9 | provideRouter(routes), 10 | provideClientHydration(withEventReplay()), 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /packages/addons/src/unplugin/types.ts: -------------------------------------------------------------------------------- 1 | import type { TreeshakeServerComposablesOptions } from './TreeshakeServerComposables' 2 | import type { UseSeoMetaTransformOptions } from './UseSeoMetaTransform' 3 | 4 | export interface BaseTransformerTypes { 5 | sourcemap?: boolean 6 | filter?: { 7 | exclude?: RegExp[] 8 | include?: RegExp[] 9 | } 10 | } 11 | 12 | export interface UnpluginOptions extends BaseTransformerTypes { 13 | treeshake?: TreeshakeServerComposablesOptions 14 | transformSeoMeta?: UseSeoMetaTransformOptions 15 | } 16 | -------------------------------------------------------------------------------- /packages/vue/src/install.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vue' 2 | import type { VueHeadClient } from './types' 3 | 4 | export const headSymbol = 'usehead' 5 | 6 | /* @__NO_SIDE_EFFECTS__ */ 7 | export function vueInstall(head: VueHeadClient) { 8 | const plugin = { 9 | install(app) { 10 | app.config.globalProperties.$unhead = head 11 | // for @vueuse/head polyfill 12 | app.config.globalProperties.$head = head 13 | app.provide(headSymbol, head) 14 | }, 15 | } 16 | return plugin.install 17 | } 18 | -------------------------------------------------------------------------------- /packages/angular/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "extends": "./tsconfig.json", 5 | "compilerOptions": { 6 | "types": [], 7 | "declaration": true, 8 | "declarationMap": true, 9 | "inlineSources": true, 10 | "outDir": "./out-tsc/lib" 11 | }, 12 | "exclude": [ 13 | "**/*.spec.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/angular/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineProject } from 'vitest/config' 2 | 3 | export default defineProject({ 4 | test: { 5 | globals: true, 6 | environment: 'jsdom', 7 | setupFiles: ['./vitest.setup.ts'], 8 | include: ['**/*.spec.ts'], 9 | exclude: ['**/head.component.spec.ts'], // Temporarily exclude due to Angular DI circular dependency issue 10 | server: { 11 | deps: { 12 | inline: ['@angular/core', '@angular/common', '@angular/platform-browser', 'zone.js'], 13 | }, 14 | }, 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /packages/unhead/src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export { AliasSortingPlugin } from './aliasSorting' 2 | export { CanonicalPlugin } from './canonical' 3 | export { defineHeadPlugin } from './defineHeadPlugin' 4 | export { DeprecationsPlugin } from './deprecations' // optional 5 | export { FlatMetaPlugin } from './flatMeta' // optional 6 | export { InferSeoMetaPlugin } from './inferSeoMetaPlugin' // optional 7 | export { PromisesPlugin } from './promises' // optional 8 | export { SafeInputPlugin } from './safe' // optional 9 | export { TemplateParamsPlugin } from './templateParams' // optional 10 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/pages/Home.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /examples/angular/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "extends": "./tsconfig.json", 5 | "compilerOptions": { 6 | "outDir": "./out-tsc/app", 7 | "types": [ 8 | "node" 9 | ] 10 | }, 11 | "files": [ 12 | "src/main.ts", 13 | "src/main.server.ts", 14 | "src/server.ts" 15 | ], 16 | "include": [ 17 | "src/**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/pages/schema-org.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 24 | -------------------------------------------------------------------------------- /packages/vue/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path' 2 | /// 3 | /// 4 | import { defineProject } from 'vitest/config' 5 | 6 | export default defineProject({ 7 | resolve: { 8 | alias: { 9 | '@unhead/dom': resolve(__dirname, 'src/client'), 10 | '@unhead/ssr': resolve(__dirname, 'src/server'), 11 | '@unhead/addons': resolve(__dirname, '../addons/src'), 12 | '@unhead/schema-org/vue': resolve(__dirname, '../schema-org/src/vue'), 13 | }, 14 | }, 15 | test: { 16 | globals: true, 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/main.js: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia' 2 | import { createSSRApp } from 'vue' 3 | import App from './App.vue' 4 | import { createRouter } from './router' 5 | 6 | // SSR requires a fresh app instance per request, therefore we export a function 7 | // that creates a fresh app instance. If using Vuex, we'd also be creating a 8 | // fresh store here. 9 | export function createApp() { 10 | const app = createSSRApp(App) 11 | const pinia = createPinia() 12 | app.use(pinia) 13 | const router = createRouter() 14 | app.use(router) 15 | 16 | return { app, router } 17 | } 18 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-streaming/src/main.js: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia' 2 | import { createSSRApp } from 'vue' 3 | import App from './App.vue' 4 | import { createRouter } from './router' 5 | 6 | // SSR requires a fresh app instance per request, therefore we export a function 7 | // that creates a fresh app instance. If using Vuex, we'd also be creating a 8 | // fresh store here. 9 | export function createApp() { 10 | const app = createSSRApp(App) 11 | const pinia = createPinia() 12 | app.use(pinia) 13 | const router = createRouter() 14 | app.use(router) 15 | 16 | return { app, router } 17 | } 18 | -------------------------------------------------------------------------------- /packages/addons/src/unplugin/webpack.ts: -------------------------------------------------------------------------------- 1 | import type { UnpluginOptions } from './types' 2 | import { TreeshakeServerComposables } from './TreeshakeServerComposables' 3 | import { UseSeoMetaTransform } from './UseSeoMetaTransform' 4 | 5 | export type { UnpluginOptions } 6 | 7 | export default (options: UnpluginOptions = {}) => { 8 | return [ 9 | TreeshakeServerComposables.webpack({ filter: options.filter, sourcemap: options.sourcemap, ...options.treeshake || {} }), 10 | UseSeoMetaTransform.webpack({ filter: options.filter, sourcemap: options.sourcemap, ...options.transformSeoMeta || {} }), 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/react/src/server.ts: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react' 2 | import type { Unhead } from 'unhead/types' 3 | import { createElement } from 'react' 4 | import { UnheadContext } from './context' 5 | 6 | export { createHead, extractUnheadInputFromHtml, renderSSRHead, transformHtmlTemplate } from 'unhead/server' 7 | 8 | export function UnheadProvider({ children, value }: { children: ReactNode, value: Unhead }) { 9 | return createElement(UnheadContext.Provider, { value }, children) 10 | } 11 | 12 | export type { 13 | CreateServerHeadOptions, 14 | SSRHeadPayload, 15 | Unhead, 16 | } from 'unhead/types' 17 | -------------------------------------------------------------------------------- /packages/svelte/test/fixtures/Counter.svelte: -------------------------------------------------------------------------------- 1 | 2 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/pages/custom-script.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 27 | -------------------------------------------------------------------------------- /bench/bundle/src/server/minimal.ts: -------------------------------------------------------------------------------- 1 | import { useHead } from 'unhead' 2 | import { createHead, renderSSRHead } from 'unhead/server' 3 | 4 | async function doHead() { 5 | // Full usage with all core features 6 | const head = createHead() 7 | 8 | useHead(head, { 9 | title: 'Test', 10 | titleTemplate: '%s | Site', 11 | meta: [ 12 | { name: 'description', content: 'Test' }, 13 | ], 14 | link: [ 15 | { rel: 'icon', href: '/favicon.ico' }, 16 | ], 17 | script: [ 18 | { src: '/test.js', defer: true }, 19 | ], 20 | }) 21 | 22 | return await renderSSRHead(head) 23 | } 24 | 25 | doHead() 26 | -------------------------------------------------------------------------------- /packages/vue/test/unit/ssr/innerContent.test.ts: -------------------------------------------------------------------------------- 1 | import { useHead } from '@unhead/vue' 2 | import { describe } from 'vitest' 3 | import { ssrRenderHeadToString } from '../../util' 4 | 5 | describe('vue ssr innerHTML', () => { 6 | it('innerHTML', async () => { 7 | const headResult = await ssrRenderHeadToString(() => { 8 | useHead({ 9 | script: [ 10 | { 11 | innerHTML: 'console.log(\'hi\')', 12 | }, 13 | ], 14 | }) 15 | }) 16 | 17 | expect(headResult.headTags).toMatchInlineSnapshot( 18 | `""`, 19 | ) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /packages/unhead/src/types/schema/noscript.ts: -------------------------------------------------------------------------------- 1 | export interface Noscript { 2 | /** 3 | * This attribute defines the unique ID. 4 | */ 5 | id?: string 6 | /** 7 | * The class global attribute is a space-separated list of the case-sensitive classes of the element. 8 | * 9 | * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class 10 | */ 11 | class?: string 12 | /** 13 | * The style global attribute contains CSS styling declarations to be applied to the element. 14 | * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/style 15 | */ 16 | style?: string 17 | } 18 | -------------------------------------------------------------------------------- /packages/addons/src/unplugin/vite.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vite' 2 | import type { UnpluginOptions } from './types' 3 | import { TreeshakeServerComposables } from './TreeshakeServerComposables' 4 | import { UseSeoMetaTransform } from './UseSeoMetaTransform' 5 | 6 | export type { UnpluginOptions } 7 | 8 | export default (options: UnpluginOptions = {}): Plugin[] => { 9 | return [ 10 | TreeshakeServerComposables.vite({ filter: options.filter, sourcemap: options.sourcemap, ...options.treeshake }), 11 | UseSeoMetaTransform.vite({ filter: options.filter, sourcemap: options.sourcemap, ...options.transformSeoMeta }), 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/vue/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createUnhead } from 'unhead' 2 | 3 | export { unheadVueComposablesImports } from './autoImports' 4 | export { injectHead, useHead, useHeadSafe, useScript, useSeoMeta, useServerHead, useServerHeadSafe, useServerSeoMeta } from './composables' 5 | export { headSymbol } from './install' 6 | export type * from './scripts/index' 7 | export type * from './types' 8 | export { resolveUnrefHeadInput } from './utils' 9 | export { VueHeadMixin } from './VueHeadMixin' 10 | /** 11 | * @deprecated Use createUnhead 12 | */ 13 | const createHeadCore = /* @__PURE__ */ createUnhead 14 | export { createHeadCore, createUnhead } 15 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/pages/xss.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue/src/entry-server.ts: -------------------------------------------------------------------------------- 1 | import { renderToString } from 'vue/server-renderer' 2 | import { createApp } from './main' 3 | import { createHead } from '@unhead/vue/server' 4 | import { useSeoMeta } from '@unhead/vue' 5 | 6 | export async function render(_url: string) { 7 | const { app } = createApp() 8 | const head = createHead() 9 | app.use(head) 10 | // no client-side hydration needed 11 | useSeoMeta({ 12 | title: 'My Awesome Site', 13 | description: 'My awesome site description', 14 | }, { head }) 15 | const ctx = {} 16 | const html = await renderToString(app, ctx) 17 | 18 | return { html, head } 19 | } 20 | -------------------------------------------------------------------------------- /packages/angular/vitest.setup.ts: -------------------------------------------------------------------------------- 1 | import { getTestBed } from '@angular/core/testing' 2 | import { 3 | BrowserDynamicTestingModule, 4 | platformBrowserDynamicTesting, 5 | } from '@angular/platform-browser-dynamic/testing' 6 | import 'zone.js' 7 | import 'zone.js/testing' 8 | 9 | // Reset TestBed if it's already been initialized 10 | if ((globalThis as any).__karma__) { 11 | getTestBed().resetTestingModule() 12 | } 13 | 14 | // First, initialize the Angular testing environment 15 | getTestBed().initTestEnvironment( 16 | BrowserDynamicTestingModule, 17 | platformBrowserDynamicTesting(), 18 | { teardown: { destroyAfterEach: true } }, 19 | ) 20 | -------------------------------------------------------------------------------- /packages/unhead/test/unit/client/state.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | import { useHead } from '../../../src' 3 | import { createClientHeadWithContext } from '../../util' 4 | 5 | describe('state', () => { 6 | it('exists', async () => { 7 | const head = createClientHeadWithContext() 8 | useHead(head, { 9 | title: 'hello', 10 | }) 11 | 12 | expect(head.entries).toMatchInlineSnapshot(` 13 | Map { 14 | 1 => { 15 | "_i": 1, 16 | "input": { 17 | "title": "hello", 18 | }, 19 | "options": {}, 20 | }, 21 | } 22 | `) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /bench/bundle/src/vue-server/minimal.ts: -------------------------------------------------------------------------------- 1 | import { useHead } from '@unhead/vue' 2 | import { createHead, renderSSRHead } from '@unhead/vue/server' 3 | 4 | async function doHead() { 5 | // Full usage with all core features 6 | const head = createHead() 7 | 8 | useHead({ 9 | title: 'Test', 10 | titleTemplate: '%s | Site', 11 | meta: [ 12 | { name: 'description', content: 'Test' }, 13 | ], 14 | link: [ 15 | { rel: 'icon', href: '/favicon.ico' }, 16 | ], 17 | script: [ 18 | { src: '/test.js', defer: true }, 19 | ], 20 | }, { head }) 21 | 22 | return await renderSSRHead(head) 23 | } 24 | 25 | doHead() 26 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/vite.config.noexternal.js: -------------------------------------------------------------------------------- 1 | import config from './vite.config.js' 2 | 3 | /** 4 | * @type {import('vite').UserConfig} 5 | */ 6 | export default Object.assign(config, { 7 | ssr: { 8 | noExternal: /./, 9 | }, 10 | resolve: { 11 | // necessary because vue.ssrUtils is only exported on cjs modules 12 | alias: [ 13 | { 14 | find: '@vue/runtime-dom', 15 | replacement: '@vue/runtime-dom/dist/runtime-dom.cjs.js', 16 | }, 17 | { 18 | find: '@vue/runtime-core', 19 | replacement: '@vue/runtime-core/dist/runtime-core.cjs.js', 20 | }, 21 | ], 22 | }, 23 | }) 24 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-streaming/vite.config.noexternal.js: -------------------------------------------------------------------------------- 1 | import config from './vite.config.js' 2 | 3 | /** 4 | * @type {import('vite').UserConfig} 5 | */ 6 | export default Object.assign(config, { 7 | ssr: { 8 | noExternal: /./, 9 | }, 10 | resolve: { 11 | // necessary because vue.ssrUtils is only exported on cjs modules 12 | alias: [ 13 | { 14 | find: '@vue/runtime-dom', 15 | replacement: '@vue/runtime-dom/dist/runtime-dom.cjs.js', 16 | }, 17 | { 18 | find: '@vue/runtime-core', 19 | replacement: '@vue/runtime-core/dist/runtime-core.cjs.js', 20 | }, 21 | ], 22 | }, 23 | }) 24 | -------------------------------------------------------------------------------- /packages/unhead/src/types/schema/base.ts: -------------------------------------------------------------------------------- 1 | export interface Base { 2 | /** 3 | * The base URL to be used throughout the document for relative URLs. Absolute and relative URLs are allowed. 4 | * 5 | * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base#attr-href 6 | */ 7 | href?: string 8 | /** 9 | * A keyword or author-defined name of the default browsing context to show the results of navigation from ``, 10 | * ``, or `
` elements without explicit target attributes. 11 | * 12 | * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base#attr-target 13 | */ 14 | target?: string 15 | } 16 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/pages/timer-script.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 24 | 25 | 31 | -------------------------------------------------------------------------------- /packages/svelte/test/fixtures/Safe.svelte: -------------------------------------------------------------------------------- 1 | 2 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /bench/bundle/src/schema-org/minimal.ts: -------------------------------------------------------------------------------- 1 | import { defineWebPage, defineWebSite, useSchemaOrg } from '@unhead/schema-org' 2 | import { useHead } from 'unhead' 3 | import { createHead, renderSSRHead } from 'unhead/server' 4 | 5 | async function doHead() { 6 | const head = createHead() 7 | 8 | useHead(head, { 9 | title: 'Test', 10 | }) 11 | 12 | // Only use WebPage and WebSite - other resolvers should be tree-shaken 13 | useSchemaOrg(head, [ 14 | defineWebSite({ 15 | name: 'Test Site', 16 | }), 17 | defineWebPage({ 18 | name: 'Test Page', 19 | }), 20 | ]) 21 | 22 | return await renderSSRHead(head) 23 | } 24 | 25 | doHead() 26 | -------------------------------------------------------------------------------- /packages/vue/src/VueHeadMixin.ts: -------------------------------------------------------------------------------- 1 | import type { UseHeadInput } from './types' 2 | import { getCurrentInstance } from 'vue' 3 | import { useHead } from './composables' 4 | 5 | export const VueHeadMixin = { 6 | created() { 7 | let source: UseHeadInput | false = false 8 | const instance = getCurrentInstance() 9 | if (!instance) 10 | return 11 | const options = instance.type 12 | if (!options || !('head' in options)) 13 | return 14 | 15 | source = (typeof options.head === 'function' 16 | ? () => options.head.call(instance.proxy) 17 | : options.head) as UseHeadInput | false 18 | source && useHead(source) 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /packages/solid-js/src/client.ts: -------------------------------------------------------------------------------- 1 | import type { CreateClientHeadOptions, Unhead } from 'unhead/types' 2 | import { createHead as _createHead, createDebouncedFn, renderDOMHead } from 'unhead/client' 3 | 4 | export { UnheadContext } from './context' 5 | 6 | export { renderDOMHead } from 'unhead/client' 7 | 8 | export function createHead(options: CreateClientHeadOptions = {}): Unhead { 9 | const head = _createHead({ 10 | domOptions: { 11 | render: createDebouncedFn(() => renderDOMHead(head), fn => setTimeout(fn, 0)), 12 | }, 13 | ...options, 14 | }) 15 | return head 16 | } 17 | 18 | export type { 19 | CreateClientHeadOptions, 20 | Unhead, 21 | } 22 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/pages/timer-script2.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 24 | 25 | 31 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-streaming/src/components/SlowComponent.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 | 27 | -------------------------------------------------------------------------------- /packages/schema-org/src/nodes/Question/Answer/index.ts: -------------------------------------------------------------------------------- 1 | import type { Thing } from '../../../types' 2 | import { defineSchemaOrgResolver } from '../../../core' 3 | 4 | /** 5 | * An answer offered to a question; perhaps correct, perhaps opinionated or wrong. 6 | */ 7 | export interface AnswerSimple extends Thing { 8 | text: string 9 | } 10 | 11 | export interface Answer extends AnswerSimple {} 12 | 13 | export const answerResolver = defineSchemaOrgResolver({ 14 | cast(node) { 15 | if (typeof node === 'string') { 16 | return { 17 | text: node, 18 | } 19 | } 20 | return node 21 | }, 22 | defaults: { 23 | '@type': 'Answer', 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /examples/angular/src/app/counter/counter.component.ts: -------------------------------------------------------------------------------- 1 | // counter.component.ts 2 | import {Component, signal} from '@angular/core'; 3 | import { useHead } from '@unhead/angular' 4 | 5 | @Component({ 6 | selector: 'app-counter', 7 | templateUrl: './counter.component.html', 8 | styleUrls: ['./counter.component.css'] 9 | }) 10 | export class CounterComponent { 11 | counter = signal(0); 12 | head = useHead() 13 | 14 | constructor() { 15 | } 16 | 17 | incrementCounter() { 18 | this.head.patch({ 19 | title: () => `Counter: ${this.counter()}` 20 | }) 21 | this.counter.update(value => value + 1); 22 | console.log('incrementing counter using setTitle ') 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/svelte/test/fixtures/SeoTest.svelte: -------------------------------------------------------------------------------- 1 | 2 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /examples/vite-ssr-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["src"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/svelte/src/client.ts: -------------------------------------------------------------------------------- 1 | import type { CreateClientHeadOptions, Unhead } from 'unhead/types' 2 | import { tick } from 'svelte' 3 | import { createHead as _createHead, createDebouncedFn, renderDOMHead } from 'unhead/client' 4 | 5 | export { UnheadContextKey } from './context' 6 | 7 | export function createHead(options: CreateClientHeadOptions = {}): Unhead { 8 | const head = _createHead({ 9 | domOptions: { 10 | render: createDebouncedFn(() => renderDOMHead(head), fn => tick().then(fn)), 11 | }, 12 | ...options, 13 | }) 14 | return head 15 | } 16 | 17 | export { renderDOMHead } from 'unhead/client' 18 | 19 | export type { 20 | CreateClientHeadOptions, 21 | Unhead, 22 | } 23 | -------------------------------------------------------------------------------- /packages/schema-org/src/nodes/HowTo/HowToStepDirection/index.ts: -------------------------------------------------------------------------------- 1 | import type { Thing } from '../../../types' 2 | import { defineSchemaOrgResolver } from '../../../core' 3 | 4 | export interface HowToDirection extends Thing { 5 | /** 6 | * The text of the direction or tip. 7 | */ 8 | text: string 9 | } 10 | 11 | /** 12 | * Describes the text of a direction or tip for a step in a HowTo guide. 13 | */ 14 | export const howToStepDirectionResolver = defineSchemaOrgResolver({ 15 | cast(node) { 16 | if (typeof node === 'string') { 17 | return { 18 | text: node, 19 | } 20 | } 21 | return node 22 | }, 23 | defaults: { 24 | '@type': 'HowToDirection', 25 | }, 26 | }) 27 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "target": "ES2022", 6 | "lib": ["ES2023"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /examples/vite-ssr-react-ts/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "target": "ES2022", 6 | "lib": ["ES2023"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/schema-org/src/nodes/WebPage/ReadAction/index.ts: -------------------------------------------------------------------------------- 1 | import { defineSchemaOrgResolver } from '../../../core' 2 | 3 | export interface ReadActionInput { 4 | target?: string[] 5 | } 6 | 7 | export interface ReadAction { 8 | '@type'?: 'ReadAction' 9 | /** 10 | * An array of string URLs which describes the URL pattern of the read action 11 | * (e.g., /search?query={search_term_string}). 12 | */ 13 | 'target': string[] 14 | } 15 | 16 | export const readActionResolver = defineSchemaOrgResolver({ 17 | defaults: { 18 | '@type': 'ReadAction', 19 | }, 20 | resolve(node, ctx) { 21 | if (!node.target.includes(ctx.meta.url)) 22 | node.target.unshift(ctx.meta.url) 23 | return node 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /examples/angular/src/app/counter/counter.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CounterComponent } from './counter.component'; 4 | 5 | describe('CounterComponent', () => { 6 | let component: CounterComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [CounterComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(CounterComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/schema-org/src/nodes/VirtualLocation/index.ts: -------------------------------------------------------------------------------- 1 | import type { Thing } from '../../types' 2 | import { defineSchemaOrgResolver } from '../../core' 3 | 4 | export interface VirtualLocationSimple extends Thing { 5 | '@type'?: 'VirtualLocation' 6 | 'url': string 7 | } 8 | 9 | export interface VirtualLocation extends VirtualLocationSimple {} 10 | 11 | /** 12 | * Describes a HowTo guide, which contains a series of steps. 13 | */ 14 | export const virtualLocationResolver = defineSchemaOrgResolver({ 15 | cast(node) { 16 | if (typeof node === 'string') { 17 | return { 18 | url: node, 19 | } 20 | } 21 | return node 22 | }, 23 | defaults: { 24 | '@type': 'VirtualLocation', 25 | }, 26 | }) 27 | -------------------------------------------------------------------------------- /examples/vite-ssr-svelte/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "resolveJsonModule": true, 8 | /** 9 | * Typecheck JS in `.svelte` and `.js` files by default. 10 | * Disable checkJs if you'd like to use dynamic types in JS. 11 | * Note that setting allowJs false does not prevent the use 12 | * of JS in `.svelte` files. 13 | */ 14 | "allowJs": true, 15 | "checkJs": true, 16 | "isolatedModules": true, 17 | "moduleDetection": "force" 18 | }, 19 | "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /packages/unhead/src/types/schema/htmlAttributes.ts: -------------------------------------------------------------------------------- 1 | import type { GlobalAttributes } from './attributes/global' 2 | 3 | export interface HtmlAttributes extends Pick { 4 | /** 5 | * Open-graph protocol prefix. 6 | * 7 | * @see https://ogp.me/ 8 | */ 9 | prefix?: 'og: https://ogp.me/ns#' | (string & Record) 10 | /** 11 | * XML namespace 12 | * 13 | * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Namespaces_Crash_Course 14 | */ 15 | xmlns?: string 16 | /** 17 | * Custom XML namespace 18 | * 19 | * @See https://developer.mozilla.org/en-US/docs/Web/SVG/Namespaces_Crash_Course 20 | */ 21 | [key: `xmlns:${'og' | string}`]: string 22 | } 23 | -------------------------------------------------------------------------------- /docs/0.nuxt/head/guides/0.get-started/1.migration.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Migrate Your Nuxt App to Unhead v2 3 | description: Learn about how to migrate to Unhead v2 from v1 in Nuxt. 4 | navigation: 5 | title: Upgrade Guide 6 | --- 7 | 8 | ## Introduction 9 | 10 | As of Nuxt 3.16, Unhead v2 is the default version for Nuxt. 11 | 12 | While this change was made without breaking changes, if you've opted into the Nuxt v4 mode, you may have run into some issues. 13 | 14 | ### Nuxt v4 Migration 15 | 16 | The best resource for managing the migration is the official [Nuxt v4 migration guide](https://nuxt.com/docs/getting-started/upgrade#migrating-to-nuxt-4). 17 | 18 | For the full list of changes or if you still 19 | have issues check out the [Unhead v2 migration guide](/docs/vue/migration). 20 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/pages/error-script.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 31 | 32 | 38 | -------------------------------------------------------------------------------- /packages/vue/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | externals: ['vue'], 5 | declaration: true, 6 | entries: [ 7 | { input: 'src/index', name: 'index' }, 8 | { input: 'src/components', name: 'components' }, 9 | { input: 'src/server', name: 'server' }, 10 | { input: 'src/client', name: 'client' }, 11 | { input: 'src/legacy', name: 'legacy' }, 12 | { input: 'src/types/index', name: 'types' }, 13 | { input: 'src/plugins', name: 'plugins' }, 14 | { input: 'src/scripts', name: 'scripts' }, 15 | { input: 'src/utils', name: 'utils' }, 16 | ], 17 | hooks: { 18 | 'rollup:options': (_, options) => { 19 | options.experimentalLogSideEffects = true 20 | }, 21 | }, 22 | }) 23 | -------------------------------------------------------------------------------- /examples/vite-ssr-react-ts/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | } 13 | .logo:hover { 14 | filter: drop-shadow(0 0 2em #646cffaa); 15 | } 16 | .logo.react:hover { 17 | filter: drop-shadow(0 0 2em #61dafbaa); 18 | } 19 | 20 | @keyframes logo-spin { 21 | from { 22 | transform: rotate(0deg); 23 | } 24 | to { 25 | transform: rotate(360deg); 26 | } 27 | } 28 | 29 | @media (prefers-reduced-motion: no-preference) { 30 | a:nth-of-type(2) .logo { 31 | animation: logo-spin infinite 20s linear; 32 | } 33 | } 34 | 35 | .card { 36 | padding: 2em; 37 | } 38 | 39 | .read-the-docs { 40 | color: #888; 41 | } 42 | -------------------------------------------------------------------------------- /examples/vite-ssr-react-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "noUncheckedSideEffectImports": true 23 | }, 24 | "include": ["src"], 25 | "references": [{ "path": "./tsconfig.node.json" }] 26 | } 27 | -------------------------------------------------------------------------------- /examples/vite-ssr-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-typescript-starter", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "node server", 8 | "build": "npm run build:client && npm run build:server", 9 | "build:client": "vite build --outDir dist/client", 10 | "build:server": "vite build --ssr src/entry-server.ts --outDir dist/server", 11 | "preview": "cross-env NODE_ENV=production node server" 12 | }, 13 | "dependencies": { 14 | "compression": "^1.8.1", 15 | "express": "^5.2.1", 16 | "sirv": "^3.0.2" 17 | }, 18 | "devDependencies": { 19 | "@types/express": "^5.0.6", 20 | "@types/node": "^24.10.4", 21 | "cross-env": "^10.1.0", 22 | "typescript": "5.8.3", 23 | "vite": "^7.3.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/unhead/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | declaration: true, 6 | hooks: { 7 | 'rollup:options': (_, options) => { 8 | options.experimentalLogSideEffects = true 9 | }, 10 | }, 11 | entries: [ 12 | { input: 'src/index', name: 'index' }, 13 | { input: 'src/types/index', name: 'types' }, 14 | { input: 'src/legacy', name: 'legacy' }, 15 | { input: 'src/server/index', name: 'server' }, 16 | { input: 'src/client/index', name: 'client' }, 17 | { input: 'src/scripts/index', name: 'scripts' }, 18 | { input: 'src/utils', name: 'utils' }, 19 | { input: 'src/plugins/index', name: 'plugins' }, 20 | { input: 'src/parser/index', name: 'parser' }, 21 | ], 22 | }) 23 | -------------------------------------------------------------------------------- /packages/unhead/src/types/schema/struct/blocking.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the possible blocking tokens for an element. 3 | */ 4 | export type BlockingToken = 'render' 5 | 6 | /** 7 | * Represents the blocking attribute for an element. 8 | * The blocking attribute must have a value that is an unordered set of unique space-separated tokens, 9 | * each of which are possible blocking tokens. 10 | */ 11 | export interface Blocking { 12 | /** 13 | * The blocking attribute indicates that certain operations should be blocked on the fetching of an external resource. 14 | * The value is an unordered set of unique space-separated tokens, each of which are possible blocking tokens. 15 | * 16 | * @example 17 | * blocking: "render" 18 | */ 19 | blocking?: BlockingToken | string // escape hatch 20 | } 21 | -------------------------------------------------------------------------------- /packages/unhead/test/unit/client/position.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | import { useHead } from '../../../src' 3 | import { useDelayedSerializedDom, useDOMHead } from '../../util' 4 | 5 | describe('dom position', () => { 6 | it('body', async () => { 7 | const head = useDOMHead() 8 | 9 | useHead(head, { 10 | script: [ 11 | { 12 | innerHTML: 'Hello World', 13 | tagPosition: 'bodyClose', 14 | }, 15 | ], 16 | }) 17 | 18 | expect(await useDelayedSerializedDom()).toMatchInlineSnapshot(` 19 | " 20 | 21 | 22 | 23 | 24 |
25 |

hello world

26 |
27 | 28 | 29 | 30 | " 31 | `) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/pages/stripe.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 29 | 30 | 36 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | "jsx": "preserve", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "noUncheckedSideEffectImports": true 23 | }, 24 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], 25 | "references": [{ "path": "./tsconfig.node.json" }] 26 | } 27 | -------------------------------------------------------------------------------- /packages/vue/src/client.ts: -------------------------------------------------------------------------------- 1 | import type { CreateClientHeadOptions } from 'unhead/types' 2 | import type { VueHeadClient } from './types' 3 | import { createHead as _createHead, createDebouncedFn, renderDOMHead } from 'unhead/client' 4 | import { vueInstall } from './install' 5 | 6 | export { VueHeadMixin } from './VueHeadMixin' 7 | export { renderDOMHead } from 'unhead/client' 8 | 9 | /* @__NO_SIDE_EFFECTS__ */ 10 | export function createHead(options: CreateClientHeadOptions = {}): VueHeadClient { 11 | const head = _createHead({ 12 | domOptions: { 13 | render: createDebouncedFn(() => renderDOMHead(head), fn => setTimeout(fn, 0)), 14 | }, 15 | ...options, 16 | }) as VueHeadClient 17 | head.install = vueInstall(head) 18 | return head 19 | } 20 | 21 | export type { 22 | CreateClientHeadOptions, 23 | VueHeadClient, 24 | } 25 | -------------------------------------------------------------------------------- /packages/unhead/test/unit/scripts/use.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expectTypeOf, it } from 'vitest' 2 | import { useScript } from '../../../src/composables' 3 | import { createHead as createServerHead } from '../../../src/server' 4 | 5 | describe('useScript', () => { 6 | it('types: inferred use()', async () => { 7 | const head = createServerHead() 8 | const instance = useScript(head, { 9 | src: 'https://cdn.example.com/script.js', 10 | }, { 11 | use() { 12 | return { 13 | // eslint-disable-next-line unused-imports/no-unused-vars 14 | test: (foo: string) => 'foo', 15 | } 16 | }, 17 | }) 18 | expectTypeOf(instance.proxy.test).toBeFunction() 19 | expectTypeOf(instance.proxy.test).parameter(0).toBeString() 20 | expectTypeOf(instance.proxy.test).returns.toBeVoid() 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We support security updates for all current major versions since 1.x. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | We take the security of Unhead seriously. If you believe you've found a security vulnerability, please: 10 | 11 | - Email us at harlan@harlanzw.com with details about the vulnerability 12 | - OR submit a security advisory through the GitHub repository (not as a regular issue) 13 | - Include steps to reproduce the vulnerability 14 | - If possible, include impact and recommendations for mitigation 15 | 16 | We'll acknowledge receipt of your report promptly and work on addressing the issue. 17 | 18 | ## Scope 19 | 20 | Please note that we do not consider XSS vulnerabilities when using the `innerHTML` attribute as security issues, as this is an inherent risk when using this feature. 21 | -------------------------------------------------------------------------------- /examples/angular/src/app/app.config.server.ts: -------------------------------------------------------------------------------- 1 | import {mergeApplicationConfig, ApplicationConfig} from '@angular/core'; 2 | import { provideServerRendering, withRoutes } from '@angular/ssr'; 3 | import { appConfig } from './app.config'; 4 | import { serverRoutes } from './app.routes.server'; 5 | import { provideServerHead } from '@unhead/angular/server' 6 | 7 | const serverConfig: ApplicationConfig = { 8 | providers: [ 9 | provideServerRendering(withRoutes(serverRoutes)), 10 | provideServerHead({ 11 | init: [ 12 | { 13 | htmlAttrs: { 14 | lang: 'en-AU', 15 | ['data-foo']: true, 16 | style: { 17 | 'font-size': '16px' 18 | } 19 | }, 20 | } 21 | ] 22 | }), 23 | ] 24 | }; 25 | 26 | export const config = mergeApplicationConfig(appConfig, serverConfig); 27 | -------------------------------------------------------------------------------- /packages/unhead/src/plugins/flatMeta.ts: -------------------------------------------------------------------------------- 1 | import type { HeadTag } from '../types' 2 | import { unpackMeta } from '../utils/meta' 3 | import { defineHeadPlugin } from './defineHeadPlugin' 4 | 5 | export const FlatMetaPlugin = /* @__PURE__ */ defineHeadPlugin({ 6 | key: 'flatMeta', 7 | hooks: { 8 | 'entries:normalize': (ctx) => { 9 | const tagsToAdd: HeadTag[] = [] 10 | ctx.tags = ctx.tags.map((t) => { 11 | // @ts-expect-error untyped 12 | if (t.tag !== '_flatMeta') { 13 | return t 14 | } 15 | // @ts-expect-error untyped 16 | tagsToAdd.push(unpackMeta(t.props).map(p => ({ 17 | ...t, 18 | tag: 'meta', 19 | props: p, 20 | }))) 21 | return false 22 | }) 23 | .filter(Boolean) 24 | .concat(...tagsToAdd) as HeadTag[] 25 | }, 26 | }, 27 | }) 28 | -------------------------------------------------------------------------------- /packages/unhead/src/utils/walkResolver.ts: -------------------------------------------------------------------------------- 1 | import type { PropResolver } from '../types' 2 | 3 | export function walkResolver(val: any, resolve?: PropResolver, key?: string): any { 4 | // Combined primitive type check 5 | const type = typeof val 6 | 7 | if (type === 'function') { 8 | if (!key || (key !== 'titleTemplate' && !(key[0] === 'o' && key[1] === 'n'))) { 9 | val = val() 10 | } 11 | } 12 | 13 | // Apply resolver if provided, otherwise use the value as-is 14 | const v = resolve ? resolve(key, val) : val 15 | 16 | if (Array.isArray(v)) { 17 | return v.map(r => walkResolver(r, resolve)) 18 | } 19 | 20 | if (v?.constructor === Object) { 21 | const next: Record = {} 22 | for (const k of Object.keys(v)) { 23 | next[k] = walkResolver(v[k], resolve, k) 24 | } 25 | return next 26 | } 27 | 28 | return v 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | env: 15 | NODE_OPTIONS: --max-old-space-size=8048 16 | 17 | steps: 18 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 19 | 20 | - name: Install pnpm 21 | uses: pnpm/action-setup@v4.1.0 22 | 23 | - name: Use Node.js 24.x 24 | uses: actions/setup-node@v6 25 | with: 26 | node-version: 24.x 27 | registry-url: https://registry.npmjs.org/ 28 | cache: pnpm 29 | 30 | - run: pnpm i 31 | 32 | - name: Build 33 | run: pnpm run build 34 | 35 | - name: Test 36 | run: pnpm test 37 | 38 | - name: attw 39 | run: pnpm run test:attw 40 | -------------------------------------------------------------------------------- /packages/schema-org/src/nodes/TVSeason/index.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'vitest' 2 | import { defineTVSeason, useSchemaOrg } from '../../' 3 | import { injectSchemaOrg, useSetup } from '../../../test' 4 | 5 | describe('defineTVSeason', () => { 6 | it('can be defined', async () => { 7 | await useSetup(async (head) => { 8 | useSchemaOrg(head, [ 9 | defineTVSeason({ 10 | seasonNumber: 2, 11 | numberOfEpisodes: 13, 12 | }), 13 | ]) 14 | 15 | const graphNodes = await injectSchemaOrg(head) 16 | 17 | expect(graphNodes).toMatchInlineSnapshot(` 18 | [ 19 | { 20 | "@id": "https://example.com/#/schema/tvseason/1", 21 | "@type": "TVSeason", 22 | "numberOfEpisodes": 13, 23 | "seasonNumber": 2, 24 | }, 25 | ] 26 | `) 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /packages/unhead/src/plugins/deprecations.ts: -------------------------------------------------------------------------------- 1 | import { defineHeadPlugin } from './defineHeadPlugin' 2 | 3 | export const DeprecationsPlugin = /* @__PURE__ */ defineHeadPlugin({ 4 | key: 'deprecations', 5 | hooks: { 6 | 'entries:normalize': ({ tags }) => { 7 | // copy logic from above hook 8 | for (const tag of tags) { 9 | if (tag.props.children) { 10 | tag.innerHTML = tag.props.children 11 | delete tag.props.children 12 | } 13 | if (tag.props.hid) { 14 | tag.key = tag.props.hid 15 | delete tag.props.hid 16 | } 17 | if (tag.props.vmid) { 18 | tag.key = tag.props.vmid 19 | delete tag.props.vmid 20 | } 21 | if (tag.props.body) { 22 | tag.tagPosition = 'bodyClose' 23 | delete tag.props.body 24 | } 25 | } 26 | }, 27 | }, 28 | }) 29 | -------------------------------------------------------------------------------- /packages/vue/src/server.ts: -------------------------------------------------------------------------------- 1 | import type { CreateServerHeadOptions } from 'unhead/types' 2 | import type { VueHeadClient } from './types' 3 | import { createHead as _createServerHead } from 'unhead/server' 4 | import { vueInstall } from './install' 5 | import { VueResolver } from './resolver' 6 | 7 | export { VueHeadMixin } from './VueHeadMixin' 8 | export { extractUnheadInputFromHtml, propsToString, renderSSRHead, type SSRHeadPayload, transformHtmlTemplate } from 'unhead/server' 9 | 10 | /* @__NO_SIDE_EFFECTS__ */ 11 | export function createHead(options: Omit = {}): VueHeadClient { 12 | const head = _createServerHead({ 13 | ...options, 14 | propResolvers: [VueResolver], 15 | }) as VueHeadClient 16 | head.install = vueInstall(head) 17 | return head 18 | } 19 | 20 | export type { 21 | CreateServerHeadOptions, 22 | VueHeadClient, 23 | } 24 | -------------------------------------------------------------------------------- /packages/angular/client/src/client.ts: -------------------------------------------------------------------------------- 1 | import type { CreateClientHeadOptions } from 'unhead/types' 2 | import { DOCUMENT } from '@angular/common' 3 | import { inject, makeEnvironmentProviders } from '@angular/core' 4 | import { UnheadInjectionToken } from '@unhead/angular' 5 | import { createHead as _createClientHead, createDebouncedFn, renderDOMHead } from 'unhead/client' 6 | 7 | export function provideClientHead(options: CreateClientHeadOptions = {}) { 8 | return makeEnvironmentProviders([{ 9 | provide: UnheadInjectionToken, 10 | useFactory: () => { 11 | const document = inject(DOCUMENT) 12 | const head = _createClientHead({ 13 | document, 14 | domOptions: { 15 | render: createDebouncedFn(() => renderDOMHead(head), fn => setTimeout(() => fn(), 0)), 16 | }, 17 | ...options, 18 | }) 19 | return head 20 | }, 21 | }]) 22 | } 23 | -------------------------------------------------------------------------------- /packages/angular/server/src/server.ts: -------------------------------------------------------------------------------- 1 | import type { CreateServerHeadOptions } from 'unhead/types' 2 | import { makeEnvironmentProviders } from '@angular/core' 3 | import { BEFORE_APP_SERIALIZED } from '@angular/platform-server' 4 | import { UnheadInjectionToken } from '@unhead/angular' 5 | import { createHead as _createServerHead } from 'unhead/server' 6 | import { UnheadSSRService } from './ssr.service' 7 | 8 | export function provideServerHead(options: CreateServerHeadOptions = {}) { 9 | const head = _createServerHead(options) 10 | return makeEnvironmentProviders([ 11 | { provide: UnheadInjectionToken, useValue: head }, 12 | UnheadSSRService, 13 | { 14 | provide: BEFORE_APP_SERIALIZED, 15 | useFactory: (service: UnheadSSRService) => () => { 16 | return service.render() 17 | }, 18 | deps: [UnheadSSRService], 19 | multi: true, 20 | }, 21 | ]) 22 | } 23 | -------------------------------------------------------------------------------- /packages/schema-org/src/nodes/PodcastSeason/index.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'vitest' 2 | import { definePodcastSeason, useSchemaOrg } from '../../' 3 | import { injectSchemaOrg, useSetup } from '../../../test' 4 | 5 | describe('definePodcastSeason', () => { 6 | it('can be defined', async () => { 7 | await useSetup(async (head) => { 8 | useSchemaOrg(head, [ 9 | definePodcastSeason({ 10 | seasonNumber: 2, 11 | numberOfEpisodes: 12, 12 | }), 13 | ]) 14 | 15 | const graphNodes = await injectSchemaOrg(head) 16 | 17 | expect(graphNodes).toMatchInlineSnapshot(` 18 | [ 19 | { 20 | "@id": "https://example.com/#/schema/podcast-season/1", 21 | "@type": "PodcastSeason", 22 | "numberOfEpisodes": 12, 23 | "seasonNumber": 2, 24 | }, 25 | ] 26 | `) 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/pages/idle-script.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 38 | 39 | 45 | -------------------------------------------------------------------------------- /packages/schema-org/src/nodes/Image/index.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'vitest' 2 | import { defineImage, useSchemaOrg } from '../../' 3 | import { injectSchemaOrg, useSetup } from '../../../test' 4 | 5 | describe('defineImage', () => { 6 | it('can be registered', async () => { 7 | await useSetup(async (head) => { 8 | useSchemaOrg(head, [ 9 | defineImage({ 10 | url: '/image.png', 11 | }), 12 | ]) 13 | 14 | const graphNodes = await injectSchemaOrg(head) 15 | 16 | expect(graphNodes).toMatchInlineSnapshot(` 17 | [ 18 | { 19 | "@id": "https://example.com/#/schema/image/1", 20 | "@type": "ImageObject", 21 | "contentUrl": "https://example.com/image.png", 22 | "inLanguage": "en-AU", 23 | "url": "https://example.com/image.png", 24 | }, 25 | ] 26 | `) 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /packages/unhead/src/server/util/propsToString.ts: -------------------------------------------------------------------------------- 1 | /* @__PURE__ */ 2 | function encodeAttribute(value: string) { 3 | return String(value).replace(/"/g, '"') 4 | } 5 | 6 | /* @__PURE__ */ 7 | export function propsToString(props: Record) { 8 | let attrs = '' 9 | 10 | for (const key in props) { 11 | if (!Object.hasOwn(props, key)) 12 | continue 13 | 14 | let value = props[key] 15 | 16 | // class (set) and style (map) 17 | if ((key === 'class' || key === 'style') && typeof value !== 'string') { 18 | value = key === 'class' 19 | ? Array.from(value).join(' ') 20 | : Array.from(value as Map) 21 | .map(([k, v]) => `${k}:${v}`) 22 | .join(';') 23 | } 24 | 25 | if (value !== false && value !== null) { 26 | attrs += value === true ? ` ${key}` : ` ${key}="${encodeAttribute(value)}"` 27 | } 28 | } 29 | 30 | return attrs 31 | } 32 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/router.js: -------------------------------------------------------------------------------- 1 | import { 2 | createRouter as _createRouter, 3 | createMemoryHistory, 4 | createWebHistory, 5 | } from 'vue-router' 6 | 7 | // Auto generates routes from vue files under ./pages 8 | // https://vitejs.dev/guide/features.html#glob-import 9 | const pages = import.meta.glob('./pages/*.vue') 10 | 11 | const routes = Object.keys(pages).map((path) => { 12 | const name = path.match(/\.\/pages(.*)\.vue$/)[1].toLowerCase() 13 | return { 14 | path: name === '/home' ? '/' : name, 15 | component: pages[path], // () => import('./pages/*.vue') 16 | } 17 | }) 18 | 19 | export function createRouter() { 20 | return _createRouter({ 21 | // use appropriate history implementation for server/client 22 | // import.meta.env.SSR is injected by Vite. 23 | history: import.meta.env.SSR 24 | ? createMemoryHistory('/test/') 25 | : createWebHistory('/test/'), 26 | routes, 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-streaming/src/router.js: -------------------------------------------------------------------------------- 1 | import { 2 | createRouter as _createRouter, 3 | createMemoryHistory, 4 | createWebHistory, 5 | } from 'vue-router' 6 | 7 | // Auto generates routes from vue files under ./pages 8 | // https://vitejs.dev/guide/features.html#glob-import 9 | const pages = import.meta.glob('./pages/*.vue') 10 | 11 | const routes = Object.keys(pages).map((path) => { 12 | const name = path.match(/\.\/pages(.*)\.vue$/)[1].toLowerCase() 13 | return { 14 | path: name === '/home' ? '/' : name, 15 | component: pages[path], // () => import('./pages/*.vue') 16 | } 17 | }) 18 | 19 | export function createRouter() { 20 | return _createRouter({ 21 | // use appropriate history implementation for server/client 22 | // import.meta.env.SSR is injected by Vite. 23 | history: import.meta.env.SSR 24 | ? createMemoryHistory('/test/') 25 | : createWebHistory('/test/'), 26 | routes, 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /packages/vue/src/types/index.ts: -------------------------------------------------------------------------------- 1 | import type { RawInput } from 'unhead/types' 2 | 3 | export type * from './safeSchema' 4 | export type * from './schema' 5 | export type * from './util' 6 | export type { ActiveHeadEntry, Head, HeadEntryOptions, HeadTag, MergeHead, MetaFlatInput, RawInput, RenderSSRHeadOptions, ResolvableHead, SerializableHead, Unhead } from 'unhead/types' 7 | 8 | export type { AriaAttributes, BodyAttributesWithoutEvents, BodyEvents, DataKeys, GlobalAttributes, HttpEventAttributes, LinkWithoutEvents, MetaFlat, ScriptWithoutEvents, SpeculationRules } from 'unhead/types' 9 | 10 | export type Base = RawInput<'base'> 11 | export type HtmlAttributes = RawInput<'htmlAttrs'> 12 | export type Noscript = RawInput<'noscript'> 13 | export type Style = RawInput<'style'> 14 | export type Meta = RawInput<'meta'> 15 | export type Script = RawInput<'script'> 16 | export type Link = RawInput<'link'> 17 | export type BodyAttributes = RawInput<'bodyAttrs'> 18 | -------------------------------------------------------------------------------- /packages/schema-org/src/nodes/TVEpisode/index.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'vitest' 2 | import { defineTVEpisode, useSchemaOrg } from '../../' 3 | import { injectSchemaOrg, useSetup } from '../../../test' 4 | 5 | describe('defineTVEpisode', () => { 6 | it('can be defined', async () => { 7 | await useSetup(async (head) => { 8 | useSchemaOrg(head, [ 9 | defineTVEpisode({ 10 | name: 'Pilot', 11 | episodeNumber: 1, 12 | duration: 'PT58M', 13 | }), 14 | ]) 15 | 16 | const graphNodes = await injectSchemaOrg(head) 17 | 18 | expect(graphNodes).toMatchInlineSnapshot(` 19 | [ 20 | { 21 | "@id": "https://example.com/#/schema/tvepisode/1", 22 | "@type": "TVEpisode", 23 | "duration": "PT58M", 24 | "episodeNumber": 1, 25 | "name": "Pilot", 26 | }, 27 | ] 28 | `) 29 | }) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-prerender/src/pages/stripe-pricing-table.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 34 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue-streaming/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /packages/schema-org/src/nodes/Place/index.ts: -------------------------------------------------------------------------------- 1 | import type { NodeRelation, Thing } from '../../types' 2 | import type { PostalAddress } from '../PostalAddress' 3 | import { defineSchemaOrgResolver, resolveRelation } from '../../core' 4 | import { addressResolver } from '../PostalAddress' 5 | 6 | export interface PlaceSimple extends Thing { 7 | '@type'?: 'Place' 8 | 'name': string 9 | 'address': NodeRelation 10 | 'latitude'?: number | string 11 | 'longitude'?: number | string 12 | } 13 | 14 | export interface Place extends PlaceSimple {} 15 | 16 | /** 17 | * Describes a HowTo guide, which contains a series of steps. 18 | */ 19 | export const placeResolver = defineSchemaOrgResolver({ 20 | defaults: { 21 | '@type': 'Place', 22 | }, 23 | resolve(node, ctx) { 24 | if (typeof node.address !== 'string') 25 | node.address = resolveRelation(node.address, ctx, addressResolver) 26 | return node 27 | }, 28 | }) 29 | -------------------------------------------------------------------------------- /packages/react/src/client.ts: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react' 2 | import type { CreateClientHeadOptions, Unhead } from 'unhead/types' 3 | import { createElement } from 'react' 4 | import { createHead as _createHead, createDebouncedFn, renderDOMHead } from 'unhead/client' 5 | import { UnheadContext } from './context' 6 | 7 | export { renderDOMHead } from 'unhead/client' 8 | 9 | export function createHead(options: CreateClientHeadOptions = {}): Unhead { 10 | const head = _createHead({ 11 | domOptions: { 12 | render: createDebouncedFn(() => renderDOMHead(head), fn => setTimeout(fn, 0)), 13 | }, 14 | ...options, 15 | }) 16 | return head 17 | } 18 | 19 | export function UnheadProvider({ children, head }: { children: ReactNode, head?: ReturnType }) { 20 | return createElement(UnheadContext.Provider, { value: head || createHead() }, children) 21 | } 22 | 23 | export type { 24 | CreateClientHeadOptions, 25 | Unhead, 26 | } 27 | -------------------------------------------------------------------------------- /packages/schema-org/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | 3 | export default defineBuildConfig({ 4 | clean: true, 5 | declaration: true, 6 | entries: [ 7 | { input: 'src/index' }, 8 | { input: 'src/vue/index', name: 'vue' }, // ships components 9 | { input: 'src/svelte', name: 'svelte' }, 10 | { input: 'src/react', name: 'react' }, 11 | { input: 'src/solid-js', name: 'solid-js' }, 12 | ], 13 | externals: [ 14 | 'vue', 15 | '@vue/runtime-core', 16 | 'unplugin-vue-components', 17 | 'unhead', 18 | 'vite', 19 | 'react', 20 | 'svelte', 21 | 'vue-router', 22 | '@unhead/vue', 23 | 'unplugin-ast', 24 | 'unplugin', 25 | 'unplugin-vue-components', 26 | 'vue', 27 | '@vue/runtime-core', 28 | '@unhead/react', 29 | '@unhead/solid-js', 30 | '@unhead/svelte', 31 | '@unhead/vue', 32 | 'unhead', 33 | 'unhead/utils', 34 | 'unhead/plugins', 35 | ], 36 | }) 37 | -------------------------------------------------------------------------------- /packages/unhead/test/unit/client/eventHandlers.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useHead } from '../../../src' 3 | import { useDelayedSerializedDom, useDOMHead } from '../../util' 4 | 5 | describe('dom event handlers', () => { 6 | it('basic', async () => { 7 | const head = useDOMHead() 8 | 9 | useHead(head, { 10 | script: [ 11 | { 12 | src: 'https://js.stripe.com/v3/', 13 | defer: true, 14 | // eslint-disable-next-line no-console 15 | onload: () => console.log('loaded stripe'), 16 | }, 17 | ], 18 | }) 19 | 20 | expect(await useDelayedSerializedDom()).toMatchInlineSnapshot(` 21 | " 22 | 23 | 24 | 25 | 26 |
27 |

hello world

28 |
29 | 30 | 31 | 32 | " 33 | `) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ### 🔗 Linked issue 6 | 7 | 8 | 9 | ### ❓ Type of change 10 | 11 | 12 | 13 | - [ ] 📖 Documentation (updates to the documentation or readme) 14 | - [ ] 🐞 Bug fix (a non-breaking change that fixes an issue) 15 | - [ ] 👌 Enhancement (improving an existing functionality) 16 | - [ ] ✨ New feature (a non-breaking change that adds functionality) 17 | - [ ] 🧹 Chore (updates to the build process or auxiliary tools and libraries) 18 | - [ ] ⚠️ Breaking change (fix or feature that would cause existing functionality to change) 19 | 20 | ### 📚 Description 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /bench/use-seo-meta-perf.bench.ts: -------------------------------------------------------------------------------- 1 | import { createHead, renderSSRHead } from 'unhead/server' 2 | import { bench, describe } from 'vitest' 3 | import { useSeoMeta } from '../packages/unhead/src' 4 | 5 | describe('use seo meta', () => { 6 | bench('x50 ssr', async () => { 7 | const head = createHead() 8 | const page = { 9 | title: 'Home', 10 | description: 'Home page description', 11 | image: 'https://nuxtjs.org/meta_0.png', 12 | } 13 | for (const i in Array.from({ length: 1000 })) { 14 | useSeoMeta(head, { 15 | // de-dupe keys 16 | title: `${page.title}-${i} | Nuxt`, 17 | description: `${page.description} ${i}`, 18 | ogImage: `${page.image}?${i}`, 19 | ogImageAlt: `${page.image}?${i}`, 20 | ogSiteName: 'Nuxt', 21 | ogType: 'website', 22 | }, { 23 | head, 24 | }) 25 | } 26 | await renderSSRHead(head) 27 | }, { 28 | iterations: 1000, 29 | time: 1000, 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /docs/0.angular/head/guides/1.core-concepts/1.components.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Component 3 | description: Use the component to manage your head tags. 4 | navigation: 5 | title: ' Component' 6 | --- 7 | 8 | The Unhead Vue package exports a ``{lang="html"} component that can be used to manage your head tags. 9 | 10 | While it's recommended to use the `useHead()`{lang="ts"} composable as it offers a more flexible API with full TypeScript support, 11 | the ``{lang="html"} component may make more sense for your project. 12 | 13 | The component will takes any child elements that you would normally put in your actual ``{lang="html"} and renders them 14 | with Unhead. 15 | 16 | ```vue 17 | 20 | 21 | 27 | ``` 28 | -------------------------------------------------------------------------------- /packages/react/test/ReactiveTitle.test.tsx: -------------------------------------------------------------------------------- 1 | import { act, render } from '@testing-library/react' 2 | import { renderSSRHead } from '@unhead/react/server' 3 | // @vitest-environment jsdom 4 | import React from 'react' 5 | import { describe, expect, it } from 'vitest' 6 | import { createHead, UnheadProvider } from '../src/client' 7 | import { ReactiveTitle } from './fixtures/ReactiveTitle' 8 | 9 | describe('unheadProvider', () => { 10 | it('updates head when title changes', async () => { 11 | const head = createHead() 12 | const { getByText } = render( 13 | 14 | 15 | , 16 | ) 17 | 18 | expect(getByText('Test')).toBeDefined() 19 | 20 | await act(async () => { 21 | getByText('Update').click() 22 | }) 23 | 24 | expect(getByText('Updated')).toBeDefined() 25 | const res = await renderSSRHead(head) 26 | expect(res.headTags).toEqual(`Updated`) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/vue/src/types/util.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef, Ref } from 'vue' 2 | 3 | type Falsy = false | null | undefined 4 | export type MaybeFalsy = T | Falsy 5 | 6 | export type ResolvableValue = MaybeFalsy | (() => MaybeFalsy) | ComputedRef> | Ref> 7 | 8 | export type ResolvableArray = ResolvableValue[]> 9 | 10 | export type ResolvableProperties = { 11 | [key in keyof T]?: ResolvableValue 12 | } 13 | 14 | export type ResolvableUnion = T extends string | number | boolean 15 | ? ResolvableValue 16 | : T extends object 17 | ? DeepResolvableProperties 18 | : ResolvableValue 19 | 20 | export type DeepResolvableProperties = { 21 | [K in keyof T]?: T[K] extends string | object 22 | ? T[K] extends string 23 | ? ResolvableUnion 24 | : T[K] extends object 25 | ? DeepResolvableProperties 26 | : ResolvableUnion 27 | : ResolvableUnion 28 | } 29 | -------------------------------------------------------------------------------- /docs/0.nuxt/schema-org/guides/0.get-started/0.installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Install Unhead Schema.org' 3 | description: 'Get started with Unhead Schema.org by installing the dependency to your project.' 4 | navigation: 5 | title: 'Installation' 6 | --- 7 | 8 | ## Introduction 9 | 10 | To use Unhead Schema.org with Nuxt, you need to install the Nuxt Schema.org module. 11 | 12 | ## Setup 13 | 14 | Please follow the [installation documentation](https://nuxtseo.com/docs/schema-org/getting-started/installation) to get started. 15 | 16 | ## Next Steps 17 | 18 | Your app is now serving basic Schema.org, congrats! 🎉 19 | 20 | The next steps are: 21 | 22 | 1. Choose an [Identity](/schema-org/recipes/identity) 23 | 2. Set up your pages for [Schema.org Params](/docs/schema-org/guides/core-concepts/params) 24 | 3. Then feel free to follow some recipes: 25 | 26 | - [Breadcrumbs](/schema-org/recipes/breadcrumbs) 27 | - [FAQ Page](/schema-org/recipes/faq) 28 | - [Site Search](/schema-org/recipes/site-search) 29 | -------------------------------------------------------------------------------- /packages/unhead/src/types/util.ts: -------------------------------------------------------------------------------- 1 | export type Booleanable = boolean | 'false' | 'true' | '' 2 | export type Stringable = string | Booleanable | number 3 | export type Arrayable = T | Array 4 | 5 | export type Never = { 6 | [P in keyof T]?: never 7 | } 8 | 9 | type Falsy = false | null | undefined 10 | 11 | export type ResolvableValue = T | Falsy | (() => (T | Falsy)) 12 | 13 | export type ResolvableProperties = { 14 | [key in keyof T]?: ResolvableValue 15 | } 16 | 17 | export type ResolvableUnion = T extends string | number | boolean 18 | ? ResolvableValue 19 | : T extends object 20 | ? DeepResolvableProperties 21 | : ResolvableValue 22 | 23 | export type DeepResolvableProperties = { 24 | [K in keyof T]?: T[K] extends string | object 25 | ? T[K] extends string 26 | ? ResolvableUnion 27 | : T[K] extends object 28 | ? DeepResolvableProperties 29 | : ResolvableUnion 30 | : ResolvableUnion 31 | } 32 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 23 | 24 | 37 | -------------------------------------------------------------------------------- /examples/vite-ssr-svelte/src/lib/Counter.svelte: -------------------------------------------------------------------------------- 1 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /packages/vue/test/unit/ssr/asyncSetup.test.ts: -------------------------------------------------------------------------------- 1 | import { renderSSRHead } from '@unhead/ssr' 2 | import { useHead } from '@unhead/vue' 3 | import { createHead } from '@unhead/vue/server' 4 | import { renderToString } from '@vue/server-renderer' 5 | import { describe, expect, it } from 'vitest' 6 | import { createSSRApp, ref } from 'vue' 7 | 8 | describe('vue ssr asyncSetup', () => { 9 | it('basic', async () => { 10 | const head = createHead({ 11 | disableDefaults: true, 12 | }) 13 | const app = createSSRApp({ 14 | async setup() { 15 | const title = ref('initial title') 16 | useHead({ 17 | title, 18 | }) 19 | await new Promise(resolve => setTimeout(resolve, 200)) 20 | title.value = 'new title' 21 | return () => '
hi
' 22 | }, 23 | }) 24 | app.use(head) 25 | await renderToString(app) 26 | 27 | const { headTags } = await renderSSRHead(head) 28 | expect(headTags).eq('new title') 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /packages/svelte/test/fixtures/ScriptLoading.svelte: -------------------------------------------------------------------------------- 1 | 29 | 30 | 31 | 32 | {#if !scriptLoaded && !scriptError} 33 |
Loading...
34 | {/if} 35 | 36 | {#if scriptLoaded} 37 |
Script Loaded
38 | {/if} 39 | 40 | {#if scriptError} 41 |
Script Error
42 | {/if} 43 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | contents: write 5 | id-token: write 6 | 7 | on: 8 | push: 9 | tags: 10 | - 'v*' 11 | 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Install pnpm 21 | uses: pnpm/action-setup@v4 22 | 23 | - name: Set node 24 | uses: actions/setup-node@v6 25 | with: 26 | node-version: latest 27 | cache: pnpm 28 | registry-url: 'https://registry.npmjs.org' 29 | 30 | - name: Force Set pnpm Registry 31 | run: pnpm config set registry https://registry.npmjs.org 32 | 33 | - run: npx changelogithub 34 | env: 35 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 36 | 37 | - name: Install Dependencies 38 | run: pnpm i 39 | 40 | - run: pnpm publish -r --access public --no-git-checks 41 | -------------------------------------------------------------------------------- /packages/unhead/src/server/renderSSRHead.ts: -------------------------------------------------------------------------------- 1 | import type { RenderSSRHeadOptions, ShouldRenderContext, SSRHeadPayload, SSRRenderContext, Unhead } from '../types' 2 | import { ssrRenderTags } from './util' 3 | 4 | /* @__NO_SIDE_EFFECTS__ */ 5 | export async function renderSSRHead(head: Unhead, options?: RenderSSRHeadOptions) { 6 | const beforeRenderCtx: ShouldRenderContext = { shouldRender: true } 7 | await head.hooks.callHook('ssr:beforeRender', beforeRenderCtx) 8 | if (!beforeRenderCtx.shouldRender) { 9 | return { 10 | headTags: '', 11 | bodyTags: '', 12 | bodyTagsOpen: '', 13 | htmlAttrs: '', 14 | bodyAttrs: '', 15 | } 16 | } 17 | const ctx = { tags: options?.resolvedTags || await head.resolveTags() } 18 | await head.hooks.callHook('ssr:render', ctx) 19 | const html: SSRHeadPayload = ssrRenderTags(ctx.tags, options) 20 | const renderCtx: SSRRenderContext = { tags: ctx.tags, html } 21 | await head.hooks.callHook('ssr:rendered', renderCtx) 22 | return renderCtx.html 23 | } 24 | -------------------------------------------------------------------------------- /packages/vue/test/unit/dom/events.test.ts: -------------------------------------------------------------------------------- 1 | // @vitest-environment jsdom 2 | 3 | import { renderDOMHead } from '@unhead/dom' 4 | import { useHead } from '@unhead/vue' 5 | import { describe, it } from 'vitest' 6 | import { useDom } from '../../../../unhead/test/fixtures' 7 | import { csrVueAppWithUnhead } from '../../util' 8 | 9 | describe('vue events', () => { 10 | it('basic', async () => { 11 | const dom = useDom() 12 | 13 | const head = csrVueAppWithUnhead(dom, () => { 14 | useHead({ 15 | bodyAttrs: { 16 | onresize: () => {}, 17 | }, 18 | }) 19 | 20 | useHead({ 21 | bodyAttrs: { 22 | onresize: () => {}, 23 | }, 24 | }) 25 | }) 26 | 27 | await renderDOMHead(head, { document: dom.window.document }) 28 | 29 | expect(dom.serialize()).toMatchInlineSnapshot(` 30 | " 31 | 32 | 33 |
hello world
" 34 | `) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /examples/vite-ssr-ts/src/entry-server.ts: -------------------------------------------------------------------------------- 1 | import typescriptLogo from './typescript.svg' 2 | import { createHead } from 'unhead/server' 3 | 4 | export function render(_url: string) { 5 | const html = ` 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |

Hello Vite!

14 |
15 | 16 |
17 |

18 | Click on the Vite logo to learn more 19 |

20 |
21 | ` 22 | const head = createHead() 23 | head.push({ 24 | title: 'Vite TS + Unhead', 25 | meta: [ 26 | { name: 'description', content: 'Vite SSR with TypeScript & Unhead' } 27 | ], 28 | }) 29 | return { html, head } 30 | } 31 | -------------------------------------------------------------------------------- /test/exports.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-expect-error untyped 2 | import yaml from 'js-yaml' 3 | import { x } from 'tinyexec' 4 | import { describe, expect, it } from 'vitest' 5 | import { getPackageExportsManifest } from 'vitest-package-exports' 6 | 7 | describe('exports-snapshot', async () => { 8 | const packages: { name: string, path: string, private?: boolean }[] = JSON.parse( 9 | await x('pnpm', ['ls', '--only-projects', '-r', '--json']).then(r => r.stdout), 10 | ) 11 | 12 | for (const pkg of packages) { 13 | if (pkg.private || pkg.path.includes('packages-aliased/') || pkg.path.includes('/angular')) 14 | continue 15 | it(`${pkg.name}`, async () => { 16 | const manifest = await getPackageExportsManifest({ 17 | importMode: 'package', 18 | cwd: pkg.path, 19 | }) 20 | // @ts-expect-error untyped 21 | await expect(yaml.dump(manifest.exports, { sortKeys: (a, b) => a.localeCompare(b) })) 22 | .toMatchFileSnapshot(`./exports/${pkg.name.split('/').pop()}.yaml`) 23 | }) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /packages/unhead/src/client/createHead.ts: -------------------------------------------------------------------------------- 1 | import type { CreateClientHeadOptions, ResolvableHead } from '../types' 2 | import { createUnhead } from '../unhead' 3 | import { renderDOMHead } from './renderDOMHead' 4 | 5 | export function createHead(options: CreateClientHeadOptions = {}) { 6 | const render = options.domOptions?.render || renderDOMHead 7 | options.document = options.document || (typeof window !== 'undefined' ? document : undefined) 8 | const initialPayload = options.document?.head.querySelector('script[id="unhead:payload"]')?.innerHTML || false 9 | // restore initial entry from payload (titleTemplate and templateParams) 10 | return createUnhead({ 11 | ...options, 12 | plugins: [ 13 | ...(options.plugins || []), 14 | { 15 | key: 'client', 16 | hooks: { 17 | 'entries:updated': render, 18 | }, 19 | }, 20 | ], 21 | init: [ 22 | initialPayload ? JSON.parse(initialPayload) : false, 23 | ...(options.init || []), 24 | ], 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /examples/vite-ssr-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-vue-typescript-starter", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "node server", 8 | "build": "npm run build:client && npm run build:server", 9 | "build:client": "vite build --outDir dist/client", 10 | "build:server": "vite build --ssr src/entry-server.ts --outDir dist/server", 11 | "preview": "cross-env NODE_ENV=production node server", 12 | "check": "vue-tsc" 13 | }, 14 | "dependencies": { 15 | "@unhead/vue": "workspace:*", 16 | "compression": "^1.8.1", 17 | "express": "^5.2.1", 18 | "rollup-plugin-visualizer": "^6.0.5", 19 | "sirv": "^3.0.2", 20 | "vite-bundle-analyzer": "^1.3.2", 21 | "vue": "^3.5.25" 22 | }, 23 | "devDependencies": { 24 | "@types/express": "^5.0.6", 25 | "@types/node": "^24.10.4", 26 | "@vitejs/plugin-vue": "^6.0.3", 27 | "cross-env": "^10.1.0", 28 | "typescript": "5.8.3", 29 | "vite": "^7.3.0", 30 | "vue-tsc": "^3.1.8" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/unhead/test/unit/server/eventHandlers.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | import { useHead } from '../../../src' 3 | import { renderSSRHead } from '../../../src/server' 4 | import { createServerHeadWithContext } from '../../util' 5 | 6 | describe('ssr event handlers', () => { 7 | it('basic', async () => { 8 | const head = createServerHeadWithContext() 9 | 10 | useHead(head, { 11 | script: [ 12 | { 13 | src: 'https://js.stripe.com/v3/', 14 | defer: true, 15 | // eslint-disable-next-line no-console 16 | onload: () => console.log('loaded stripe'), 17 | }, 18 | ], 19 | }) 20 | 21 | const ctx = await renderSSRHead(head) 22 | expect(ctx).toMatchInlineSnapshot(` 23 | { 24 | "bodyAttrs": "", 25 | "bodyTags": "", 26 | "bodyTagsOpen": "", 27 | "headTags": "", 28 | "htmlAttrs": "", 29 | } 30 | `) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /docs/0.nuxt/head/guides/1.core-concepts/1.components.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Components 3 | description: Use component to manage your head tags. 4 | navigation: 5 | title: 'Components' 6 | --- 7 | 8 | ## Introduction 9 | 10 | Nuxt exports several Vue components that can be used to manage your head tags. 11 | 12 | While it's recommended to use the `useHead()`{lang="ts"} composable as it offers a more flexible API with full TypeScript support, 13 | the Vue component may make more sense for your project. 14 | 15 | ## Usage 16 | 17 | For full usage instructions please refer to the [Nuxt documentation](https://nuxt.com/docs/getting-started/seo-meta#components). 18 | 19 | ```vue 20 | 23 | 24 |