Skip to content

Latest commit

 

History

History
323 lines (261 loc) · 11.6 KB

README.md

File metadata and controls

323 lines (261 loc) · 11.6 KB


Lunar v1.0.1. Making the core of your application framework-independent.

npm GitHub tag Build Status Codacy Badge

Tested on:
OS X Chrome 48, 49, Firefox 44, Safari 8 and 9, Opera 12.15.
Win 8 Firefox 44, Chrome 49, IE 9, 10 and 11.
iPhone 6 and Galaxy S5.

Index

Installing

  • Run npm install lunarjs --save
  • Alternatively, download the lunar/dist/lunar.js file and place it in your project.
  • Lunar is under UMD. You can require, import it, or use it as a global variable
const Lunar = require('lunarjs'); // or
import Lunar from 'lunarjs'; // or global variable
console.log(Lunar);

const actions = { ONE: 'ONE' };
const MyFeature = Lunar({...actions}).createModule();
const ReactComponent = Lunar(this).createActivator([MyFeature,...])
ReactComponent.addMiddleware([...]) // before and after middlwares
ReactComponent.request[actions.ONE]().then()

// You can add an additional proxy layer
const AngularService = Lunar(this).createProxy(MyFeature)
AngularService.addMiddleware([...]) // before and after middlwares
const AngularController = Lunar(this).createActivator([AngularService,...])
Directive.request[actions.ONE]().then()

Back to top

Inspiration, fancy names

We were inspired by those amazing tools and philosophies:

Why not have the decoupling strategy that already exists between Enterprise Applications, inside your own Single Applications, between your Business Logic and Frameworks? :)
Back to top

MVC, Flux and other frameworks, the problem: Refactoring

Usual frameworks propose a good enough separation of concerns by the three major areas that we deal with:

  • Data
  • Logic
  • User Interface

They get separated into Model Controller and View, which is far better than the mixed cake that we rarely see nowadays (hopefully). We can say that from bottom up, code change less, progressively: View changes a lot, Controller changes often and Models change rarely. Best case scenario those 3 are decoupled in a way we can reuse M, V and C. Often, we need to do parallel actions and mix Controllers features. That is when things get complicated.

Functional, Reactive programming and alike means a tremendous evolution compared to MVC, when regarding scaling and long-term projects. If you have a entrance point of data, you just transform or project it in the way you want, through functions until it gets rendered for the user. But the main concern we are trying to deal with here is still in place: Refactoring.

Refactoring is a reality, specially when developing big and long lasting applications. The benefits from Client-Side applications are very clear to us but the number of frameworks and their updates rain on us every week. We might feel tempted to test or do proof of concepts on different platforms, frameworks and philosophies but it seems too difficult. We can stay stuck into the same old application for years.
Back to top

What we propose

We just have one concern here, the C of traditional MVC, the Angular's Service or Redux Reducer. They usually holds the Business Logic of your application -- what distinguish your product from others. Frameworks do have their role, and makes view rendering and model sincronization a lot easier. It would just be even easier for us if the Business Logic could be decoupled from tools and easily migrated when needed. So, use any framework or library you want, but keep your core code agnostic.

First, you should separate "framework-code" from "application-code". Frameworks should deal with HTTP requests, view/templaing/virtual-dom rendering, data synchronization and/or database integration if needed. Application should be just functions, pure functions in the best case scenario. Let's see an example.
Back to top

How is Lunar different than Redux?

Redux is awesome. As the official documentation states, "Redux is a predictable state container for JavaScript apps." and "evolves the ideas of Flux, but avoids its complexity by taking cues from Elm.". Redux is an architecture and it is a state container. Lunar doesn't worry about state or how you structure your application. The difference between them is design. Lunar only concern is how to decouple business logic from frameworks, it doesn't manage state, you can even use Lunar with Redux.

Back to top

Structure

/client  
    /lunar  
    /angular

The lunar folder

/lunar
	/folder-by-feature
	/eg-home
	/simulator
  • Your files will host all functions related to a specific feature.
  • Additionally, each feature have a set of public actions.
  • If writting tests, add your .spec file in there too.
/lunar/home
	home.js
	home.spec.js
	actions.js

The feature main file

You can divide this file into multiple modules as needed, but all functionalities should come into one file at the end.

Your file will look something like this:

