Skip to main content Accessibility Feedback
Versions

Reef v6

A lightweight library for creating reactive, state-based components and UI. Reef is a simpler alternative to React, Vue, and other large frameworks.

Features

  • Weighs just 2.4kb (minified and gzipped) with zero dependencies.
  • Simple templating with JavaScript strings or template literals.
  • Load it with a <script> element or ES module import—no command line or transpiling required.
  • Uses DOM diffing to update only the things that have changed.
  • Automatically encodes markup in your data to protect you from cross-site scripting (XSS) attacks.
  • Work with native JavaScript methods and browser APIs instead of custom methods and pseudo-languages.
  • Supported all the way back to IE9.

Ditch that bloated framework, and make web development fun and simple again!

Why use Reef?

Reef is an anti-framework.

It does a lot less than the big guys like React and Vue. It doesn’t have a Virtual DOM. It doesn’t require you to learn a custom templating syntax. It doesn’t provide a bunch of custom methods.

Reef does just one thing: render UI.

Couldn’t you just use some template strings and innerHTML? Sure. But Reef only updates things that have changed instead clobbering the DOM and removing focus from your form fields. It also automatically renders a new UI when your data updates, and helps protect you from XSS attacks.

If you’re craving a simpler, back-to-basics web development experience, Reef is for you.

(And if not, that’s cool too! Carry on.)

Getting Started

1. Include Reef on your site

Reef comes in two flavors: standalone and polyfilled.

The polyfilled build uses the .polyfill suffix, and includes polyfills for Proxies and Custom Events, which are required for IE support.

CDN

You can also use the jsDelivr CDN.

<script src="https://cdn.jsdelivr.net/npm/reefjs/dist/reef.min.js"></script>

Reef using semantic versioning. You can grab a major, minor, or patch version from the CDN with the @1.2.3 syntax. You can find all available versions under releases.

<script src="https://cdn.jsdelivr.net/npm/reefjs@6/dist/reef.min.js"></script>

ES Modules

Reef now supports modern browsers and module bundlers (like Rollup, Webpack, Snowpack, and so on) using the ES modules import syntax. Use the .es version.

import Reef from 'path/to/reef.es.min.js';

More ways to install Reef

Direct Download

You can download the files directly from GitHub.

Compiled and production-ready code can be found in the dist directory. The src directory contains development code.

<script src="path/to/reef.min.js"></script>

CommonJS

If you use NodeJS, you can import Reef using the require() method with the .cjs version.

var Reef = require('path/to/reef.cjs.min.js');

AMD

If you use RequireJS, SystemJS, and other AMD formats, you can import Reef with the .amd version.

requirejs(['path/to/reef.amd.min.js'], function (Reef) {
  //...
});

NPM

You can also use NPM (or your favorite package manager).

npm install reefjs --save

2. Add an element to render your component/UI into

This is typically an empty div with a targetable selector.

<div id="app"></div>

3. Create your component

Create a new Reef() instance, passing in two arguments: your selector, and your options.

Provide a selector

The first argument is the selector for the element you want to render the UI into. Alternatively, you can pass in the element itself.

// This works
var app = new Reef('#app');

// This does too
var elem = document.querySelector('#app');
var app = new Reef(elem);

Provide a Template

The second argument is an object of options. It requires a template property, as either a string or a function that returns a string, to render into the DOM.

You can use old-school strings, or if you’d prefer, ES6 template literals.

// Your template can be a string
var app = new Reef('#app', {
	template: '<h1>Hello, world!</h1>'
});

// It can also be a function that returns a string
var app = new Reef('#app', {
	template: function () {
		return '<h1>Hello, world!</h1>';
	}
});

[Optional] Add State/Data

As an optional property of the options argument, you can include state for your component with the data property.

The data object is automatically encoded and passed into your template function, so that you can use it to customize your template.

// Some data
var app = new Reef('#app', {
	data: {
		greeting: 'Hello',
		name: 'world'
	},
	template: function (props) {
		return `<h1>${props.greeting}, ${props.name}!</h1>`;
	}
});

Template literals give you a simple, JSX-like templating experience. If you want, you can use old-school concatenated strings for more backwards compatibility.

4. Render your component

