{"id":33054394,"url":"https://github.com/Wildhoney/Switzerland","last_synced_at":"2025-11-15T10:01:27.266Z","repository":{"id":38271906,"uuid":"68638095","full_name":"Wildhoney/Switzerland","owner":"Wildhoney","description":"🇨🇭Switzerland takes a functional approach to Web Components by applying middleware to your components. Supports Redux, attribute mutations, CSS variables, React-esque setState/state, etc… out-of-the-box, along with Shadow DOM for style encapsulation and Custom Elements for interoperability.","archived":false,"fork":false,"pushed_at":"2024-09-27T00:25:56.000Z","size":41027,"stargazers_count":268,"open_issues_count":2,"forks_count":15,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-05-19T13:08:32.354Z","etag":null,"topics":["component","component-architecture","components","css-variables","custom-elements","functional","immutable","shadow-dom","virtual-dom","webcomponents"],"latest_commit_sha":null,"homepage":"https://switzerland.herokuapp.com/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Wildhoney.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-09-19T19:18:37.000Z","updated_at":"2024-07-01T14:36:13.000Z","dependencies_parsed_at":"2024-09-24T16:02:06.985Z","dependency_job_id":"906f69bc-52b4-4899-966c-15f30ecb205f","html_url":"https://github.com/Wildhoney/Switzerland","commit_stats":{"total_commits":2103,"total_committers":3,"mean_commits":701.0,"dds":0.0442225392296719,"last_synced_commit":"32a5991cec038f2727d576bbde824f9e17e73308"},"previous_names":[],"tags_count":166,"template":false,"template_full_name":null,"purl":"pkg:github/Wildhoney/Switzerland","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wildhoney%2FSwitzerland","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wildhoney%2FSwitzerland/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wildhoney%2FSwitzerland/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wildhoney%2FSwitzerland/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Wildhoney","download_url":"https://codeload.github.com/Wildhoney/Switzerland/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wildhoney%2FSwitzerland/sbom","scorecard":{"id":151320,"data":{"date":"2025-08-11","repo":{"name":"github.com/Wildhoney/Switzerland","commit":"32a5991cec038f2727d576bbde824f9e17e73308"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"41 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-wf5p-g6vw-rhxx","Warn: Project is vulnerable to: GHSA-8hc4-vh64-cxmj","Warn: Project is vulnerable to: GHSA-jr5f-v2jv-69x6","Warn: Project is vulnerable to: GHSA-qwcr-r2fm-qrc7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-pxg6-pf52-xh8x","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-ghr5-ch3p-vcr6","Warn: Project is vulnerable to: GHSA-4gmj-3p3h-gm8h","Warn: Project is vulnerable to: GHSA-rv95-896h-c2vc","Warn: Project is vulnerable to: GHSA-qw6h-vgh9-j6wx","Warn: Project is vulnerable to: GHSA-jchw-25xp-jwwc","Warn: Project is vulnerable to: GHSA-cxjh-pqwp-8mfp","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-78xj-cgh5-2h22","Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55","Warn: Project is vulnerable to: GHSA-hcrg-fc28-fcg5","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-rhx6-c78j-4q9w","Warn: Project is vulnerable to: GHSA-h755-8qp9-cq85","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-m6fv-jmcg-4jfg","Warn: Project is vulnerable to: GHSA-76p7-773f-r4q5","Warn: Project is vulnerable to: GHSA-cm22-4g7w-348p","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-wqq4-5wpv-mx2g","Warn: Project is vulnerable to: GHSA-3787-6prv-h9w3","Warn: Project is vulnerable to: GHSA-9qxr-qj54-h672","Warn: Project is vulnerable to: GHSA-m4v8-wqvr-p9f7","Warn: Project is vulnerable to: GHSA-c76h-2ccp-4975","Warn: Project is vulnerable to: GHSA-cxrh-j4jr-qwg3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-16T10:45:17.018Z","repository_id":38271906,"created_at":"2025-08-16T10:45:17.019Z","updated_at":"2025-08-16T10:45:17.019Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":284538091,"owners_count":27022334,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-15T02:00:06.050Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["component","component-architecture","components","css-variables","custom-elements","functional","immutable","shadow-dom","virtual-dom","webcomponents"],"created_at":"2025-11-14T04:00:21.582Z","updated_at":"2025-11-15T10:01:27.259Z","avatar_url":"https://github.com/Wildhoney.png","language":"TypeScript","funding_links":[],"categories":["Libraries and Frameworks"],"sub_categories":["Switzerland"],"readme":"\u003cimg src=\"https://raw.githubusercontent.com/Wildhoney/Switzerland/master/media/logo.png\" alt=\"Switzerland\" width=\"300\" /\u003e\n\n\u003e Switzerland takes a functional approach to web components using Preact with shadow DOM for style encapsulation, custom elements for interoperability and server-side rendering for universality.\n\n![npm](http://img.shields.io/npm/v/switzerland.svg?style=for-the-badge)\n\u0026nbsp;\n![License MIT](http://img.shields.io/badge/license-mit-lightgrey.svg?style=for-the-badge)\n\u0026nbsp;\n[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=for-the-badge)](https://github.com/prettier/prettier)\n\n**yarn**: `yarn add switzerland`\n\u003cbr /\u003e\n**npm**: `npm install switzerland`\n\u003cbr /\u003e\n**cdn**: [`https://cdn.jsdelivr.net/npm/switzerland@latest/dist/index.client.js`](https://cdn.jsdelivr.net/npm/switzerland@latest/dist/index.client.js)\n\n![Screenshot](https://raw.githubusercontent.com/Wildhoney/Switzerland/master/media/screenshot.png)\n\n---\n\n## Contents\n\n1. [Getting Started](#getting-started)\n1. [Import Maps](#import-maps)\n1. [Managing State](#managing-state)\n1. [Applying Styles](#applying-styles)\n1. [Data Fetching](#data-fetching)\n1. [Environment Context](#environment-context)\n1. [Extending Elements](#extending-elements)\n\n## Getting Started\n\nSwitzerland optionally begins with server-side rendering with hydration on the client thanks to [declarative shadow DOM](https://developer.chrome.com/en/articles/declarative-shadow-dom/) \u0026mdash; with our components looking very familiar due to our usage of [Preact](https://preactjs.com/).\n\n```tsx\nimport { create } from \"switzerland\";\n\nexport default create(\"x-countries\", () =\u003e {\n  return (\n    \u003cul\u003e\n      \u003cli\u003eJapan\u003c/li\u003e\n      \u003cli\u003eCroatia\u003c/li\u003e\n      \u003cli\u003eSingapore\u003c/li\u003e\n    \u003c/ul\u003e\n  );\n});\n```\n\nOnce we've defined our `x-countries` component we are able to both render it on the server and hydrate it on the client as a standard `\u003cx-countries /\u003e` DOM element. We can then take a step further and allow our countries to be passed as a HTML attribute on the DOM node using `\u003cx-countries list=\"Japan,Croatia,Singapore\"\u003e`.\n\n```tsx\nimport { create, type, use } from \"switzerland\";\n\ntype Attrs = {\n  countries: string[];\n};\n\nexport default create\u003cAttrs\u003e(\"x-countries\", () =\u003e {\n  const attrs = use.attrs({\n    countries: type.Array(type.String),\n  });\n\n  return (\n    \u003cul\u003e\n      {attrs.countries.map((country) =\u003e (\n        \u003cli key={country}\u003e{country}\u003c/li\u003e\n      ))}\n    \u003c/ul\u003e\n  );\n});\n```\n\nUsing our component from within a Node environment requires us to use the exported asynchronous `render` function; we can specify an optional second parameter to the function, however our component currently doesn't perform data fetching or media inclusion and so is unnecessary.\n\n```tsx\nimport { render } from \"switzerland\";\n\napp.get(\"/\", async (_, response) =\u003e {\n  const html = await render(\u003cCountries list=\"Japan,Croatia,Singapore\" /\u003e);\n  response.send(html);\n});\n```\n\nAs our components are self-contained modules, any changes to their attributes will initiate a re-render of the component's tree \u0026ndash; regardless of whether those attributes change from inside another component or through vanilla DOM accessors.\n\n```js\nconst node = document.querySelector(\"x-countries\");\nnode.attributes.values = `${node.attributes.values},Ukraine,Maldives`;\n```\n\n## Import Maps\n\nSwitzerland doesn't _need_ to be compiled except for optional TypeScript and JSX transpiling because it uses native ES modules in the browser and Node 16+ on the server. It achieves this by using `node_modules` when rendering on the server using named imports, and in the browser it uses import maps to resolve those named imports to CDN URLs which offers enhanced caching. We provide a utility for the server to automatically generate the import maps for your application based on its dependencies.\n\n```tsx\nimport fs from \"node:fs\";\nimport { imports, render } from \"switzerland\";\n\napp.get(\"/\", async (_, response) =\u003e {\n  const html = await render(\u003cCountries list=\"Japan,Croatia,Singapore\" /\u003e);\n  const importMap = await imports({ path: path.resolve(\"../app/src\") });\n\n  response.send(`\n        \u003chead\u003e\n            \u003cscript type=\"importmap\"\u003e\n                ${importMap}\n            \u003c/script\u003e\n        \u003c/head\u003e\n\n        \u003cbody\u003e\n            ${html}\n        \u003c/body\u003e\n    `);\n});\n```\n\nYou need to give the `imports` function the base path of your Switzerland components. It will then traverse the files [using `ts-morph`](https://github.com/dsherret/ts-morph) which provides an abstract syntax tree (AST) of your code and allows us to pick out the external dependencies; it then iteratively matches each of those dependencies it finds to the versions installed by your chosen package manager. We [use the `@jspm/generator` package](https://github.com/jspm/generator) to resolve the dependencies to `jspm.io` URLs by default \u0026ndash; however you may also pass the `provider` option to change the provider.\n\nOnce you have the import map configured, when rendering Switzerland components in the browser it will use those CDN URLs and prevent any need to package up dependencies via a bundler. You can focus purely on the simple task of transpiling TypeScript and JSX into native ES modules using nothing more than `tsc` \u0026ndash; although if you want to minify you may need to add [Terser](https://www.npmjs.com/package/@rollup/plugin-terser).\n\n```json\n{\n  \"include\": [\"src\"],\n\n  \"compilerOptions\": {\n    \"rootDir\": \"./src\",\n    \"outDir\": \"./dist\",\n\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"nodenext\",\n    \"esModuleInterop\": true,\n    \"target\": \"esnext\",\n    \"strict\": true,\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"preact\",\n    \"declaration\": true\n  }\n}\n```\n\n## Managing State\n\nSince we use [Preact](https://preactjs.com/) to render Switzerland's components the API should already be familiar. For ease of use we re-export Preact's hook functions but you may also use them directly from Preact.\n\n```tsx\nimport { create, use } from \"switzerland\";\n\nexport default create(\"x-countries\", () =\u003e {\n  const [countries, setCountries] = use.state([\n    \"Japan\",\n    \"Croatia\",\n    \"Singapore\",\n  ]);\n\n  return (\n    \u003cul\u003e\n      {countries.map((country) =\u003e (\n        \u003cli key={country}\u003e{country}\u003c/li\u003e\n      ))}\n    \u003c/ul\u003e\n  );\n});\n```\n\n## Applying Styles\n\nStyles within a shadow boundary allow for encapsulation which means we can use regular CSS documents scoped to our component's tree. We can attach our stylesheets to our component by using a regular `link` node, although Switzerland provides a `node` utility for `StyleSheet` and `Variables` \u0026mdash; the latter applies custom variables to your component tree allowing CSS to access those JavaScript variables. We use the `use.path` hook to resolve media \u0026mdash; CSS documents, images, etc... \u0026mdash; relative to our component.\n\n```tsx\nimport { create, node, use } from \"switzerland\";\n\nexport default create(\"x-countries\", () =\u003e {\n  const path = use.path(import.meta.url);\n  const [countries, setCountries] = use.state([\n    \"Japan\",\n    \"Croatia\",\n    \"Singapore\",\n  ]);\n\n  return (\n    \u003c\u003e\n      \u003cul\u003e\n        {countries.map((country) =\u003e (\n          \u003cli key={country}\u003e{country}\u003c/li\u003e\n        ))}\n      \u003c/ul\u003e\n\n      \u003cnode.Variables\n        backgroundColour={countries.length === 0 ? \"#8ECCD4\" : \"#FBDEA3\"}\n      /\u003e\n\n      \u003cnode.StyleSheet href={path(\"./styles/default.css\")} /\u003e\n      \u003cnode.StyleSheet\n        href={path(\"./styles/mobile.css\")}\n        media=\"(max-width: 768px)\"\n      /\u003e\n      \u003cnode.StyleSheet href={path(\"./styles/print.css\")} media=\"print\" /\u003e\n    \u003c/\u003e\n  );\n});\n```\n\nWe can then be quite loose when applying those styles to our component knowing that the shadow boundary will prevent styles from leaking out \u0026mdash; we use a CSS variable to apply a conditional background colour with a fallback.\n\n```css\n:host {\n  box-shadow: 0 0 5px #e8c5b0;\n}\n\nul {\n  background-color: var(--background-color, \"#E39AC7\");\n}\n```\n\n## Data Fetching\n\nSince Switzerland allows for server-side rendering by default a `use.loader` utility hook is provided for fetching data \u0026ndash; although you may choose to use **any** other third-party fetching utility or a simple `useEffect` and that is fine too. Using `loader` hook allows for fetching data server-side and then preventing a re-fetch on the client; we achieve this by rendering our components twice in the asynchronous `render` function we covered earlier and then including the serialised data in the tree.\n\n```tsx\nimport { create, use } from \"switzerland\";\n\nexport default create(\"x-countries\", () =\u003e {\n  const { data, loading, error } = use.loader(\n    \"x-countries\",\n    () =\u003e\n      fetch(\"https://www.example.org/countries\").then((response) =\u003e\n        response.json()\n      ),\n    null\n  );\n\n  return loading ? (\n    \u003cp\u003eLoading\u0026hellip;\u003c/p\u003e\n  ) : (\n    \u003cul\u003e\n      {data.map((country) =\u003e (\n        \u003cli key={country}\u003e{country}\u003c/li\u003e\n      ))}\n    \u003c/ul\u003e\n  );\n});\n```\n\nWe provide a unique ID to the `loader` function which _should_ identify the individual request to prevent duplicates and to allow for reconciliation on the client. With the dependencies argument in third position we can re-invoke the `loader` client-side whenever a parameter changes; in our case we probably don't want to re-fetch given nothing changes but if fetching by a given list we might expect the current list of countries to be provided as dependencies.\n\n## Environment Context\n\nProviding the environment context requires some user configuration on the server side \u0026mdash; the `render` function takes an optional second parameter which allows us to specify both the root directory on the web-server and _optionally_ the domain we're running the server on.\n\n```ts\nimport App from \"./App\";\nimport { preload, render } from \"switzerland\";\n\nconst vendor = path.resolve(\"..\");\n\nconst options = {\n  path: process.env[\"DOMAIN\"]\n    ? `https://${process.env[\"DOMAIN\"]}/client`\n    : \"http://localhost:3000/client\",\n  root: vendor,\n};\n\napp.get(\"/\", async (_, response) =\u003e {\n  const html = await render(\n    \u003cCountries list=\"Japan,Croatia,Singapore\" /\u003e,\n    options\n  );\n  response.send(html);\n});\n```\n\nWe use these options to resolve media using the `use.path` hook with `import.meta.url` relative to the component \u0026ndash; on the server we need to know the root directory in order to achieve this. On the client-side however it's slightly more simple since we know the root based on each components' path. Likewise with the `path` option where we specify the domain the web-server is running on; we use this to provide absolute paths to media so that components can be utilised in third-party applications, however since it's optional we use the aforementioned `root` to specify a relative path which is perfectly fine when we're only using our components on our own web-server.\n\nUsing the `use.env` hook we can access these defined parameters as well as a few additional items.\n\n```tsx\nimport { create, use } from \"switzerland\";\n\nexport default create(\"x-countries\", () =\u003e {\n  const { path, root, node, isServer, isClient } = use.env();\n\n  return (\n    \u003c\u003e\n      {node \u0026\u0026 \u003ch1\u003eHey {node.nodeName}!\u003c/h1\u003e}\n\n      \u003cp\u003eServer: {isServer}\u003c/p\u003e\n      \u003cp\u003eClient: {isClient}\u003c/p\u003e\n\n      \u003cul\u003e\n        \u003cli\u003eJapan\u003c/li\u003e\n        \u003cli\u003eCroatia\u003c/li\u003e\n        \u003cli\u003eSingapore\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/\u003e\n  );\n});\n```\n\n## Extending Elements\n\nYou may also extend native HTML elements using the `x-hello:button` syntax in the `create` function \u0026ndash; it'll create a `x-hello` custom element that extends from the `button` constructor allowing you to add your own twist to it.\n\n```tsx\nimport { create, use } from \"switzerland\";\n\nexport default create(\"x-hello:button\", () =\u003e {\n  const handleClick = use.callback((): void =\u003e console.log(\"Hello!\"), []);\n\n  return \u003cbutton onClick={handleClick}\u003eSay Hello!\u003c/button\u003e;\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWildhoney%2FSwitzerland","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FWildhoney%2FSwitzerland","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWildhoney%2FSwitzerland/lists"}