// Import your actions constant file
import Lunar from 'lunarjs';
import actions from './actions';

// Your core feature code, an object with pure functions
export default Lunar({
	// You can have private properties if you want
	title: 'Lunar',
	actions: actions,
	[actions.FORMAT_TITLE]: function(data) {
		return data + ' ' + this.title;
	},
	[actions.INCREMENT]: function(data) {
		return ++data;
	},
	[actions.DECREMENT]: function(data) {
		return --data;
	}
}).createModule();

The actions file

Refactors happen all the time. In order to change your code in just one place we use actions constants that will set and get functions for you. All public functions from the application that you want the framework to be able to access should have an action:

export default {
	FORMAT_TITLE: 'FORMAT_TITLE',
	INCREMENT: 'INCREMENT',
	DECREMENT: 'DECREMENT'
};

Back to top

React Example

We know that React concerns only the V (sort of) of our apps. After you create an Lunar module, you should have an endpoint where you call it. This can be the Component it self.

import Lunar from 'lunarjs';
import Home from 'lunar/home';

React.createClass({
	getInitialState() {
		return {
			title: ''
		}
	},
	componentDidMount() {
		var self = this;
		Lunar(this).createActivator([Home]);

		this.request[Home.actions.FORMAT_TITLE]('Welcome').then(function(data) {
			self.setState({
				title: data
			});
		}, function(err) {
			console.log('Error: ', err);
		});
	},
	render() {
		return (
			React.createElement('h1', null, this.state.title)
		);
	}
});

Back to top

Angular Example

The Angular team saw that M, V and C are not enough for code decoupling. They added a few extra things like Services, Factories and Providers. Whenever you have code that you will reuse in multiple places, put them into Services. This is very helpful but still coupled. Refactoring means changing your Service, Controller and Directive at least.


Open full size version

Let's assume that you consider the Angular Service the holder of logic. Controller the scope provider and the Directive where actions get fired. In the previous example we had your Lunar module and an Activator where you fire your actions. If you have/need a layer in between day you can create what we call Proxy. In Angular's architecture that would be in your Service.

import Home from 'lunar/home';

// Service
// After .extend, the service can add middlewares
// And have the actions and methods built into itself
// through the Lunar factory
angular.module('app.home')
    .service('HomeService', HomeService);

function HomeService($http) {
    Lunar(this).createProxy(Home);

	// This is optional.
	// You might want to execute something before
	// your core code is run, or after.
	this.addMiddleware({
		action: Home.actions.FORMAT_TITLE,
		before: function(data) {
			// Lunar will send the Promise
			// result to your code.
		    return $http({
		        method: 'GET',
		        url: 'http://localhost:4000/posts'
		    });
		}
	});

	return this.service;
}

HomeService.$inject = ['$http'];
// Controller
// The controller doesn't know about Lunar,
// it just uses the service.
// After .extend, it gets the methods bound into its scope.
angular.module('app.home')
    .controller('HomeController', HomeController);

function HomeController(HomeService) {
    var vm = this;

    vm.title = 'Hello.';

    Lunar(this).createActivator([HomeService]);
}

HomeController.$inject = ['HomeService'];
// Page template
// We pass methods (request) to the directive.
header(title="vm.title", request="vm.request")
// Directive
// The directive just calls the method which return a promise.
angular
    .module('app.header')
    .directive('header', HeaderDirective);

function HeaderDirective() {
    var directive = {
        templateUrl: '../components/header/header.html',
        link: function (scope, element, attr) {
            var actions = scope.actions;

            scope.getTitle = function(params) {
                scope.request[actions.FORMAT_TITLE](params).then(function(data) {
                    scope.title = data;
                    if (!scope.$root.$$phase) scope.$digest();
                }, function(err) {
                    console.log('Error: ', err);
                });
            }
        },
        scope: {
            title: '=',
            methods: '='
        },
        restrict: 'E'
    };

    return directive;
}

Want to know more? Head to the wiki to see API explanations, React, Backbone and other examples.
Back to top

Contributing? Development instructions

$ make sdocker

Starts Docker default machine, if you are on Mac.

$ make setup

First time only image setup.

$ make up && docker exec lunar npm run dev

Starts container and run watch tasks.

See the makefile to see available commands such as unit, integration tests and others.
Back to top