{"id":16370805,"url":"https://github.com/integralist/stark","last_synced_at":"2025-10-26T07:32:07.505Z","repository":{"id":9107940,"uuid":"10889240","full_name":"Integralist/Stark","owner":"Integralist","description":"Simplified separation of components into decoupled applications","archived":false,"fork":false,"pushed_at":"2013-08-20T10:35:05.000Z","size":320,"stargazers_count":13,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-07T17:22:01.501Z","etag":null,"topics":[],"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/Integralist.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}},"created_at":"2013-06-23T18:00:15.000Z","updated_at":"2015-12-10T09:52:37.000Z","dependencies_parsed_at":"2022-08-29T03:01:46.903Z","dependency_job_id":null,"html_url":"https://github.com/Integralist/Stark","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Integralist/Stark","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Integralist%2FStark","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Integralist%2FStark/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Integralist%2FStark/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Integralist%2FStark/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Integralist","download_url":"https://codeload.github.com/Integralist/Stark/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Integralist%2FStark/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281074244,"owners_count":26439422,"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-10-26T02:00:06.575Z","response_time":61,"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":[],"created_at":"2024-10-11T03:06:02.380Z","updated_at":"2025-10-26T07:32:07.205Z","avatar_url":"https://github.com/Integralist.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Stark\n\nSimplified separation of components into decoupled applications.\n\nThis isn't a framework (or all-encompassing suite of tools). It is a 'strategy', a pattern for handling the architecture of your code. I feel this particular strategy works very well and keeps code simple and decoupled and performant (through the use of a custom build script).\n\n## Table of contents\n\n- [API](#api)\n- [Example](#example)\n- [Conventions](#conventions)\n- [CSS Design](#css-design)\n- [Non-JavaScript Components](#non-javascript-components)\n- [Components vs Patterns?](#components-vs-patterns)\n- [Grid System](#grid-system)\n- [Browser support](#browser-support)\n- [Extra output in our CSS?](#extra-output-in-our-css)\n- [JavaScript Design](#javascript-design)\n- [JavaScript Conventions](#javascript-conventions)\n- [Build Script](#build-script)\n- [Example of optimised code](#example-of-optimised-code)\n- [Parsing?](#parsing)\n- [Component usage](#component-usage)\n- [Extension usage](#extension-usage)\n- [TODO](#todo)\n\n## API\n\n- `app.use('extension', 'extension', 'extension')` loads specified extensions\n- `app.start()` goes through the HTML looking for components to load\n\nWe also use RequireJS and AMD to wrap the above API calls, for example...\n\n```js\n// the only part of this code that needs to change\n// is the list of extensions you want to use\n// the rest is standard (read: required to be run)\nrequire(['app', 'configuration'], function(app) {\n    app.use('extension-a', 'extension-b');\n    app.start();\n});\n```\n\n## Example\n\nHTML...\n\n```html\n\u003cdiv data-component=\"hello\" id=\"js-component-hello\"\u003ehello\u003c/div\u003e\n\u003cdiv data-component=\"world\" id=\"js-component-world\"\u003eworld\u003c/div\u003e\n\u003cscript src=\"libs/require.js\" data-main=\"bootstrap-about\" async\u003e\u003c/script\u003e\n```\n\nBootstrap... \n\n```js\nrequire(['app', 'configuration'], function(app) {\n    app.use('mediator', 'extension-a', 'extension-b');\n    app.start();\n});\n```\n\nComponent...\n\n```js\ndefine({\n    init: function() {\n        console.log('an example component');\n    }\n});\n```\n\nExtension...\n\n```js\n// Silly example\ndefine(function(){\n    if (!String.prototype.blah) {\n        String.prototype.blah = function() {\n            return this + ' BLAH!';\n        };\n    }\n});\n```\n\n## Conventions\n\nWe do require you to implement a few conventions (for the build script to work properly). \n\nWe're not talking Ruby on Rails level of 'convention over configuration', but just a few small conventions need to be adhered to and luckily none of them are confusing or complicated. \n\nI clarify them in more detail later, but in summary the conventions are...\n\n- HTML components need a `data-attribute` and an `id`\n- The data attribute needs to be like: `data-component=\"xxx\"`\n- The id attribute needs to be like: `id=\"js-component-xxx\"`\n- The `xxx` needs to be the name of the component\n- Components are placed inside a `/components/` directory\n- Extensions are placed inside a `/extensions/` directory\n- Each page has its own bootstrap js file `boostrap-xxx.js`\n- JavaScript needs to be loaded at the bottom of the page\n- Components need to have an `init` method\n\n## CSS Design\n\nWe have the following files:\n\n- core experience (for non-js devices)\n- groups 1-4 (enhanced core experience for js-enabled devices)\n- components (all other functionality)\n\nWe're using a form of an inheritance pattern.\n\nThrough the use of media queries, each 'group' inherits the styles from the group before it. \n\ne.g. group 1 sets some styles, group 2 only sets additional styles it needs for that particular break-point, but relies on the group 1 styles that came before it.\n\nThe components encapsulate their own logic for handling viewport dimensions.\n\n## Non-JavaScript Components\n\nSome components you create will have no need for JavaScript. If that's the case then it's best to create them using a simple HTML class instead of a `data-component` attribute and corresponding `id`.\n\nFor example...\n\n```html\n\u003cdiv class=\"features-and-analysis\"\u003e\n    ...\n\u003c/div\u003e\n\n\u003cdiv class=\"index\"\u003e\n    ...\n\u003c/div\u003e\n```\n\n## Components vs Patterns?\n\nJust to be clear that a 'pattern' and a 'component' are two different things.\n\nSummary: Component \u003e Pattern \u003e Layout\n\nA component is a single piece of functionality. If you had a list of news articles then the list could potentially be the component. You're inserting into the page a news listing component.\n\nAs part of that component you have individual news stories that all look the same. They are a 'pattern'. We're abstracted the common design elements that make up the component as a whole into a pattern, and that pattern has been written in such a way that it is a reusable chunk of code (as we may find that the pattern we've abstracted is common across a lot of different components).\n\nA layout then dictates how the pattern looks at certain break-points.\n\n### Where is the logic held?\n\nThe way we're building our components is so that the logic is self-contained (encapsulated). The question that now comes up is: \"what about the pattern within the component?\".\n\nWhat this means is, who should hold the logic for determining how the content should look on screen?\n\nWe feel that the 'pattern' which is being imported into the 'component' shouldn't determine how it looks at different break points. That should be handled by the component itself.\n\nTo explain: a pattern is a combination of HTML and CSS. Think of the [media object as an example](http://www.stubbornella.org/content/2010/06/25/the-media-object-saves-hundreds-of-lines-of-code/).\n\nNow our version of the media object has the image placed above the text content by default. Not everywhere we use this pattern is going to want the image above the text. \n\nSo imagine we now have 'component-a' and 'component-b'. Component 'a' is a list of media objects. Component 'b' is also a list of media objects. Whilst component 'a' will display the objects in a grid format (as the screen gets larger), component 'b' will keep the objects in a list but instead will place the image on the left side of the text content and not placed above it.\n\nSo the way we see it: any changes to the pattern itself (such as the image being placed on the left of the text, rather than above it) should be handled by the pattern's own code. Specifically via the use of a 'modifier' class (if you're not sure what that means then look up writing CSS using B.E.M notation).\n\nAny changes to the layout of that pattern should be handled by the component.\n\n## Grid System\n\nWe utilise the same grid system as suggested by the [BBC GEL guidelines](http://www.bbc.co.uk/gel). Feel free to discard it and use your own grid if you so choose, but we've included it for any one who might find it useful.\n\nWe don't use grids very often because they don't respond well (not easily anyway, not without much tweaking) in a responsive layout. For example, if you have a grid of three columns and they should display in a linear one column layout when on mobile, you then need to overwrite the specific column widths... that's messy and so we have limited use for them.\n\n## Browser support\n\nThe only thing we're using which will be a concern to some of you is the `box-sizing` property. Which means IE8+ is all good. If you're still supporting IE7... well, I'm sorry.\n\n## Extra output in our CSS?\n\nIn our simplified example it would look like there is a lot of code being generated, but this is because our examples aren't great.\n\nIn a real project, the styles don't change that much when moving up the break-points. For example, a brand logo will generally look the same across all break-points with the exception that it might be floated or positioned different on desktop sized device compared to the rest of the devices you're testing against.\n\nSo don't take the generated CSS output from this repo as a representation of your own project.\n\n## JavaScript Design\n\nThe basic API is based off of the [Aura.js](https://github.com/aurajs/aura) project, but 99% of that API is not replicated here.\n\nI'm looking to create a simplified project boilerplate and NOT a framework (or encompassing suite of tools).\n\n## JavaScript Conventions\n\nThere are few conventions that need to be adhered to...\n\n1. Components need to be placed inside a `components` directory, inside a folder which matches the components name. The file itself needs to be called `component.js`. A component needs to be an object with an `init` method. See this project for examples.\n\n    Note: we store off the component's container (e.g. `\u003cdiv data-component=\"xxx\"\u003e`) in the global app namespace `window.app.components.xxx` so you can do what you like with it within the component (e.g. you might want to completely replace that container for some new HTML). For the build script to be able to do this you need to have added an `id` to your component container that matches your component name (e.g. `\u003cdiv data-component=\"xxx\" id=\"js-component-xxx\"\u003e\"`) -\u003e notice the naming convention of `js-component-nameOfComponent`.\n\n    When we create HTML elements and inject them into the DOM we use `id` attributes for referencing those elements (as that is the quickest and most efficient way to access a DOM node). We also use the following naming convention: `js-nameImGoingToUse` (notice the camelCase)\n\n2. Extensions need to be placed inside a `extensions` directory, inside a folder which matches the extension name. The file itself needs to be called `extension.js`. See this project for examples. \n\n\tNote: extensions can't be passed to a component so make sure your 'extensions' are globally available by attaching them to a single global namespace called `app` (defined inside the app.js file). If you change the name then remember to update the grunt-customtasks.js as well.\n\t\n\t**And don't mistake the phrase \"Don't pollute the global namespace\" with \"Don't ever use the global namespace ever ever ever\" -\u003e that's just dogmatic nonsense.**\n\n3. Each page should have its own bootstrap file: `boostrap-xxx.js` (where `xxx` is the page type or name). This file should include only one `require` call and that should be to the 'app' (see this project for an example) -\u003e no other code of importance to the project should be placed inside that bootstrap file (code for the page should either be a component or a extension that is loaded by `app.js`).\n\n4. JavaScript needs to be loaded at the bottom of the page. This is because when running our code in development we are asynchronously loading modules and then storing off the relevant HTML elements in a global namespace for our components to utilise however they need to. But this creates a waterfall effect for our network loading and so in production we don't want that -\u003e hence our build script takes care of that. But for the code not to error we need to grab the elements in the page and store them off using `getElementById` and so the scripts need to be loaded at the bottom of the page for that to work.\n\n...that's it.\n\n## Build Script\n\nWe rely heavily on the conventions defined above and [Grunt](http://gruntjs.com/) to build (concatenate + minify) our components and extensions into single page specific modules.\n\nTo build the page specific modules simply run `grunt build`.\n\n### Breakdown of the custom grunt tasks\n\n`grunt build` (calls custom task `get-components`)  \n`grunt get-components` (builds the r.js configuration object and calls `requirejs`)  \n`grunt requirejs` (executes build and does some clean-up)\n\n## Example of optimised code\n\n### HTML for 'about' page...\n\n```html\n\u003cdiv data-component=\"world\" id=\"js-component-world\"\u003eWorld\u003c/div\u003e\n\u003cdiv data-component=\"contacts\" id=\"js-component-contacts\"\u003e\u003c/div\u003e\n```\n\nnote: the `contacts` component has a dependency on jQuery\n\n### Original `bootstrap-about`...\n\n```js\nrequire(['app', 'configuration'], function(app) {\n    app.use('blah', 'mediator');\n    app.start();\n});\n```\n\n...in development this would asynchronously load both the `blah` and `mediator` extensions and any components found within `index.html`.\n\n### The resulting concatenated `bootstrap-about`...\n\nNote: this would be minified but for the sake of readability I turned off the minification process.\n\n```js\nwindow.app = {\n    components: {\n        \"world\": document.getElementById(\"js-component-world\"),\n        \"contacts\": document.getElementById(\"js-component-contacts\")\n    }\n};\n\ndefine(\"bootstrap-about\", function () {});\n\ndefine('extensions/blah/extension', [], function () {\n    if (!String.prototype.blah) {\n        String.prototype.blah = function () {\n            return this + ' BLAH!';\n        };\n    }\n});\n\ndefine('extensions/mediator/extension', [], function () {\n    var mediator = (function () {\n        var subscribe = function (channel, fn) {\n            if (!mediator.channels[channel]) {\n                mediator.channels[channel] = [];\n            }\n\n            mediator.channels[channel].push({\n                context: this,\n                callback: fn\n            });\n\n            return this;\n        },\n\n            publish = function (channel) {\n                if (!mediator.channels[channel]) {\n                    return false;\n                }\n\n                var args = Array.prototype.slice.call(arguments, 1);\n\n                for (var i = 0, l = mediator.channels[channel].length; i \u003c l; i++) {\n                    var subscription = mediator.channels[channel][i];\n                    subscription.callback.apply(subscription.context, args);\n                }\n\n                return this;\n            };\n\n        return {\n            channels: {},\n            publish: publish,\n            subscribe: subscribe,\n            wrap: function (obj) {\n                obj.subscribe = subscribe;\n                obj.publish = publish;\n            }\n        };\n\n    }());\n\n    window.app.mediator = mediator;\n\n    return mediator;\n});\n\ndefine('components/world/component', {\n    init: function () {\n        console.log('world component initialised');\n\n        if (window.app.mediator) {\n            console.log('app.mediator', app.mediator);\n        }\n    }\n});\n\n/*!\n * jQuery JavaScript Library v2.0.3 -sizzle,-event-alias,-effects,-deprecated\n * http://jquery.com/\n *\n * Includes Sizzle.js\n * http://sizzlejs.com/\n *\n * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2013-07-07T17:13Z\n */\n(function (window, undefined) {\n\n    // jQuery code\n\n})(window);\n\ndefine('components/contacts/component', ['jquery'], function ($) {\n    return {\n        init: function () {\n            this.container = window.app.components.contacts;\n        }\n    };\n});\nrequire([\"extensions/blah/extension\", \"extensions/mediator/extension\", \"components/world/component\", \"components/contacts/component\"], function (a, b, c, d) {\n    c.init();\n    d.init();\n});\n```\n\n## Parsing?\n\nIn this build script I read the `.html` files looking for components. But if all your components are dynamically inserted server-side by a config file (like BBC News) then you would need to change the build script to look at a config file and parse that type of file instead (you'd also need to enforce a naming convention to make it easy for the build script to parse the config file)\n\n## Component usage\n\nIt's fine for Components to specify their own dependencies. So if all your dependencies require jQuery then it's ok to specifically inject that dependency into each module. \n\nWhen it comes to running the build script the jQuery module will only be included once, and being explicit with your dependencies means the component is easier to move into another project as the dependencies are clearly stated.\n\n## Extension usage\n\nExtensions aren't injectable into a component so they need to be made available on the single global namespace `window.app`.\n\nThe app.js module declares `window.app` (which is an object you can hook onto). The mediator library is an example of using this hook to make the mediator code available within a component.\n\n## TODO\n\n- Fix issue with build script where we grab references to all components where we should only be getting references to components on a specific page (this is a flaw that I'm not sure yet how to resolve as we're using a r.js' `wrap` property to inject the global property at the top of the concatenated file) -\u003e Possibly, use `wrap` to inject the window.app.component and then for each bootstrap-xxx read we determine the component elements to grab\n- Start building out common components based on BBC News website -\u003e ensuring they are flexible and adaptable to different break-points and that they communicate efficiently via the mediator communication object.\n- Move from Sass to Stylus (can't do until Stylus gets support for passing code blocks)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintegralist%2Fstark","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fintegralist%2Fstark","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintegralist%2Fstark/lists"}