npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

not.js

v0.1.0

Published

A simple, html template dsl.

Downloads

13

Readme

not.js Build Status

not.js is a DSL written in javascript, for html. It allows you to easily define html without leaving the comfort of your own javascript file. It's all just valid javascript... but... not.

Usage

######NOTE: Ensure any node app using not.js has access to Proxy objects - this can mean running with --harmony or other flags depending on the node version in question

The tests are a great place to look for many examples, but here's the gist of it: implied.not.js:

module.exports = function() { //Export a plain js function
  h1; $($scope.title); $h1 //Write your tags, seperated by semicolons if on the same line
  ul({class: 'un-list'}) //Pass attributes by calling the tag
  for (var scratch in $scope.items) {
    if ($scope.items.hasOwnProperty(scratch)) { //Do logic wherever

      li.item //Chain classes off tags
        $('Item: '+$scope.items[scratch]) //Write text nodes with $
      $li

    }
  }
  $ul //Write close nodes by ending the tag with a $
};

You'll notice that virtually nothing in that function is defined within that file. How is this still valid, you ask? Magic. And dynamic runtime environment overrides. Mostly magic.

Tags

All top-level identifiers aside from $scope and $ are interpreted as tags. Bare identifiers are written as opening tags. Identifiers ending with $ are interpreted as self-closing tags. Identifiers starting with $ are ending tags. 'Call' a start or self-closing tag with an object to specify any attributes you'd like on the tag. The $ function acts as a text node - strings you pass are escaped. You may instead pass a function with a comment inside it, which will be interpreted as a multiline string. Passing 'true' as the second argument disables escaping. Multiple tags can go on one line if separated by a semicolon.

Classes

Chaining off a tag or a called tag will result in classes being added to that tag. div({class="col-md-6"}).content.main -> <div class="col-md-6 content main">

Logic

Just write javascript. Really; write any javascript inline with your markup and it just works (tm).

Known exceptions to 'just working': var statements don't function immediately within an explicitly defined context. Make an IIFE within the explicit with (like in the first test) to get around the problem, or use implied contexts. Or don't use locals.

Scope

If using express, pass an object to the 'scope' field on the options object to reveal that object as '$scope' within the dsl call. Otherwise, your API looks like so:

  • string: builder function used for outputting as a string
  • create([builder]): Outputs a proxy object for use - see test/html.js for usage.
  • prepareFunc(func, [builder]): Outputs a function that when called with a scope object, returns the builder's result
  • renderFunc(func, [scope], [builder]): Shortcut for prepareFunc(func, builder)(scope)
  • renderPath(path, [options], callback): Connect-standard view engine callback (aliased to __express). Valid options are:
{
    "explicit": boolean, //If truthy, it no longer assumes you want
                         // your function implicitly dsl-ified, and
                         // simply calls the required function with
                         // the scope, expecting a string in return
                         // defaults to false

    "builder": builder, //Optional builder function to output as
                        // some alternate format (like DOM nodes)
                        // defaults to string

    "scope": object //Object to expose within the dsl for use in logic
                    // defaults to {}
}

Shortcuts

There are some convenient shortcuts builtin for things the usual syntax makes awkward:

-include

$(notjs.renderPathSync(path, $scope), true)

as

- include(path)

This will directly include the template at the path in question at that point in this template, using the current scope object. It does not support asynchronously resolving templates.

-render

$(notjs.renderFunc(func, $scope), true)

as

- render(func)

Renders a function into the current output using the current scope.

-using

- using(name)

This has no simple translations. Any content following the using, rather than being written to the output buffer, is written to $scope[name]. It's useful for making a layout or placeholder based structure, enabling things like

-using('headers')
title; $('Whatever'); title;

-using('body')
div.container
  h1; $('Content area'); $h1
$div

-include('/layouts/main.not.js') //Where the included template inserts $scope.headers and $scope.body

-done

- done

A - using and a -include both implicitly call a - done internally. - done signals the end of a using block.

-doctype

$('<!DOCTYPE '+value+'>', true)

as

- doctype(value)

Just a shorthand for writing out a doctype.

-script

script({src: value}); $script;

as

- script(value)

A shorthand for writing out a standard script tag.

-comment

$('<!-- ', true);
<tags as normal>
$(' -->', true);

as

- comment
<tags as normal>
- $comment

A shorthand for using html comments, if you need to template those for some reason.

The general form is - <shortcut>[(<args>)]. The - is the unary minus operator - if automatic semicolon insertion fails you (and it will), end the prior line with a semicolon by hand to ensure it is interpreted as unary minus. If you fail to do so, the interpreter has a penchant of doing things out of the order you anticipated, yielding unexpected results. Additionally, the ~ and + unary operator should function identically - so - comment and ~comment should both be acceptable. Use whatever operator suits your preference, but I recommend you at least stay consistent.

Suggestions for ease of use

The implied context by default is nice, however, that means the following silly example does not function:

var http = require('http');
module.exports = function() {
    http.get('http://www.github.com', function(cli) {
        var sink = '';
        cli.on('data', function(data) {
            sink += data;
        });
        cli.on('end', function() {
            $(sink, true);
        });
        cli.on('error', function(e) {
          html
            head
              meta({title: JSON.parse(e)})
            $head
            body
              $(JSON.parse(e));
            $body
          $html
        });
    });
};

Since http (and JSON) will be overridden by the implicit context. You have two options:

  • Export the desired functions into the $scope
  • Utilize explicit contexts for complicated files

On a per-file basis, the easiest is with 'explicit' contexts, like so:

var http = require('http');
var Promise = require('promise');
var notjs = require('not.js');

module.exports = function(scope) {
  var context = notjs.create();

  var promise = new Promise(function(resolve, reject) {
    http.get('http://www.github.com', function(cli) {
      var sink = '';
      cli.on('data', function(data) {
        sink += data;
      });
      cli.on('end', function() {
        context(scope).$(sink, true); //One statement, don't bother actually making a context

        resolve(context.collect());
      });
      cli.on('error', function(e) {
        scope.error = JSON.parse(e);
        with (context(scope)) { //Make a context - you may invoke this multiple times (and with different contexts)
          html
            head
              meta({title: $scope.error})
            $head
            body
              $($scope.error);
            $body
          $html
        }

        resolve(context.collect()); //The total results are returned when .collect() is called
                                    //(and can be reset with a call to .restart())
      });
    });
  });

  return promise; //For your convenience, the renderPath function will handle promises
};

The result object is built as tags appear. Don't mix async and sync calls - The template will probably build in an unexpected order. Use promise chaining or the like to execute chunks of async template in a known order.

Doing the same thing with a lot of things in the scope object but still within an implied context: Scope object passed in the options object:

{
    Promise: require('promise'),
    http: require('http'),
    JSON: JSON
}

not.js File:

module.exports = function() {
  var promise = new $scope.Promise(function(resolve, reject) {
    $scope.http.get('http://www.github.com', function(cli) {
      var sink = '';
      cli.on('data', function(data) {
          sink += data;
      });
      cli.on('end', function() {
          $(sink, true);
          resolve(); //The return value of implied functions isn't actually used
      });
      cli.on('error', function(e) {
        var error = $scope.JSON.parse(e);
        html
          head
            meta({title: error})
          $head
          body
            $(error);
          $body
        $html
        resolve();
      });
    });
  });

  return promise; //Implied functions that return promises have their .proxy attribute
                  //set to the proxy generated for them - call .collect() on the proxy
                  //to get the result string when the promise resolves
                  //eg. promise.then(function() { return promise.proxy.collect(); })
                  //    .then(function(rendered) {doStuff(rendered);});
};