Giter Club home page Giter Club logo

asimov.js's Introduction

asimov.js

NPM version Build Status Code Climate Dependency Status

A better toolkit for building awesome websites and apps

Made by Adam Renklint, Berlin 2014. MIT licensed.

asimov.js is at its core only two simple things: a command line interface that loads and executes command scripts in your project and node_modules folder, and a plugin interface for adding initializers and extend the app lifecycle.

On it's own, asimov.js doesn't really do much - it leaves all the heavy lifting to plugins. The two most basic ones are asimov-server and asimov-static. Together you get an awesome static site generator with extendable, chained processing and a high-performance clustered server, with express-compatible middleware.

Or you roll your own plugin in a just a few seconds.

Getting started

Install asimov.js globally to use the cli

npm install -g asimov

Start your project and execute any registered initializers. This assumes your index.js exports a member function called start. More on that later.

asimov start

And any other command, you execute the same way. Later in this guide, you'll learn to build a command that counts lines of code.

asimov loc

Create a new project

So let's create our very first asimov.js app, a tiny thing that will load an initializer that logs a message.

Every app is also a plugin, meaning you can easily compose large applications from lots of smaller pieces of functionality, or publish your app to npm and let others use it as a plugin.

/asimov-log-message
  /lib
    /init
      logMessage.js
  index.js
  package.json

Install and add asimov.js to your projects dependencies.

$ npm install --save asimov

logMessage.js should export an initializer, a function that is passed a next function, which continues the chain.

// lib/init/logMessage.js
module.exports = function initializer (next) {
  console.log("Hello world");
  next();
};

Then, we add some bootstrap code in index.js.

var asimov = require('asimov');
var logMessage = require('./lib/init/logMessage');

// This is our plugin hook, the function that other modules
// can use to load our modules functionality
module.exports = function plugin () {
  asimov.init(logMessage);
};

// The "start" method should bootstrap your app
// by adding the plugin hook and starting asimov.js
module.exports.start = function bootstrap () {
  asimov
    .use(module.exports)
    .start();
};

// If we are not loaded as a plugin, start the app
module.parent || module.exports.start();

And run it...

$ asimov
> Hello world

asimov is short for asimov start, which is actually just calling node index.js.

Adding plugins

Every asimov.js app is also a plugin, and has the same interface as our app. In our projects plugin hook, we add all the plugins we need to get the job done. If we published the project above, we could add it in our next projects index.js.

var logger = require('asimov-log-message');
module.exports = function plugin (options) {
  asimov.use(logger);
};

Plugins can configure your app, add initializers and extend the public interface of asimov with new methods.

Development plugins that shouldn't be included when you app is loaded as a plugin are added in the bootstrap function.

var debuggerPlugin = require('asimov-some-debugger-plugin');
module.exports.start = function bootstrap () {
  asimov
    .use(module.exports)
    .use(debuggerPlugin)
    .start();  
};

Initializers

Initializers come in three flavours: vanilla, pre and post. For most apps and plugins, vanilla initializers will be enough, but in some case you might need to override the entire vanilla chain.

var overrider = require('./lib/init/overrider');
module.exports = function plugin (options) {
  asimov.preinit(overrider);
};

If you want your initializer to be executed last, register it with asimov.postinit().

var doneLogger = require('./lib/init/doneLogger');
module.exports = function plugin (options) {
  asimov.postinit(doneLogger);
};

Remember, you can break the chain at any time by not calling next() in your initializer, and create new, alternate chains. For example, this is how asimov-server can be used as both cluster master and worker, by having two very different chain of initializers.

Create a new command

Let's say we want to extend asimov.js with a new command that counts the lines of code in the lib folder. We could later publish it to npm, and use it in other asimov.js projects as a plugin.

Create a new module structure, and add lib/commands/loc.js - it will be loaded when you call asimov loc.

/asimov-loc
  /lib
    /commands
      loc.js
  index.js
  package.json

In lib/commands/loc.js, we add the code to recursively count the lines of javascript code in the lib folder.

