{"id":15099266,"url":"https://github.com/rosenkolev/web-components-vite-app","last_synced_at":"2026-01-23T14:45:14.571Z","repository":{"id":245438853,"uuid":"818246690","full_name":"rosenkolev/web-components-vite-app","owner":"rosenkolev","description":"An web application using only native browser technology and vite build tool. HTML-first,template-agnostic,web 2.0.","archived":false,"fork":false,"pushed_at":"2024-06-25T15:14:37.000Z","size":86,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-13T20:03:46.313Z","etag":null,"topics":["custom-elements","vite","web-components","web2"],"latest_commit_sha":null,"homepage":"https://rosenkolev.github.io/web-components-vite-app/","language":"JavaScript","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/rosenkolev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2024-06-21T12:19:01.000Z","updated_at":"2025-01-23T10:04:19.000Z","dependencies_parsed_at":"2024-09-15T17:22:25.119Z","dependency_job_id":null,"html_url":"https://github.com/rosenkolev/web-components-vite-app","commit_stats":{"total_commits":10,"total_committers":2,"mean_commits":5.0,"dds":0.09999999999999998,"last_synced_commit":"7529601b17ddf26c4c762f7fa509611cc0da3ead"},"previous_names":["rosenkolev/web-components-vite-app"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rosenkolev/web-components-vite-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rosenkolev%2Fweb-components-vite-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rosenkolev%2Fweb-components-vite-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rosenkolev%2Fweb-components-vite-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rosenkolev%2Fweb-components-vite-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rosenkolev","download_url":"https://codeload.github.com/rosenkolev/web-components-vite-app/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rosenkolev%2Fweb-components-vite-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28694457,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-23T14:15:13.573Z","status":"ssl_error","status_checked_at":"2026-01-23T14:09:05.534Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["custom-elements","vite","web-components","web2"],"created_at":"2024-09-25T17:08:57.787Z","updated_at":"2026-01-23T14:45:14.553Z","avatar_url":"https://github.com/rosenkolev.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Web Components Application\n\nAn web application using only browser native technologies and no frameworks.\n\n`fast` `web components` `browser native` `HTML template based` `vite`\n\nThis application demonstrate that the bowser's have evolved to support complex application without external dependecies.\n\n# Component\n\nThe entire source code a component is in a single file (e.g. `home-page.component.html`).\n\n```html\n\u003ctemplate id=\"home-page\"\u003e\n  \u003ch2\u003eHome Page\u003c/h2\u003e\n\u003c/template\u003e\n\u003cscript\u003e\n  class HomePage extends CustomElement {\n    static component = Object.freeze({\n      selector: 'home-page'\n    });\n  };\n  HomePage.componentInit();\n\u003c/script\u003e\n```\n\nUse data attributes to bind and attach events.\n\n__Example:__\n- onclick event calls method onClick: `\u003cbutton data-on=\"click:onClick('RED')\"\u003eRED\u003c/button\u003e`\n- bind color property to InnerText: `\u003cspan data-bind=\"color\"\u003e\u003c/span\u003e`\n- bind color property to css color: `\u003cspan data-css=\"color:color\"\u003e\u003c/span\u003e`\n- bind color property to attrbiute, with default value BLACK: `\u003coutput-color color=\"BLACK\" data-bind-color=\"color\" /\u003e`\n\n```html\n\u003ctemplate id=\"test-page\"\u003e\n  \u003cbutton data-on=\"click:onClick('RED')\"\u003eRED\u003c/button\u003e\n  \u003cbutton data-on=\"click:onClick('BLUE')\"\u003eBLUE\u003c/button\u003e\n  \u003coutput-color color=\"BLACK\" data-bind-color=\"color\" data-on=\"colorReset:onReset($event)\"\u003e\u003c/output-color\u003e\n\u003c/template\u003e\n\u003cscript\u003e\n  class TestPage extends CustomElement {\n    static component = Object.freeze({\n      selector: 'test-page'\n    });\n    \n    onClick(color) {\n      this.state.setState({ color });\n    }\n\n    onReset(color) {\n      this.state.setState({ color });\n    }\n  };\n  TestPage.componentInit();\n\u003c/script\u003e\n```\n\n# index.html\n\nAll html files will be bundled into the `index.html` file.\n\n```html\n\u003c!-- index.html--\u003e\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"utf-8\" /\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /\u003e\n    \u003ctitle\u003eSPA WC Vite\u003c/title\u003e\n    \u003cscript\u003ewindow.DEBUG_CUSTOM_ELEMENTS=true\u003c/script\u003e\n    \u003c!-- core.js will be injected here--\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003c!-- components will be injected here --\u003e\n    \u003capp-root\u003e\u003c/app-root\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n# core.js\n\nThe `core.js` creates the `CustomElement` class that provide abstraction for easier working with `Web Components`.\n\n```javascript\nwindow.core = {\n  isObject: function (obj) { /** check is object. */ },\n  toArray: function (obj) { /** covert list to array */ },\n  registerComponent: function (selector, element, dependencies, extend) {\n    /** abstraction that first register dependencies, than register the component */\n    window.customElements.define(selector, element, { extends: extend });\n  },\n  /** other methods */\n};\n\nclass StateManager {\n  constructor(onStateChanged) {\n  }\n\n  setState(newState) {\n  }\n\n  set(key, value) {\n  }\n}\n\nclass CustomElement extends HTMLElement {\n  static componentInit() {\n    this.prototype.template = document.getElementById(this.component.templateId || this.component.selector);\n    core.registerComponent(/** */);\n  }\n\n  state = new StateManager(() =\u003e {\n    /* update all [data-bind], [data-bind-attributeName], [data-css], etc. */\n  });\n\n  constructor() {\n    this.attachShadow({ mode: \"open\" });\n    this.shadowRoot.appendChild(this.template.content.cloneNode(true));\n\n    // attach to all event listeners specified by [data-on]\n    this.shadowRoot.querySelectorAll(\"[data-on]\").forEach((node) =\u003e {\n      node.addEventListener(/** */);\n    });\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n    if (oldValue !== newValue) {\n      this.state.set(name, newValue);\n    }\n  }\n}\n```\n\n# Vite\n\nVite is used only for bundeling files. In reality `vite` is not needed and can be done with a simple NodeJs script.\n\n`package.json`\n```json\n{\n  \"scripts\": {\n    \"start\": \"vite\",\n    \"build\": \"vite build\"\n  },\n  \"devDependencies\": {\n    \"vite\": \"5.1.7\"\n  }\n}\n```\n\n```javascript\n// vite.config.js\n/** @param options {{ path: string, at: 'head' | 'body' | 'body-pre' }} */\nfunction injectFilesInIndexHtml(options) {\n  return {\n    name: 'inject-files-in-index-html',\n    transformIndexHtml: {\n      transform(html) {\n        /** ... */\n        const isDirectory = options.path...;\n        const files = isDirectory ? fs.readdirSync(basePath) : [options.path];\n        const filesContent = files.map((file) =\u003e {\n          const pathToFile = path.resolve(basePath, file);\n          const txt = fs.readFileSync(pathToFile);\n          switch (file.split('.').pop()) {\n            /** ... */\n            case 'html':\n              return txt.toString();\n          }\n        });\n        const data = filesContent.join('');\n        switch (options.at) {\n          case 'head':\n            return html.replace('\u003c/head\u003e', `${data}\\n\u003c/head\u003e`);\n          case 'body':\n            return html.replace('\u003c/body\u003e', `${data}\\n\u003c/body\u003e`);\n          case 'body-pre':\n            return html.replace('\u003cbody\u003e', `\u003cbody\u003e\\n${data}`);\n        }\n      },\n    },\n  };\n}\n\nexport default defineConfig({\n  plugins: [\n    injectFilesInIndexHtml({ path: 'core.js', at: 'head' }),\n    injectFilesInIndexHtml({ path: 'components/', at: 'body-pre' }),\n  ],\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frosenkolev%2Fweb-components-vite-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frosenkolev%2Fweb-components-vite-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frosenkolev%2Fweb-components-vite-app/lists"}