You can render your component by calling the .render() method on it.

app.render();

Try the demo on CodePen →

State Management

Reef uses data reactivity to update your UI.

Data reactivity means that the UI “reacts” to changes in your data. Update your data, and the UI automatically renders any required updates based on the new state.

// Create a component and render it
var app = new Reef('#app', {
	data: {
		greeting: 'Hello',
		name: 'world'
	},
	template: function (props) {
		return `<h1>${props.greeting}, ${props.name}!</h1>`;
	}
});
app.render();

// This causes component to update with "Hi, universe"
app.data.greeting = 'Hi';
app.data.name = 'Universe';

You can also update the entire data object.

// This will also update the UI
app.data = {
	greeting: 'Hi',
	name: 'Universe'
};

Try data reactivity on CodePen →

Updating data without reactivity

Sometimes, you want to update data without updating the UI.

You can get an immutable copy of your data using the clone() method. This creates a non-reactive copy of your data that won’t affect the state of your component.

var data = app.clone();

And starting with v6.1.0, you can immutably clone any array or object using the Reef.clone() method. Pass in the object to clone as an argument.

// Changes you make to data won't affect dataClone and vice-versa
var dataClone = Reef.clone(data);

Advanced Components

HTML in your data

Reef automatically encodes any markup in your data before passing it into your template to reduce your risk of cross-site scripting (XSS) attacks.

You can disable this feature by setting the allowHTML option to true.

Important! Do NOT do this with third-party or user-provided data. This exposes you to the risk of cross-site scripting (XSS) attacks.

var app = new Reef('#app', {
	data: {
		greeting: '<strong>Hello</strong>',
		name: 'world'
	},
	template: function (props) {
		return `<h1>${props.greeting}, ${props.name}!</h1>`;
	},
	allowHTML: true // Do NOT use with third-party/user-supplied data
});

Try allowing HTML in your data on CodePen →

Default and state-based HTML attributes

You can use component data to conditionally include or change the value of HTML attributes in your template.

In the example below, the checkbox is checked when agreeToTOS is true.

var app = new Reef('#app', {
	data: {
		agreeToTOS: true
	},
	template: function (props) {
		return `
			'<label for="tos">
				<input type="checkbox" id="tos" ${props.agreeToTOS ? 'checked' : ''}>
			</label>`;
	}
});

You might also want to use a default value for an attribute, but not change it based on your component’s state. You can do that by prefixing any attribute with default in your template.

In this example, option[value="hermione"] has the [selected] attribute on it when first rendered, but will defer to whatever changes the user makes when diffing and updating the UI.

var app = new Reef('#app', {
	data: {},
	template: function () {
		return `
			<label for="wizards">Who is the best wizard?</label>
			<select>
				<option value="harry">Harry</option>
				<option value="hermione" defaultSelected>Hermione</option>
				<option value="neville">Neville</option>
			</select>`;
	}
});

Nested Components

If you’re managing a bigger app, you may have components inside components.

Reef provides you with a way to attach nested components to their parent components. When the parent component is updated, it will automatically update the UI of its nested components if needed.

Associate a nested component with its parent using the attachTo key in your options. This accepts an array of components to attach your nested component to. You only need to render the parent component. It’s nested components will render automatically.

You can access a parent component’s state from a nested component by assigning the parent component data property to the data key in your nested component’s options.

// Parent component
var app = new Reef('#app', {
	data: {
		greeting: 'Hello, world!',
		todos: [
			'Buy milk',
			'Bake a birthday cake',
			'Go apple picking'
		]
	},
	template: function (props) {
		return `
			<h1>${props.greeting}</h1>
			<div id="todos"></div>`;
	}
});

// Nested component
var todos = new Reef('#todos', {
	data: app.data,
	template: function (props) {
		return `
			<h2>Todo List</h2>
			<ul>
				${props.todos.map(function (todo) {
					return `<li>${todo}</li>`;
				}).join('')}
			</ul>`;
	},
	attachTo: [app]
});

app.render();

Try nested components on CodePen →

Attaching and Detaching Nested Components

You can attach or detach nested components at any time using the attach() and detach() methods.

Both methods accept individual components or arrays of components as arguments.

// Attach components
app.attach(todos);
app.attach([todos]);