var asimov = require('../../index');

function countLinesInPath (path) {
  // A function that recursively counts
  // the lines in all the javascript files
  // You'll need to figure that part out on your own
}

module.exports = function () {

  // Some basic setup
  var path = process.cwd() + '/lib';
  var namespace = 'loc';
  var started = new Date();

  // And get the count
  var count = countLinesInPath(path);

  // Log the result, and how long it took to count
  var message = 'Counted ' + count + ' lines in ' + path;
  asimov.logger.since(namespace, message, started);
};

If you publish this on npm, any other project that installs it to their node_modules folder will have the command asimov loc available, unless they create lib/commands/loc.js in their project.

Add and run sequences

You can add your own sequences to asimov.js, and let other projects hook into your processing. This is how asimov-server implements middleware and asimov-pages implements rendering processors.

asimov.addSequence('chain');
asimov.chain(myChainFactory());

asimov.runSequence('chain')
  .done(function (
    'yay! done.'
  ))
  .fail(function (err) {
    console.log('ooops', err)
  });

Configuration

You can change the behavior of plugins and configure your app with the asimov.config method.

// Set a configuration variable. Chainable.
asimov.config('myConfigVar', true);

// And get it.
var myVar = asimov.config('myConfigVar');

// Or get the whole config object.
var myConfig = asimov.config();

// Also supports constants, just uppercase the name.
// Trying to set it again will throw an error.
asimov.config('SOMETHING_CONSTANT', true);

// Use an object literal, or a JSON file. Also chainable.
var production = require('./env/production.json');
asimov.config(production);

Signals and rolling restarts

Out of the box, asimov.js supports zero-downtime updates with "rolling restarts", using POSIX signals.

For your convience, the master process' PID is saved in process.pid in your project root. This file is created when you start your app, removed when you stop it and can be used to send signals to asimov's master process.

To reload all code and restart your app, send SIGHUP to the master process.

$ kill -HUP $(cat process.pid)

Add shutdown handlers

You can register functions that will be executed when your app is being restarted or forcefully shutdown. These work exactly the same way as initializers, are executed in sequence and you can change or override the chain of side effects by not calling the next() callback.

asimov.shutdown(function (next) {
  // do some cleanup here
  next();
});

Register a public interface

Your app or plugin could need to register methods or variables on asimov's public interface. Chainable method.

asimov.register('doNothing', function () {});

For example, this is how asimov-pages exposes its main collection as asimov.pages.

Verbose logging

To get detailed logging on everything that happens in your app and plugins, set the env var VERBOSE to true. Or take the shortcut, call asimov debug.


Develop and contribute

  1. First, fork this repo.
  2. Implement something awesome
  3. Write tests and run them with npm test
  4. Submit a pull request

Credits

Author: Adam Renklint. Contributors: Marvin Labod, Timothy Achumba.

asimov.js's People

Contributors

adamrenklint avatar mlabod avatar

Stargazers

PsytecSoundz avatar Edward Cody avatar Crystal Silver avatar rchk avatar Javier Garmon avatar  avatar Bruno Gomes avatar  avatar Angus H. avatar Kostas Kapenekakis avatar Gareth Johns avatar Daniel Tarazona avatar Kevyworks avatar Michael Anthony avatar  avatar MO avatar Hidde van der Ploeg avatar Linkzero Tsang avatar Taufik Obet avatar Michael Richards avatar Christian Boyle avatar Paris Xavier Pinkney avatar Mathieu Lemaire avatar  avatar Niklas Palm avatar Mariusz Cieśla avatar casey cavanagh avatar Yuan Zhu avatar Ryan Glover avatar Kerry M avatar Dermot McGuire avatar Francesco Kirchhoff avatar Andrew-David J. avatar Gustavo Esquinca avatar Tair Asim avatar Jon B avatar Gerson Alexander Pardo Gamez avatar Jonathan Goode avatar mark prokoudine avatar Lachlan Campbell avatar Mateusz "Serafin" Gajewski avatar Lucifero Von Nachtosphere avatar Alistair Harris avatar Yueping Qian avatar Filipe Oliveira avatar  avatar Yoshi avatar Ferri Sutanto avatar Daijiro Wachi avatar João Paulo Pinheiro Teixeira avatar Chris Witko avatar Octavio Turra avatar Kadir Dogan avatar Alexander Kustov avatar Pablo Fierro avatar Gianluca Cherubin avatar Tom Wrenn avatar zain avatar Iheanyi Ekechukwu avatar The Dude avatar Rob Hunter avatar Tianlu Xue avatar Suleiman Leadbitter avatar Daniel Jacob Archer avatar Gonçalo Morais avatar Haris Pobric avatar David Simon avatar Kevin Rabinovich avatar  avatar Maciej Jurczak avatar Iqbal Perkasa avatar Diego avatar  avatar Damian Samolej avatar Rio Purnomo avatar  avatar  avatar

