solidusjs / solidus

A simple server that generates pages from JSON and Templates
MIT License
28 stars 7 forks source link

Routers, or preprocessors fail under pressure when using Crossroads.js #121

Closed pushred closed 9 years ago

pushred commented 9 years ago

Being able to require other CommonJS modules has really helped to avoid duplicating preprocessor code. But as code is added, organization begins to be a concern. Matching code to views also gets tricky, often requiring additional "parent" files, sometimes created one-to-one with views. This is also a problem on the frontend, where developers often either rely upon unmatching DOM selectors (creating unnecessary overhead) or pseudo-routing like checking for the existence of ID and class attributes on a <body> tag. Sometimes this extends to adding data attributes for various route parameters that other code may be dependent upon.

As we've discussed, routers seem to provide a solution to these challenges, both organizationally and technically. It'd be great if a single router could be used both server and clientside for a consistent way to organize JS, which becomes particularly important when rendering some things clientside. So I wanted to try out one that I like to use in the browser that also supports node: Crossroads.js. It's got at least a couple attributes that make it a theoretically nice fit in Solidus:

Director is another promising option, but it uses the :rails style syntax for parameters that I'd rather not muddle our own conventions with. Crossroads is also working fine with one particularly nasty gotcha: preprocessors seem to stop running if I request the page too quickly in succession. If I hold back for ~1 minute or so they begin to work again. So I'm wondering if Crossroads and #102 are conflicting on some level.

Here's how I'm using Crossroads:

index.js

var crossroads = require('crossroads');

var albums = require('./photos/albums');
var album = require('./photos/album');
var photo = require('./photos/photo');

var views = {

  "/photos": albums,
  "/photos/{folder}": albums,
  "/photos/{folder}/{album}": album,
  "/photos/{folder}/{album}/{photo}": photo

};

module.exports = function( context ){

  Object.keys(views).forEach(function( route ){
    crossroads.addRoute(route, function(){
      views[route](context);
    });
  });

  crossroads.parse(context.url.path.replace('.json',''));

  return context;
};

photos.hbs

{
    "preprocessor": "index.js"
}
joanniclaborde commented 9 years ago

The problem is not pressure, it's a Crossroads "feature". Here's what's happening: each time a preprocessor is needed (index.js), a process worker is chosen from a pool of 4 workers. This means you eventually end up with 4 different instances of Crossroads (Node caches require('crossroads') in each process). So when you request a preprocessor, if you end up using an already initialized worker, the parse method doesn't trigger any routing because of that ignoreState flag.

Possible solutions:

  1. Set ignoreState to true, or use resetState(). Note that you should check that the routes do not already exist before adding them (if (crossroads.getNumRoutes() == 0) ...). A better alternative would be add the routes once, outside the exported function. You can send the current context to the handler with crossroads.parse(context.url.path.replace('.json',''), [context]);
  2. Use a new router instance in every call, instead of reusing the shared crossroads variable:

    module.exports = function( context ){
     var router = crossroads.create();
     // ... (use router instead of crossroads)
joanniclaborde commented 9 years ago

Here's a working fix:

var crossroads = require('crossroads');

var albums = require('./photos/albums');
var album = require('./photos/album');
var photo = require('./photos/photo');

var views = {

  "/photos": albums,
  "/photos/{folder}": albums,
  "/photos/{folder}/{album}": album,
  "/photos/{folder}/{album}/{photo}": photo

};

crossroads.ignoreState = true;

Object.keys(views).forEach(function(route) {
  crossroads.addRoute(route, function(context) {
    views[route](context);
  });
});

module.exports = function(context) {
  crossroads.parse(context.url.path.replace('.json',''), [context]);

  return context;
};