├── .npmignore ├── .gitignore ├── .travis.yml ├── component-playground ├── global-reset.css ├── lib │ ├── parse-fixture-path.js │ ├── get-title-for-fixture.js │ └── get-component-fixture-tree.js ├── public │ └── index.html ├── entry.js └── config.js ├── .jscsrc ├── bin └── component-playground.js ├── karma.conf.js ├── tests ├── component-playground │ └── lib │ │ ├── parse-fixture-path.js │ │ └── get-title-for-fixture.js └── bind-polyfill.js ├── LICENSE ├── package.json ├── CONTRIBUTING.md └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tests 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tests/coverage 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | after_success: 5 | - npm run coveralls 6 | -------------------------------------------------------------------------------- /component-playground/global-reset.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | height: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /component-playground/lib/parse-fixture-path.js: -------------------------------------------------------------------------------- 1 | module.exports = function(fixturePath) { 2 | // './my-component/my-state.js' => ('my-component', 'my-state') 3 | return fixturePath.match(/^\.\/(.+)\/(.+)\.js$/); 4 | }; 5 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "excludeFiles": [ 3 | ".git/**", 4 | "node_modules/**", 5 | "tests/coverage/**" 6 | ], 7 | "fileExtensions": [".js", ".jsx"], 8 | "preset": "google", 9 | 10 | "disallowMultipleVarDecl": null 11 | } 12 | -------------------------------------------------------------------------------- /component-playground/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React Component Playground 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /component-playground/lib/get-title-for-fixture.js: -------------------------------------------------------------------------------- 1 | module.exports = function(params) { 2 | var title = 'React Component Playground'; 3 | 4 | // Set document title to the name of the selected fixture 5 | if (params.component && params.fixture) { 6 | title = params.component + ':' + params.fixture + ' – ' + title; 7 | } 8 | 9 | return title; 10 | }; 11 | -------------------------------------------------------------------------------- /bin/component-playground.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'), 4 | WebpackDevServer = require('webpack-dev-server'), 5 | webpack = require('webpack'); 6 | 7 | var rootPath = path.join(__dirname, '..'), 8 | playgroundPath = path.join(rootPath, 'component-playground'); 9 | 10 | var config = require(path.join(playgroundPath, 'config')); 11 | var compiler = webpack(config.webpack); 12 | 13 | var server = new WebpackDevServer(compiler, { 14 | contentBase: path.join(playgroundPath, 'public'), 15 | publicPath: '/build/', 16 | hot: true 17 | }); 18 | 19 | server.listen(config.server.port, config.server.hostname, function() {}); 20 | -------------------------------------------------------------------------------- /component-playground/entry.js: -------------------------------------------------------------------------------- 1 | require('./global-reset.css'); 2 | 3 | var ReactQuerystringRouter = require('react-querystring-router'), 4 | ComponentPlayground = require('react-component-playground'), 5 | getComponentFixtureTree = require('./lib/get-component-fixture-tree.js'), 6 | getTitleForFixture = require('./lib/get-title-for-fixture.js'); 7 | 8 | module.exports = new ReactQuerystringRouter.Router({ 9 | container: document.getElementById('component-playground'), 10 | defaultProps: { 11 | components: getComponentFixtureTree() 12 | }, 13 | getComponentClass: function() { 14 | return ComponentPlayground; 15 | }, 16 | onChange: function(params) { 17 | document.title = getTitleForFixture(params); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | basePath: 'tests/', 4 | browsers: ['PhantomJS'], 5 | coverageReporter: { 6 | type: 'lcov', 7 | dir: 'coverage/' 8 | }, 9 | files: [ 10 | '**/*.js' 11 | ], 12 | frameworks: ['mocha', 'chai', 'sinon-chai'], 13 | preprocessors: { 14 | '**/*.js': ['webpack'] 15 | }, 16 | reporters: ['mocha', 'coverage'], 17 | webpack: { 18 | module: { 19 | postLoaders: [{ 20 | test: /\.js$/, 21 | exclude: /(node_modules|tests)\//, 22 | loader: 'istanbul-instrumenter' 23 | }] 24 | } 25 | }, 26 | webpackMiddleware: { 27 | noInfo: true 28 | } 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /tests/component-playground/lib/parse-fixture-path.js: -------------------------------------------------------------------------------- 1 | var parseFixturePath = 2 | require('../../../component-playground/lib/parse-fixture-path.js'); 3 | 4 | describe('Parse fixture path', function() { 5 | it('should extract component and fixture name', function() { 6 | var parts = parseFixturePath('./my-component/my-state.js'); 7 | 8 | expect(parts[1]).to.equal('my-component'); 9 | expect(parts[2]).to.equal('my-state'); 10 | }); 11 | 12 | it('should extract nested component and fixture name', function() { 13 | var parts = parseFixturePath('./component-group/my-component/my-state.js'); 14 | 15 | expect(parts[1]).to.equal('component-group/my-component'); 16 | expect(parts[2]).to.equal('my-state'); 17 | }); 18 | }) 19 | -------------------------------------------------------------------------------- /tests/component-playground/lib/get-title-for-fixture.js: -------------------------------------------------------------------------------- 1 | var getTitleForFixture = 2 | require('../../../component-playground/lib/get-title-for-fixture.js'); 3 | 4 | describe('Get title for fixture', function() { 5 | it('should return "React Component Playground" ' + 6 | 'when no fixture is selected', function() { 7 | var title = getTitleForFixture({}); 8 | 9 | expect(title).to.equal('React Component Playground'); 10 | }); 11 | 12 | it('should include component and fixture name in title ' + 13 | 'when no fixture is selected', function() { 14 | var title = getTitleForFixture({ 15 | component: 'FooComponent', 16 | fixture: 'bar-fixture' 17 | }); 18 | 19 | expect(title) 20 | .to.equal('FooComponent:bar-fixture – React Component Playground'); 21 | }); 22 | }) 23 | -------------------------------------------------------------------------------- /component-playground/lib/get-component-fixture-tree.js: -------------------------------------------------------------------------------- 1 | var parseFixturePath = require('./parse-fixture-path.js'); 2 | 3 | module.exports = function() { 4 | var requireFixture = require.context('COSMOS_FIXTURES', true, /\.js$/), 5 | fixtures = {}; 6 | 7 | requireFixture.keys().forEach(function(fixturePath) { 8 | var pathParts = parseFixturePath(fixturePath), 9 | componentName = pathParts[1], 10 | fixtureName = pathParts[2]; 11 | 12 | // Fixtures are grouped per component 13 | if (!fixtures[componentName]) { 14 | fixtures[componentName] = { 15 | class: require('COSMOS_COMPONENTS/' + componentName), 16 | fixtures: {} 17 | }; 18 | } 19 | 20 | fixtures[componentName].fixtures[fixtureName] = requireFixture(fixturePath); 21 | }); 22 | 23 | return fixtures; 24 | }; 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ovidiu Cherecheș 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/bind-polyfill.js: -------------------------------------------------------------------------------- 1 | /** 2 | * PhantomJS doesn't support Function.prototype.bind natively, so 3 | * polyfill it whenever this module is required. 4 | */ 5 | 6 | (function() { 7 | var Ap = Array.prototype; 8 | var slice = Ap.slice; 9 | var Fp = Function.prototype; 10 | 11 | if (!Fp.bind) { 12 | Fp.bind = function(context) { 13 | var func = this; 14 | var args = slice.call(arguments, 1); 15 | 16 | function bound() { 17 | var invokedAsConstructor = func.prototype && (this instanceof func); 18 | return func.apply( 19 | // Ignore the context parameter when invoking the bound function 20 | // as a constructor. Note that this includes not only constructor 21 | // invocations using the new keyword but also calls to base class 22 | // constructors such as BaseClass.call(this, ...) or super(...). 23 | !invokedAsConstructor && context || this, 24 | args.concat(slice.call(arguments)) 25 | ); 26 | } 27 | 28 | // The bound function must share the .prototype of the unbound 29 | // function so that any object created by one constructor will count 30 | // as an instance of both constructors. 31 | bound.prototype = func.prototype; 32 | 33 | return bound; 34 | }; 35 | } 36 | })(); 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cosmos-js", 3 | "version": "0.6.8", 4 | "description": "A development tool for designing truly encapsulated React components", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/skidding/cosmos.git" 8 | }, 9 | "dependencies": { 10 | "babel-core": "^5.2.17", 11 | "babel-loader": "^5.0.0", 12 | "css-loader": "^0.15.6", 13 | "less": "^2.5.1", 14 | "less-loader": "^2.2.0", 15 | "lodash": "^3.5.0", 16 | "react-component-playground": "^0.3.0", 17 | "react-hot-loader": "^1.2.7", 18 | "react-querystring-router": "^0.2.0", 19 | "style-loader": "^0.12.3", 20 | "webpack": "^1.7.3", 21 | "webpack-dev-server": "^1.8.0", 22 | "yargs": "^3.6.0" 23 | }, 24 | "devDependencies": { 25 | "chai": "^2.2.0", 26 | "coveralls": "^2.11.2", 27 | "esprima-fb": "^12001.1.0-dev-harmony-fb", 28 | "istanbul": "^0.3.13", 29 | "istanbul-instrumenter-loader": "^0.1.2", 30 | "jscs": "^1.12.0", 31 | "karma": "^0.13.2", 32 | "karma-chai": "^0.1.0", 33 | "karma-cli": "0.0.4", 34 | "karma-coverage": "^0.2.7", 35 | "karma-mocha": "^0.1.10", 36 | "karma-mocha-reporter": "^1.0.2", 37 | "karma-phantomjs-launcher": "^0.1.4", 38 | "karma-sinon-chai": "^0.3.0", 39 | "karma-webpack": "^1.7.0", 40 | "mocha": "^2.2.4", 41 | "sinon": "^1.14.1", 42 | "sinon-chai": "^2.7.0" 43 | }, 44 | "peerDependencies": { 45 | "react": "^0.13.1" 46 | }, 47 | "scripts": { 48 | "pretest": "jscs --esprima=esprima-fb ./", 49 | "test": "karma start --single-run", 50 | "coveralls": "cat tests/coverage/*/lcov.info | node_modules/coveralls/bin/coveralls.js" 51 | }, 52 | "bin": { 53 | "component-playground": "./bin/component-playground.js" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /component-playground/config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | argv = require('yargs').argv, 3 | _ = require('lodash'), 4 | webpack = require('webpack'); 5 | 6 | var playgroundPath = __dirname, 7 | rootPath = path.join(playgroundPath, '..'), 8 | modulesPath = path.join(rootPath, 'node_modules'), 9 | cwd = process.cwd(); 10 | 11 | var resolvePath = function(userPath) { 12 | return path.resolve(cwd, userPath); 13 | }; 14 | 15 | var config = {}, 16 | userConfig; 17 | 18 | try { 19 | userConfig = require( 20 | resolvePath(argv.configPath || 'component-playground.config')); 21 | } catch (e) { 22 | if (e instanceof Error && e.code === 'MODULE_NOT_FOUND') { 23 | userConfig = {}; 24 | } else { 25 | throw e; 26 | } 27 | } 28 | 29 | config.server = _.extend({ 30 | port: 8989, 31 | hostname: 'localhost' 32 | }, userConfig.server); 33 | 34 | config.webpack = { 35 | context: playgroundPath, 36 | entry: [ 37 | 'webpack-dev-server/client?http://' + config.server.hostname + ':' + 38 | config.server.port, 39 | 'webpack/hot/dev-server', 40 | './entry' 41 | ], 42 | resolve: { 43 | alias: { 44 | COSMOS_COMPONENTS: resolvePath(argv.componentsPath || 'components'), 45 | COSMOS_FIXTURES: resolvePath(argv.fixturesPath || 'fixtures') 46 | }, 47 | extensions: ['', '.js', '.jsx'] 48 | }, 49 | resolveLoader: { 50 | root: modulesPath 51 | }, 52 | module: { 53 | loaders: [{ 54 | test: /\.jsx?$/, 55 | exclude: /node_modules/, 56 | loaders: ['react-hot-loader', 'babel-loader'] 57 | }, { 58 | test: /\.css$/, 59 | loader: 'style-loader!css-loader' 60 | }, { 61 | test: /\.less$/, 62 | loader: 'style-loader!css-loader!less-loader' 63 | }] 64 | }, 65 | output: { 66 | libraryTarget: 'umd', 67 | library: 'cosmosRouter', 68 | path: path.join(playgroundPath, 'public', 'build'), 69 | filename: 'bundle.js', 70 | publicPath: '/build/' 71 | }, 72 | plugins: [ 73 | new webpack.HotModuleReplacementPlugin(), 74 | new webpack.NoErrorsPlugin() 75 | ] 76 | }; 77 | 78 | if (userConfig.webpack) { 79 | config.webpack = userConfig.webpack(config.webpack); 80 | } 81 | 82 | module.exports = config; 83 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Mission 2 | 3 | **How to design components that are truly encapsulated?** 4 | 5 | [Read more.](https://github.com/skidding/cosmos/wiki/Problem) 6 | 7 | ## Manifesto 8 | 9 | _cos·mos1 `/ˈkäzməs,-ˌmōs,-ˌmäs/` noun — 1. The universe seen as 10 | a well-ordered whole._ 11 | 12 | - Zero bootstrap 13 | - Can be plugged into any other framework 14 | - Everything is a Component 15 | - Components are oblivious of ancestors 16 | - Any component can be represented by a URI 17 | - The state of the entire UI can be serialized at any given point in time 18 | - Components can implement any data sync mechanism 19 | 20 | These set of principles were defined before any code was written and have guided the evolution of Cosmos to present. 21 | 22 | ## Packaging 23 | 24 | Cosmos is split into smaller repositories for modularity and ease of development. This contributing guide applies to all included packages. 25 | 26 | - [ReactComponentTree](https://github.com/skidding/react-component-tree) – Serialize and reproduce the state of an entire tree of React components 27 | - [ReactComponentPlayground](https://github.com/skidding/react-component-playground) – Minimal frame for loading and testing React components in isolation 28 | - [ReactQuerystringRouter](https://github.com/skidding/react-querystring-router) – Bare router for React components, using query string as props 29 | 30 | The `cosmos-js` package is the top layer, responsible for offering a minimum configuration ComponentPlayground setup for projects powered by React components. 31 | 32 | ## Design 33 | 34 | [The Best Code is No Code At All.](http://blog.codinghorror.com/the-best-code-is-no-code-at-all/) Start with this in mind before adding any new line of code. 35 | 36 | 1. When thinking of adding or changing something, make sure it is in line with the project [mission.](#mission) 37 | 2. Once the value added is clear, strive for the simplest, most obvious solution. 38 | 39 | ## Style 40 | 41 | Unless explicitly overridden, all rules from the [Google JavaScript Style Guide](https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml) apply. The [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) is also a good reference. 42 | 43 | ## Tests 44 | 45 | Unit tests both keep the project sane and define its specs. Providing test coverage for every contribution is mandatory. 46 | 47 | Repo | Build | Status 48 | --- | --- | --- 49 | [Cosmos](https://github.com/skidding/cosmos/tree/master/tests) | [![Build Status](https://travis-ci.org/skidding/cosmos.svg?branch=master)](https://travis-ci.org/skidding/cosmos) | [![Coverage Status](https://coveralls.io/repos/skidding/cosmos/badge.svg?branch=master)](https://coveralls.io/r/skidding/cosmos?branch=master) 50 | [ReactComponentTree](https://github.com/skidding/react-component-tree/tree/master/tests) | [![Build Status](https://travis-ci.org/skidding/react-component-tree.svg?branch=master)](https://travis-ci.org/skidding/react-component-tree) | [![Coverage Status](https://coveralls.io/repos/skidding/react-component-tree/badge.svg?branch=master)](https://coveralls.io/r/skidding/react-component-tree?branch=master) 51 | [ReactComponentPlayground](https://github.com/skidding/react-component-playground/tree/master/tests) | [![Build Status](https://travis-ci.org/skidding/react-component-playground.svg?branch=master)](https://travis-ci.org/skidding/react-component-playground) | [![Coverage Status](https://coveralls.io/repos/skidding/react-component-playground/badge.svg?branch=master)](https://coveralls.io/r/skidding/react-component-playground?branch=master) 52 | [ReactQuerystringRouter](https://github.com/skidding/react-querystring-router/tree/master/tests) | [![Build Status](https://travis-ci.org/skidding/react-querystring-router.svg?branch=master)](https://travis-ci.org/skidding/react-querystring-router) | [![Coverage Status](https://coveralls.io/repos/skidding/react-querystring-router/badge.svg?branch=master)](https://coveralls.io/r/skidding/react-querystring-router?branch=master) 53 | 54 | The only untested sections are the [webpack config](component-playground/config.js) and the [dev runner](bin/component-playground.js), which are also the parts most likely to change in the future. 55 | 56 | ## Git flow 57 | 58 | Make sure you're familiar with the [Github flow.](https://guides.github.com/introduction/flow/) 59 | 60 | ### Branches 61 | 62 | Prefix branch names with the corresponding issue number. E.g. `137-contributing-guide` 63 | 64 | ### Commits 65 | 66 | Use the imperative mood to express verbs and add a hashtag with the corresponding issue number at the end of each commit message. The Github UI will generate links to the referenced issues. 67 | 68 | > Adapt fixture mapping to new ComponentPlayground format [#115](https://github.com/skidding/cosmos/issues/115) 69 | 70 | ### Review 71 | 72 | Ask for review *before* writing the code. Saves everybody time and effort. 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cosmos [![Build Status](https://travis-ci.org/skidding/cosmos.svg?branch=master)](https://travis-ci.org/skidding/cosmos) [![Coverage Status](https://coveralls.io/repos/skidding/cosmos/badge.svg?branch=master)](https://coveralls.io/r/skidding/cosmos?branch=master) 2 | 3 | Cosmos is a JavaScript DX* tool for designing truly encapsulated 4 | [React](http://facebook.github.io/react/) components. 5 | 6 | It scans your project for React component 7 | [_fixtures_](http://en.wikipedia.org/wiki/Test_fixture) and loads them inside [ComponentPlayground](https://github.com/skidding/react-component-playground), 8 | enabling you to: 9 | 10 | 1. Render your components under any combination of props and state 11 | 2. See component states evolve in real-time as you interact with running 12 | instances 13 | 14 | > Working with ComponentPlayground improves the component design because it 15 | surfaces any implicit dependencies. It also forces you to define sane inputs 16 | for every component, making them more predictable and easier to debug down the 17 | road. 18 | 19 | ![Component Playground](https://cloud.githubusercontent.com/assets/250750/8532005/e6d3b3bc-2433-11e5-9fc3-39a9288198e9.gif) 20 | 21 | _\*DX stands for Developer Experience, the counterpart of UX in building a product, system or service._ 22 | 23 | ### Requirements 24 | 25 | - [x] You should already be using CommonJS modules to structure your code and 26 | [webpack](http://webpack.github.io/) to bundle your modules for the browser 27 | - [x] Your React components should be fully encapsulated. They should have no 28 | global dependencies and rely exclusively on _props_ for input. Including styles, 29 | which means you need to be using 30 | [style-loader](https://github.com/webpack/style-loader). 31 | - [x] You must create component fixtures for ComponentPlayground to load. The 32 | component and fixture files should be nested as in the folder structure below. 33 | See the [example repo](https://github.com/skidding/cosmos-example) for a better 34 | picture. 35 | 36 | ### Installing 37 | 38 | - Install the Cosmos package through npm `npm install cosmos-js` 39 | - Run the ComponentPlayground executable `node_modules/.bin/component-playground` 40 | - Open [localhost:8989](http://localhost:8989) 41 | 42 | ### Under the hood 43 | 44 | Running the ComponentPlayground executable will: 45 | 46 | 1. Start a [webpack dev server](http://webpack.github.io/docs/webpack-dev-server.html), 47 | serving an instance of ComponentPlayground at `localhost:8989` 48 | 2. Scan the current folder for components and fixtures and feed them to 49 | ComponentPlayground 50 | 51 | #### File structure 52 | 53 | This is the file structure Cosmos expects: 54 | ``` 55 | | 56 | +-- components 57 | | +-- FooComponent.jsx 58 | | +-- namespace 59 | | | +-- BarComponent.jsx 60 | +-- fixtures 61 | | +-- FooComponent 62 | | | +-- default.js 63 | | | +-- active.js 64 | | +-- namespace 65 | | | +-- BarComponent 66 | | | +-- default.js 67 | | | +-- paused.js 68 | ``` 69 | 70 | If the _components_ and _fixtures_ folders are not siblings, their paths can be 71 | specified via cli args: 72 | 73 | ```bash 74 | node_modules/.bin/component-playground --components-path src/components --fixtures-path tests/fixtures 75 | ``` 76 | 77 | #### webpack config 78 | 79 | The webpack build bundles modules from both the current folder and the Cosmos 80 | package. It is compatible with React classes, ES6 classes, JSX and CSS/LESS 81 | modules [out of the box](component-playground/config.js#L34-L76), but you can 82 | customize the webpack config to support additional loaders and settings by 83 | creating a `component-playground.config.js` file in the project root. E.g. 84 | 85 | ```js 86 | module.exports.webpack = function(config) { 87 | config.module.loaders.push(/*...*/); 88 | return config; 89 | }; 90 | ``` 91 | 92 | You can use this functionality to inject external styles or scripts if your 93 | components need e.g. Bootstrap to work. 94 | ```js 95 | config.entry.push(path.join(process.cwd(), 'injectBootstrapTags.js')); 96 | ``` 97 | 98 | #### Hot loading 99 | 100 | Cosmos includes [React Hot Loader](http://gaearon.github.io/react-hot-loader/) 101 | and has webpack's [hot module replacement](http://webpack.github.io/docs/hot-module-replacement.html) 102 | enabled so you can tweak the components and their styles without refreshing the 103 | browser: 104 | 105 | ![React Hot Loader in Cosmos](https://cloud.githubusercontent.com/assets/250750/7526576/5c725b16-f51b-11e4-95ef-312c6fd7bcc7.gif) 106 | 107 | ### Thank you for your interest! 108 | 109 | [![Join the chat at https://gitter.im/skidding/cosmos](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/skidding/cosmos?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 110 | 111 | Explore the [Contributing Guide](CONTRIBUTING.md) for more information. 112 | --------------------------------------------------------------------------------