{"id":16217451,"url":"https://github.com/sukima/fancy-pants","last_synced_at":"2026-02-17T09:40:17.470Z","repository":{"id":46059045,"uuid":"304401858","full_name":"sukima/fancy-pants","owner":"sukima","description":"A performant JavaScript Micro-lib for Custom Elements with reactive updates via dirty tracking","archived":false,"fork":false,"pushed_at":"2023-05-10T15:20:19.000Z","size":450,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-28T17:56:53.043Z","etag":null,"topics":["custom-elements","customelements","javascript","javascript-library"],"latest_commit_sha":null,"homepage":"https://fancy-pants.js.org","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/sukima.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2020-10-15T17:33:57.000Z","updated_at":"2022-12-09T10:46:12.000Z","dependencies_parsed_at":"2024-10-27T20:30:57.098Z","dependency_job_id":"4b8b0be4-2e03-4939-9d15-c58e20cf60f9","html_url":"https://github.com/sukima/fancy-pants","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sukima%2Ffancy-pants","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sukima%2Ffancy-pants/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sukima%2Ffancy-pants/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sukima%2Ffancy-pants/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sukima","download_url":"https://codeload.github.com/sukima/fancy-pants/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243982182,"owners_count":20378605,"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","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","customelements","javascript","javascript-library"],"created_at":"2024-10-10T11:45:09.568Z","updated_at":"2026-02-17T09:40:12.426Z","avatar_url":"https://github.com/sukima.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Give Up GitHub\n\nThis project has given up GitHub.  ([See Software Freedom Conservancy's *Give\nUp  GitHub* site for details](https://GiveUpGitHub.org).)\n\nYou can now find this project at\n[SourceHut](https://sr.ht/~sukima/fancy-pants.js/) instead.\n\nAny use of this project's code by GitHub Copilot, past or present, is done\nwithout our permission.  We do not consent to GitHub's use of this project's\ncode in Copilot.\n\nJoin us; you can [give up GitHub](https://GiveUpGitHub.org) too!\n\n![Logo of the GiveUpGitHub campaign](https://sfconservancy.org/img/GiveUpGitHub.png)\n\n----\n\n\u003cp style=\"display: flex; flex-direction: column; align-items: center;\"\u003e\n  \u003cimg src=\"https://fancy-pants.js.org/images/logo.png\" width=\"128\" style=\"border-radius: 24px;\" alt=\"FancyPants logo\"\u003e\n  \u003cspan style=\"font-family: monospace; margin-top: 0.5rem;\"\u003eVersion 3.0.1\u003c/span\u003e\n\u003c/p\u003e\n\nHave you ever been working on a simple JavaScript project like a bookmarklet or\ntiny static site and thought to yourself if only you could have a backbone like\nmicro-lib to make writing **custom elements** easier?\n\nNo?! … oh guess it was just me. Well it is done now might as well show it.\n\nThis *micro-lib* is an attempt to introduce some very modern ECMAScript ideas\nand allow you to make small and yet *performant* custom elements.\n\n```javascript\nimport Component from 'https://fancy-pants.js.org/min/component.js';\nimport { tracked } from 'https://fancy-pants.js.org/min/tracking.js';\n\nclass MyCustomElement extends Component {\n\n  // Whenever this value changes it will schedule a render cycle\n  count = tracked(0);\n\n  // Every second update the count\n  connectedCallback() {\n    super.connectedCallback();\n    this.timer = setInterval(() =\u003e this.count++, 1000);\n  }\n\n  // Be nice when the component is disconnected\n  disconnectedCallback() {\n    super.disconnectedCallback();\n    clearInterval(this.timer);\n  }\n\n  // Every render cycle update the Shadow DOM\n  render() {\n    this.shadow.querySelector('output').value = this.count;\n  }\n\n  // Use this innerHTML to create the Shadow DOM\n  static get shadow() {\n    return `\u003cspan\u003eCounter: \u003coutput\u003e\u003c/output\u003e\u003c/span\u003e`;\n  }\n\n}\n\nMyCustomElement.register();\n```\n\n[demo](https://fancy-pants.js.org/tutorial-example6.html)\n\n## Locations\n\n#### Minified\n\n* `import Component from 'https://fancy-pants.js.org/min/component.js';`\n* `import { tracked, activateTracking } from 'https://fancy-pants.js.org/min/tracking.js';`\n\n#### Unminified\n\n* `import Component from 'https://fancy-pants.js.org/component.js';`\n* `import { tracked, activateTracking } from 'https://fancy-pants.js.org/tracking.js';`\n\n## Why use this\n\n* Your project is large enough to benefit from using custom elements but not\n  big enough to need templates, DOM libs, or a VirtualDOM™\n* You want an easy way to memoize the render functions\n* You just want a render function to be called when it needs to be called\n* You want to use VanillaJS™ DOM API to update content but you don't want to\n  have to roll your own guards to prevent unoptimized DOM mutations (i.e.\n  Backbone™)\n\nBasically if your project was large enough to warrant jQuery then this might be\nworth it. This micro-lib fits into the same needs spectrum as Backbone™.\n\n## Why not use this\n\n* You are serious about writing an actual application\n* You want to use a VirtualDOM™\n* Your project is larger then a single HTML file\n\nIf your project needs more then just jQuery then this is not for you.\n\n## Technical explanation\n\nThis lib has three basic parts tracking, rendering, and component.\n\n### Tracking\n\nThis concept is explained in extreme detail (and where I stole this idea from)\nin the blog post \"[How Autotracking Works][1]\" by [Chris Garrett][2].\n\n[1]: https://www.pzuraq.com/how-autotracking-works/\n[2]: https://www.pzuraq.com/author/pzuraq/\n\nThe condensed version is that when we consume a tracked property it stores\nincrements a counter specific to that property (*tag*). We then allow functions\nto be memoized which run only when the tags it knows about have changed.\n\nBecause this is a dense topic I'll paraphrase by running through an example.\n\nA function executes (I will call this `render()`) it *consumes* a tracked\nproperty. When this happens the system records that the `render()` depends on\nthat tag.\n\nThe next time `render()` is called it checks the list of tags for itself to see\nif any of the tags revision counter is larger then the last time the memoized\nfunction ran. If so then it executes recording consumed tags. If not then it\nknows nothing has changed and does a no-op.\n\nIn the end this means that when we dirty a tag by incrementing its revision\ncounter the next time the `render()` happens it is ready to execute again.\n\nThis affords us the ability to call the `render()` function without worrying\nthat it is executing when nothing has changed. This setup also means that we do\nnot need to declare the dependencies because the very act of consuming a value\nwill register it as a dependency. In short executing the memoized function will\n**auto-track** its dependencies.\n\n### Renderer\n\nThe rendering module is responsible for collecting functions and scheduling\nwhen to execute those functions. It uses a microtask to schedule the next run.\nIf all the functions it attempts to run on each render cycle are memoized it is\nquite performant.\n\nIt taps into the tracking system simply to schedule a render cycle when\na tracked property is dirtied.\n\n### Component\n\nA simple custom element implementation which is able to create a shadow DOM\nfrom a template and defines a render function that gets memoized.\n\n## Documentation\n\nEach part is split into three files. Import the ones as you need them. Here is\na basic overview of how this system works. There is also\n[detailed API docs][api]\n\nPlease check out the [examples][] (view source) as they do not use any\nminification and are easily digestible.\n\n### Component\n\nFirst create an HTML template. This can either be a `\u003ctemplate\u003e` element or\na string. For now we will presume a `\u003ctemplate\u003e` element.\n\n```html\n\u003ctemplate id=\"my-component\"\u003e\n  \u003coutput\u003e\u003c/output\u003e\n\u003c/template\u003e\n```\n\nTake note it has the ID of `my-component` which will match to the component's\nname `MyComponent`.\n\n```javascript\nimport Component from 'fancy-pants/component.js';\n\nclass MyComponent extends Component {\n}\n\nMyComponent.register();\n```\n\nthe `register()` will call `customElements.define(…)` and will infer the\ntagname based on dasherizing the class name — `MyComponent` will define a\n`\u003cmy-component\u003e\u003c/my-component\u003e` element.\n\nThis can be overridden by providing a static tagName.\n\n```javascript\nclass MyComponent extends Component {\n  static get tagName() {\n    return 'some-other-dashed-name';\n  }\n}\nMyComponent.register();\n```\n\nBy default it will look for a `\u003ctemplate\u003e` in the dom with an ID of the\ntagname. This can be overridden by passing in a selector string.\n\n```javascript\nMyComponent.register('#a-different-template-id');\n```\n\nDynamic content can be updated with the `render()` method. This methods is\nautomatically memoized for you. Any tracked/auto-tracked properties consumed\nwill contribute to this method to being executed.\n\nAll other custom element methods are available just be sure to call the\nappropriate `super.*()`.\n\nAny observed attributes will also be auto-tracked. Use `this.getAttribute()`\nand `this.setAttribute()` as normal.\n\n```javascript\nclass MyComponent extends Component {\n  static get observedAttributes() {\n    return ['foo'];\n  }\n}\n```\n\nThe Shadow DOM for the component can be accessed via `this.shadow`.\n\n### Renderer\n\nThe rendering module is not exclusive to components. You can add any function\nto the renderer. Even memoized functions if you wish.\n\n```javascript\nimport { registerRenderer, scheduleRender } from 'fancy-pants/rendering.js';\n\nlet shouldRender = true;\n\nregisterRenderer(() =\u003e {\n  if (!shouldRender) {\n    console.log('noop cycle');\n  } else {\n    console.log('render cycle');\n  }\n  shouldRender = false;\n});\n```\n\nRendering is async within a microtask cycle. Calling `scheduleRender()` will\nonly schedule the render cycle therefor multiple calls will only result in one\npass over the registered functions.\n\n```javascript\nscheduleRender();                  // =\u003e render cycle\nsetTimeout(scheduleRender, 1000);  // =\u003e noop cycle\nsetTimeout(() =\u003e shouldRender = true, 2000);\nsetTimeout(scheduleRender, 3000);  // =\u003e render cycle\n```\n\nIf needed you can also remove a renderer function with `unregisterRenderer(…)`.\n\n### Tracking\n\nAny object can have tracked properties. Until [decorators][3] are available\nthere is an initialization step to activate tracked properties.\n\n[3]: https://github.com/tc39/proposal-decorators\n\n```javascript\nimport { tracked, activateTracking } from 'fancy-pants/tracking.js';\n```\n\nThe `tracked()` function returns a `Tracked` object.\n\nCalling `activateTracking()` on an object will convert all `Tracked`\nproperties to getter and setters hooked into the auto-tracking system.\n\nThis allows the following syntax to work with `Component`s.\n\n```javascript\nlet myObject = {\n  foo: tracked()\n};\nactivateTracking(myObject);\n```\n\nIt also returns the same object to a more condenced syntax.\n\n```javascript\nlet myObject = activateTracking({\n  foo: tracked()\n});\n```\n\nAnytime foo is assigned it will mark the property as dirty and schedule\na render cycle.\n\n```javascript\nmyObject.foo = 'bar';\n```\n\nTo create a function that only runs when any of its auto-tracked dependencies\nchanges use `memoizeFunction()`.\n\n```javascript\nimport { memoizeFunction } from 'fancy-pants/tracking.js';\nlet optimised = memoizeFunction(() =\u003e { … });\n```\n\nFor more specific usage see the many [Examples][examples].\n\n[examples]: https://fancy-pants.js.org/tutorial-examples.html\n[api]: https://fancy-pants.js.org/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsukima%2Ffancy-pants","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsukima%2Ffancy-pants","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsukima%2Ffancy-pants/lists"}