Code Monkey home page Code Monkey logo

bladejs's Introduction

BladeJS

An implementation of Laravel's templating engine in JavaScript. Try it live here!

Note: This was written purely for fun, and although it seemingly works okay, please do not actually use it. There are many good, well tested templating engines already in existence. The below docs are only a reminder for me of how the thing works.

Usage

Note: Blade.code, Blade.template, and Blade.render all take the same options object.

Rendering a string

You can render a string through Blade.render. Note that the process of compiling a template may be quite slow. It is much more performant to precompile a template.

const Blade = require('bladejs');

const data = '@foreach(users as user){{ user.name }}@endforeach';
const locals = { users: [] };
const options = {
	debug: true
};

const html = Blade.render(data, locals, options);

Creating a template function

You may create a template function through the Blade.template function. This requires loading in the entire Blade compiler, just as the previous does.

const customDirectives = {
	markdown: function (contents) {
		return marked(contents);
	}
};

const data     = `@if(user)Hello {{ user.name }}@endif`;
const options  = { customDirectives };
const template = Blade.template(data, options);

const locals   = { user: 'You' };
const html     = template(locals);

console.log(html); // Hello You

Precompiling templates

You may precompile a template using Blade.code and the standalone option. Setting the standalone option to true will inline the two functions needed by the template function which would otherwise be passed in for you when using Blade.render.

Note: Blade.code will throw an error if the compiled code somehow generates a syntax error.

const fs = require('fs');
const marked = require('marked');

const options = { standalone: true };
const code = 'module.exports = ' + Blade.code(data, options);

const customDirectives = {
	markdown: function (contents) {
		return marked(contents);
	}
};

fs.writeFileSync('./outputpath', code);

const template = require('./outputpath');

// You need to pass in the custom directives manually!
const html = template(locals, customDirectives);

Note: The customDirectives need to passed to the template function as a second argument.

Creating custom directives

You may create custom directives by passing them in with the options. Note that if you precompile the template, you need to pass it in as the second argument to the template function.

const customDirectives = {
	markdown: function (expression, contents) {
		if (contents !== undefined) {
			return marked(contents);
		} else if (expression !== undefined) {
			return marked(expression);
		}
	}
};

const options = {
	customDirectives: customDirectives
};

Blade.render(data, locals, options);
// or
const template = Blade.template(data, options);
// or with a precompiled template
const template = require('./precompiled-template');
const html     = template({ user: 'You' }, customDirectives);

Three parts to a custom directive

A custom directive has three parts, two of which are optional. The first is the directive itself e.g. @markdown. The second is the argument to the directive. For example, 'Hello' is the argument to the markdown directive in @markdown('hello').

Note: Although the argument is optional, you can not have an empty argument, e.g. @markdown() will give an error.

The second part is the closing directive, which would be @endmarkdown. You can use arguments with or without the closing block. All of the following are valid, and will cause the callback for the directive to be called.

@markdown

<!-- with argument -->
@markdown('content')

<!-- close directive without argument -->
@markdown
@endmarkdown

<!-- closing directive with argument -->
@markdown('Hello')
@endmarkdown

The first parameter to the callback (named expression) is the argument passed to the directive, just as it would be in Laravel. The second parameter (named contents) is the rendered contents of the directive's block. If either one was omitted from use in the directive, they will be passed as undefined.

const customDirectives = {
	markdown: function (expression, contents) {
        if (contents !== undefined) {
            return marked(contents);
        } else if (expression !== undefined) {
            return marked(expression);
        }
	}
};

const options = { customDirectives };

// expression will be '### Title'
Blade.render(`@markdown('### Title')`, options);

// expression will be 123 and contents will be '### Title'
Blade.render(`@markdown(123)### Title@endmarkdown`, options);

// expression and contents are both undefined
Blade.render(`@markdown`, options);

Benchmarks

Hogan.js
  Escaped   : 3508ms
  Unescaped : 251ms
  Total     : 3759ms

EJS
  Escaped   : 3981ms
  Unescaped : 2275ms
  Total     : 6256ms

Handlebars.js
  Escaped   : 1630ms
  Unescaped : 438ms
  Total     : 2068ms

Pug without `with`
  Escaped   : 1688ms
  Unescaped : 361ms
  Total     : 2049ms

Pug
  Escaped   : 3062ms
  Unescaped : 94ms
  Total     : 3156ms

Blade
  Escaped   : 3087ms
  Unescaped : 125ms
  Total     : 3212ms

Dependencies

  • Native node module path.

  • blade-expression, which is a JavaScript expression parser that I wrote, which has only three dependencies object-assign, repeat-string, and pad-start

  • object-assign

  • pad-start

  • escape-string-regexp

Altogether, blade.min.js comes in at 66.75 KB.

Todo

  • Figure out why the minified version is throwing errors.

  • Change tests to compare by files.

  • Add tests for relative inlcude paths

  • Add tests for dependecy resolving

  • Change escape to be an identifier instead of a member expression on helpers.

  • Add second argument to @include

  • Add second argument to @section

  • Add second argument to @yield

  • Add support for escaped directives and interpolation

  • Add @foreach(users as key => value)

  • Add caching support

  • Add streaming support

  • Maybe use functions for includes?

  • Change TextNode type to just Text

  • Add caching in the compiler for expression ASTs

  • Should the output of custom directives be escaped?

  • The debug mode check of identifiers everytime an identifier is used could probably just be swapped for a check at the beginning of the function. The check would validate that all identifiers found are in locals. The current way probably doesn't work anyway with different conditional branches.

bladejs's People

Contributors

anthonykoch avatar

Watchers

 avatar

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.