{"id":16281084,"url":"https://github.com/schorfes/pacto","last_synced_at":"2025-03-20T01:33:48.775Z","repository":{"id":29950074,"uuid":"123288827","full_name":"schorfES/pacto","owner":"schorfES","description":"A lightweight framework for non SPA websites.","archived":false,"fork":false,"pushed_at":"2024-10-01T14:36:55.000Z","size":2962,"stargazers_count":6,"open_issues_count":6,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-10-17T21:29:22.558Z","etag":null,"topics":["esnext","fast","framework","frontend","javascript","lightweight"],"latest_commit_sha":null,"homepage":"","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/schorfES.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":"2018-02-28T13:29:07.000Z","updated_at":"2024-08-15T08:55:38.000Z","dependencies_parsed_at":"2024-04-22T10:02:50.848Z","dependency_job_id":"03dc3168-5cd6-4c34-b669-10ab93042c79","html_url":"https://github.com/schorfES/pacto","commit_stats":{"total_commits":494,"total_committers":7,"mean_commits":70.57142857142857,"dds":"0.48785425101214575","last_synced_commit":"85ca505f684f574f2e60b3f0dbdeaff02431fcbb"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schorfES%2Fpacto","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schorfES%2Fpacto/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schorfES%2Fpacto/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/schorfES%2Fpacto/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/schorfES","download_url":"https://codeload.github.com/schorfES/pacto/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221733334,"owners_count":16871850,"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":["esnext","fast","framework","frontend","javascript","lightweight"],"created_at":"2024-10-10T19:04:54.759Z","updated_at":"2024-10-27T21:06:55.627Z","avatar_url":"https://github.com/schorfES.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pacto\n\n[![CI Status](https://github.com/schorfES/pacto/actions/workflows/ci.yml/badge.svg)](https://github.com/schorfES/pacto/actions)\n[![Coverage Status on Codecov](https://codecov.io/gh/schorfES/pacto/branch/main/graph/badge.svg)](https://codecov.io/gh/schorfES/pacto)\n[![Known Vulnerabilities](https://snyk.io/test/github/schorfES/pacto/badge.svg)](https://snyk.io/test/github/schorfES/pacto)\n[![Minified gzipped size](https://badgen.net/bundlephobia/minzip/pacto)](https://bundlephobia.com/result?p=pacto)\n\nA lightweight framework for non SPA websites.\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n- [Why?](#why)\n- [The Concepts](#the-concepts)\n- [Installation](#installation)\n- [Requirements](#requirements)\n  - [Support NodeList.prototype.forEach](#support-nodelistprototypeforeach)\n- [Documentation](#documentation)\n  - [Context](#context)\n    - [Actions](#actions)\n    - [Build-in actions](#build-in-actions)\n      - [Initialize](#initialize)\n      - [InitializeLazy](#initializelazy)\n      - [Define a root element](#define-a-root-element)\n      - [Define a custom conditon](#define-a-custom-conditon)\n      - [Handling errors](#handling-errors)\n    - [Values](#values)\n  - [View](#view)\n  - [EventEmitter](#eventemitter)\n- [License](#license)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Why?\n\nThere are a [lot of great frameworks](https://javascriptreport.com/the-ultimate-guide-to-javascript-frameworks/)\nout there which are supposed to be used for _single page applications (SPA)_.\nWhen using them on regular websites it's hard to apply those frameworks to an\nalready server-side rendered DOM or to enhance certain sections with some\n_interaction candy_. On the other hand, the network payload to ship an SPA\nframework is quite huge when using for example only the _state management_ or\n_virtual DOM_ of that framework. This may results in a higher _time to\ninteractive_ due to network traffic, parse, interpret and execution time.\n\nPacto tries to reduce those problems by shipping small features which are using\nlatest browser features like [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver)\nand [WeakMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakmap).\n\n## The Concepts\n\nIn contrast to other libraries, pacto follows the traditional approach of an MVC\nframework. The core of pacto is a context instance. This is based on a typical\nevent bus where events can be added, removed and triggered (pubsub pattern). The\ncontext instance also allows adding actions to certain events. An action is\ndesigned to hold a part of the application logic. Each time a relevant event\noccurs, an added action will run to execute a logic like to update a state, to\nfetch or to recalculate data.\n\nThese actions allow creating modules. Each module should contain at least one\n[initialize action](#initialize) but can be composed of multiple actions,\nstores, states, services, views etc. This initialize action is meant to be\nthe entry point of each module. It setups and executes its module:\n\n![pacto app module structure](https://raw.githubusercontent.com/schorfES/pacto/main/docs/app.png)\n\nThe state management is not solved by pacto, but there is small _backbone/mobx inspired_\nmodel and collection extension for pacto called [pacto.model](https://github.com/schorfES/pacto.model).\n\n## Installation\n\nPacto is available on [NPM](https://www.npmjs.com/package/pacto):\n\n```bash\nnpm install pacto --save\n```\n\n## Requirements\n\nPacto is _dependency free_, but it requires latest browser features.\nSo you may need to add a polyfill for [WeakMap](https://www.npmjs.com/package/weakmap-polyfill).\nWhen using [InitializeLazy](#initializelazy) you can also add a polyfill for\n[IntersectionObserver](https://www.npmjs.com/package/intersection-observer).\n\nUsing dynamic imports, this boilerplate can be used to load all required\npolyfills before loading and running the app:\n\n```javascript\n(function(src){\n\tPromise.all([\n\t\t(!!window.WeakMap || import('weakmap-polyfill')),\n\t\t(!!window.IntersectionObserver || import('intersection-observer')),\n\t]).then(() =\u003e {\n\t\tvar script = document.createElement('script');\n\t\tscript.type = 'text/javascript';\n\t\tscript.charset = 'utf-8';\n\t\tscript.async = true;\n\t\tscript.defer = true;\n\t\tscript.src = src;\n\t\tdocument.body.appendChild(script);\n\t});\n})('/path/to/app-using-pacto.js');\n```\n\n### Support NodeList.prototype.forEach\n\nPacto expects the support of `NodeList.prototype.forEach`. For older Browsers such\nas IE11 you need to add another [polyfill](https://www.npmjs.com/package/nodelist-foreach-polyfill).\n\n## Documentation\n\n### Context\n\nAn instance of pacto's Context has typical known properties of an\n[EventEmitter](#eventemitter) like `.on()`, `.off()` and `.trigger()`. It also\nallows to handle [Actions](#actions) and store/receive [Values](#values) in each\ninstance.\n\n```javascript\nimport {Context} from 'pacto';\n\nconst context = new Context();\ncontext\n\t.on('event:type', (event) =\u003e console.log('The event occurred.', event))\n\t.trigger('event:type', {foo: 'bar'})\n\t.off('event:type');\n```\n\nThe context can store the event-histroy. This allows modules to load lazy and\nreact on previous events from history, if required. The history is disabled by\ndefault. To enable this feature pass `{history: true}` into the constructor. The\nhistory can be flushed.\n\n```javascript\nimport {Context} from 'pacto';\n\nconst context = new Context({history: true});\ncontext.trigger('event:type');\ncontext.trigger('event:type', {foo: 'bar'});\ncontext.histroy; // logs: [{type: 'event:type', data: null}, {type: 'event:type', data {foo: 'bar'}}]\ncontext.flushHistory();\ncontext.histroy; // logs: []\n```\n\n#### Actions\n\nAn Action is a class which can bound to a specific event. Each action class\nneeds to contain at least a `.run()` method. When an action relevant event is\ndispatched through the context, an instance of the action class will be created\nand executed. The instance of each action has access to the context and\nthe passed event data which triggered the execution of that action.\n\nAction management is done by the `.actions` property of the context instance.\n\n```javascript\nimport {Context} from 'pacto';\n\nclass Action {\n\trun() {\n\t\tconsole.log('I am an action', this.context, this.event);\n\t}\n}\n\nconst context = new Context();\ncontext.actions.add('event:type', Action);\ncontext.trigger('event:type', {foo: 'bar'}); // logs: 'I am an action', {context}, {event}\n```\n\nRead more about the [actions API](./docs/Context.md#actions).\n\n#### Build-in actions\n\nPacto offers build-in actions that help to define modules by using a configuration.\n\n##### Initialize\n\nThe initialize action setups a module and wires a [view](#view) to a DOM element. Each\ninitialize action is described by its settings: `selector`, `view`, `namespace`.\nThe selector is a CSS valid selector to define which elements to use for each\nview instance. The created view instance is grouped in a list of views\nby the initialize action. This list is stored inside the context values using a\ngiven namespace (take a look at [Values](#values)).\n\n```javascript\n// Initialize.js\nimport {Initialize} from 'pacto';\nimport {View} from 'mymodule/views/View';\n\nexport class Action extends Initialize {\n\tget settings() {\n\t\treturn {\n\t\t\tselector: '.mymodule',\n\t\t\tnamespace: 'mymodule:views'\n\t\t\tview: View\n\t\t};\n\t}\n}\n\n// App.js\nimport {Context} from 'pacto';\nimport {Action as MyModule} from 'mymodule/actions/Initialize';\n\nconst context = new Context();\ncontext.actions.add('app:start', [\n\tMyModule,\n\t// Add more modules here...\n]);\ncontext.trigger('app:start');\n```\n\nThe initialize action of pacto ships some hooks which are called while executing\nand creating views. These hooks can be used by overwriting them:\n\n* `beforeAll()`\n* `beforeEach(options, el, index)`\n* `afterEach(view, el, index)`\n* `afterAll(views)`\n\n`beforeAll`, `beforeEach` and `afterEach` can return `false` to skip the current\nexecution phase.\n\n##### InitializeLazy\n\nUsing an app bundler like webpack, parcel or rollup allows using code splitting\nby defining dynamic imports. Using them creates a smaller app build by\nseparating them into chunks. The InitializeLazy action of pacto offers the\npossibility to simply use that feature and only load a certain module when its\ncorresponding element exists inside the users DOM. If at least one of these\nelements is found and visible, the initialize action of that module will be\nimported, instantiated and executed. Once loaded the specific action will\nreplace the lazy action.\n\n![pacto app module structure with lazy initialize action](https://raw.githubusercontent.com/schorfES/pacto/main/docs/appchunk.png)\n\n```javascript\n// Initialize.js\nimport {Initialize} from 'pacto';\nimport {View} from 'mymodule/views/View';\n\nexport class Action extends Initialize {\n\tget settings() {\n\t\treturn {\n\t\t\tselector: '.mymodule',\n\t\t\tnamespace: 'mymodule:views'\n\t\t\tview: View\n\t\t};\n\t}\n}\n\n// InitializeLazy.js\nimport {InitializeLazy} from 'pacto';\n\nexport class Action extends InitializeLazy {\n\tget settings() {\n\t\treturn {\n\t\t\tselector: '.mymodule',\n\t\t};\n\t}\n\n\tget import() {\n\t\treturn import('mymodule/actions/Initialize');\n\t}\n}\n\n// App.js\nimport {Context} from 'pacto';\nimport {Action as MyLazyModule} from 'mymodule/actions/InitializeLazy';\n\nconst context = new Context();\ncontext.actions.add('app:start', [\n\tMyLazyModule,\n\t// Add more modules here...\n]);\ncontext.trigger('app:start');\n```\n\n##### Define a root element\n\nBoth actions, [`Initialize`](#initialize) and [`InitializeLazy`](#initializelazy) takes care of an event property `root`. If not defined, they use the document body to look up for modules by the given selector. When passing a DOM element as root by triggering an event from the context, this element is used to look up child modules. This is useful when a specific section needs to be re-initialized.\n\n```javascript\nconst root = document.querySelector('.fetched-content');\nthis.context.trigger('app:start', { root });\n```\n\n##### Define a custom conditon\n\nThe `InitializeLazy` action has a getter `condition` that returns a promise. It\nallows customizing the load condition when executing the startup (lookup for\nmatching elements) process. The default implementation waits for the DOM-ready\nstate to be \"complete\" (using the `DOMContentLoaded` event to wait for).\n\n```javascript\nexport class Action extends InitializeLazy {\n\tget settings() {\n\t\treturn {\n\t\t\tselector: '.mymodule',\n\t\t};\n\t}\n\n\tget import() {\n\t\treturn import('mymodule/actions/Initialize');\n\t}\n\n\tget condition() {\n\t\t// Not a real world example...\n\t\treturn new Promise((resolve) =\u003e setTimeout(resolve, 1000));\n\t}\n}\n```\n\n##### Handling errors\n\nNote that due to various reasons there could be an error during the\nexecution of your actions. To be able to catch these you can listen\nfor `\u003caction-id\u003e:error` to get notified when an error occurs.\n\nSo in our `App.js` we can add the `app:start:error` handler to get notified:\n\n```javascript\ncontext.on('app:start:error', (event) =\u003e {\n\tconst {error} = event.data;\n\n\talert('An error while initializing the App occured.. ' + error.message);\n});\n```\n\n#### Values\n\nThe `.values` property of a context instance is a key/value storage. Each\ntype of value can be stored using a unique namespace (key).\n\n```javascript\nimport {Context} from 'pacto';\n\nconst context = new Context();\ncontext.values.add('name:space', {foo: 'bar'});\nconsole.log(context.values.has('name:space')); // logs: true\nconsole.log(context.values.get('name:space')); // logs: {foo: 'bar'}\n\ncontext.values.remove('name:space');\nconsole.log(context.values.has('name:space')); // logs: false\nconsole.log(context.values.get('name:space')); // logs: undefined\n```\n\nRead more about the [values API](./docs/Context.md#values).\n\n### View\n\nA view is a simple wrapper class for DOM elements. It holds the references to\nits DOM element and context. An instance of this class is meant to be the\ncommunicator between user interactions and pacto framework actions. It is also\nthe right place to do complex renderings using virtual DOM libraries by\noverwriting the `render()` function.\n\n```javascript\nimport {View} from 'pacto';\n\nclass ToggleButton extends View {\n\trender() {\n\t\tthis.el.addEventListener('click', (event) =\u003e {\n\t\t\tthis.el.classList.toggle('foo');\n\t\t\tthis.context.trigger('togglebutton:toggle');\n\t\t});\n\t}\n}\n```\n\n### EventEmitter\n\nAll objects that emit events are instances of the EventEmitter class. These\nobjects expose an `.on()` function that allows one or more functions to be\nattached to named events emitted by the object. To remove those attached\nfunctions the `.off()` function can be used. When a specific event is dispatched\non an EventEmitter instance, all attached functions by that event are called\nsynchronously.\n\n## License\n\n[LICENSE (MIT)](./LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fschorfes%2Fpacto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fschorfes%2Fpacto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fschorfes%2Fpacto/lists"}