// Detach components
app.detach(todos);
app.detach([todos]);

Try attaching nested components on CodePen →

Shared State

There are two ways to handle shared state with Reef when your components (in addition to the nested component/parent component relationship documented above).

Source of Truth Object

You can associate a named data object with multiple components.

The biggest downside to this approach is that it’s non-reactive. You need to manually run the render() method on any component that needs to be updated when you update the state.

var sourceOfTruth = {
	greeting: 'Hello, world!',
	todos: [
		'Buy milk',
		'Bake a birthday cake',
		'Go apple picking'
	]
};

// Parent component
var app = new Reef('#app', {
	data: sourceOfTruth,
	template: function (props) {
		return `
			<h1>${props.greeting}</h1>
			<div id="todos"></div>`;
	}
});

// Nested component
var todos = new Reef('#todos', {
	data: sourceOfTruth,
	template: function (props) {
		return `
			<h2>Todo List</h2>
			<ul>
				${props.todos.map(function (todo) {
					return `<li>${todo}</li>`;
				}).join('')}
			</ul>`;
	},
	attachTo: [app]
});

// Initial render
app.render();

// Update the state
sourceOfTruth.greeting = 'Hi, universe';

// Re-render the DOM
app.render();

Try working with a single source of truth on CodePen →

Data Stores (aka Lagoons)

A lagoon is a Reef instance that’s only purpose is to reactively store shared data.

Create a lagoon by setting the lagoon option to true when creating your Reef instance. A lagoon doesn’t have a template of its own, but automatically updates the UI of any attached components when its data is updated.

var sourceOfTruth = new Reef(null, {
	data: {
		greeting: 'Hello, world!',
		todos: [
			'Buy milk',
			'Bake a birthday cake',
			'Go apple picking'
		]
	},
	lagoon: true
});

// Parent component
var app = new Reef('#app', {
	data: sourceOfTruth.data,
	template: function (props) {
		return `
			<h1>${props.greeting}</h1>
			<div id="todos"></div>`;
	},
	attachTo: [sourceOfTruth]
});

// Nested component
var todos = new Reef('#todos', {
	data: sourceOfTruth.data,
	template: function (props) {
		return `
			<h2>Todo List</h2>
			<ul>
				${props.todos.map(function (todo) {
					return `<li>${todo}</li>`;
				}).join('')}
			</ul>`;
	},
	attachTo: [sourceOfTruth, app]
});

// Initial render
app.render();

// Reactively update state
sourceOfTruth.data.greeting = 'Hi, universe';

Try creating a lagoon on CodePen →

The render event

Whenever Reef updates the DOM, it emits a custom render event that you can listen for with addEventListener().

The render event is emitted on the element that was updated, and bubbles, so you can use event delegation if you’d prefer.

The event.detail property includes a copy of the data at the time that the template was rendered.

document.addEventListener('render', function (event) {

	// Only run for elements with the #app ID
	if (!event.target.matches('#app')) return;

	// The data for this template
	var data = event.detail;

}, false);

Try the render event on CodePen →

Emitting your own custom events

Reef includes a helper function, Reef.emit(), that you can use to emit your own custom events in your apps.

Pass in the element to emit the event on and the event name as arguments. You can optionally pass in an object with event details as a third argument.

// Emit the 'partyTime' event on the document element
Reef.emit(document, 'partyTime', {
	msg: `It's party time!`
});

Debugging

By default, Reef fails silently. You can put Reef into debugging mode to expose helpful error message in the Console tab of your browser’s Developer Tools.

Turn debugging mode on or off with the Reef.debug() method. Pass in true to turn it on, and false to turn it off.

// Turns debugging mode on
Reef.debug(true);

// Turns debugging mode off
Reef.debug(false);

Demos

Want to see Reef in action? Here are some demos and examples you can play with.

Changelog

  • Component data is now directly reactive.
  • The setData() and getData() methods (previously used for reactivity) have been removed.
  • You can get an immutable copy of your data with the clone() method.
  • Added an emit() method for emitting custom events.
  • IE support now requires polyfills or transpiling.

Browser Compatibility

Reef works in all modern browsers, and IE 9 and above.

License

The code is available under the MIT License.