Watchers

 avatar James Cloos avatar Michael Anthony avatar Daniel Chong avatar Tristan Matthias avatar  avatar Sévag avatar

asimov.js's Issues

Refactor everything...

asimov.js is functional, but before there's too much mess of a code, let's take a step back and make it robust

  1. Implement integration/headless and unit (node + headless) spec runners for framework and app #2, #3
  2. Create a testable structure (content, site, custom middleware) in _asimov-framework/tests/tree" and change main.js load framework two levels up
  3. Empty loader - we're starting over
  4. Implement Model, Collection, (FilesystemCollection) with specs
  5. Refactor piece by piece, using models and collections, with tests for everything

Filesystem cache

Cache should persist to a temporary folder in the filesystem, that would be wiped between deploys but remain between restarts. On start, the cache should always try to read the filesystem cache and add, which would make the initial render of everything an "update", at least in terms of logging.

How to make this work with cluster?

Change the initial render to jump to random positions in the queue, then check the filesystem cache. If the file is already rendered by another worker, just read and set the cache. Otherwise render and and save to the filesystem cache.

But this means that on restart, since all files would exist, nothing would be re-rendered, even if it changed since the last time render and caching happened...

Initializers

Should be the first thing in Loader#bootstrap, hold other things if async, return deferred

Client application prototype

  • Using React and JSX
  • Create proxy class for React element
  • Should use controllers (mixins) to define behavior - a view is only rendering
  • Automatically inject and remove the jsx meta tag
  • Make use of Router, RouteController, AppView and at least two components, interacting

Inheritance in tests

Should be possible to re-use and extend a test by simply including it, and it will trigger all the parents tests before the tests in the subclassed file, with the subclass test topic as the subject.

Server interval logger

Every 15 minutes, log how long the server has been alive, how many requests have been received, and how many served - since the last log

Render plugins

How would a developer hook into the rendering, to change or add to it?

$ asimov deploy

If no Procfile, create one, make sure everything is commited, and push to heroku

$ asimov.js init

I thought about allowing install of a specific version, and have migrations up and down for the folder structure, but that's overkill. I should only support the latest minor version and a folder structure change should always mean a minor version

So just copy over the default folder structure, with rendered templates, and "asimov.js": "latest" in package.json.

Page "alias" attribute

In alias attribute, define one or more urls. When this url is requested, it should return a 302 Moved permanently and redirect to the current url.

combine asimov repos into one

asimov framework exported trough main.js, cli in bin/asimov, app-template in root to help test and simplify scaffolding
asimov-core ?
asimov.org website, powered by asimov

The framework should contain as much as possible in itself, except for core. Since the testing is tailored specifically for the asimov structure and thinking, I can build it in there and save some headache.

$ asimov edit :componentName

  • Start a server process, with a frontend that pulls in the component
  • Auto reload client process on on code change
  • Watch and run tests

A component should include one or more preview pages, like a theme or plugin, for demo purposes.

Themes & plugins

Should be able to load themes and plugins from npm, bower, mauer and GitHub

App environment config

use ENV var, load current config, if existing
configs can inherit from another by declaring {inherits: parentName}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.