Component-Based Development

Velocity adopts JSX, a syntax extension for JavaScript, in its server-side rendering. Familiar to developers through libraries like React, JSX combines the versatility of JavaScript with an HTML-like syntax, resulting in a powerful tool for constructing component-based interfaces. However, in Velocity, we use JSX independently from React, focusing purely on server-side rendering without hooks or lifecycle methods.

We employ esbuild for transpiling our JSX code into standard JavaScript, benefiting from its speed and efficiency. Even though JSX is not valid JavaScript, esbuild allows us to leverage its excellent tooling without needing to incorporate React into our project.

What is JSX?

JSX resembles HTML with its tag-based structure, allowing developers to nest tags and assign properties (or "props") to them. However, unlike HTML, JSX is case-sensitive and has stricter syntactic rules.

While it might be best known as part of the React ecosystem, JSX does not inherently depend on React. It was initially developed by Facebook to provide an easier way to create complex React.createElement() calls, but it can also be used separately, offering an easier way to write component-based UIs.

For instance, instead of writing:

React.createElement('a', { href: 'https://example.com' }, 'Hello, world!');

You can use JSX to express the same idea more concisely:

<a href="https://example.com">Hello, world!</a>

Implementing JSX

At its core, JSX is relatively simple. Consider this rudimentary implementation of a JSX-like function:

function createElement(type, props, ...children) {
  const propString = props
    ? Object.entries(props)
        .map(([key, value]) => `${key}="${value}"`)
        .join(' ')
    : '';
  const childrenString = children.join('');
  return `<${type} ${propString}>${childrenString}</${type}>`;
}

In the createElement function above, we take the type of the element (for example, 'div'), its properties, and any child elements to generate HTML strings.

However, in Velocity, our implementation extends beyond this simplicity to incorporate sanitation, performance enhancements, and the ability to use asynchronous components. Asynchronous components enable the use of await and Promises within components to access APIs, significantly increasing the flexibility of our server-side rendering.

Building an Example

Let's create a simple page that displays a list of product names fetched from an API:

import page from '~/utilities/page';

export const ProductListPage = page`/products`((request) => (
  <html>
    <body>
      <h1>Product List</h1>
      <ProductList />
    </body>
  </html>
));

const ProductList = async () => {
  const products = await fetchProductsFromApi(); // an async operation
  return (
    <ul>
      {products.map((product) => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>
  );
};

Here, ProductList is an asynchronous component. Upon invocation, it initiates a call to fetchProductsFromApi(), an asynchronous operation that returns a promise resolving to an array of products. Each product is then transformed into an li element, forming a list of product names.

This synergistic use of JSX and asynchronous components lets us construct dynamic, server-side rendered pages that integrate data fetched from APIs.

Safely Injecting HTML with unsafe

When dealing with user-generated or external content, it's essential to sanitize this content before rendering it into the page. Sanitization prevents XSS (Cross-Site Scripting) attacks, where malicious actors inject scripts into webpages viewed by other users.

However, there may be legitimate use cases where you need to include unescaped HTML in your rendered output. For example, you may want to include an inline script tag, or embed some HTML formatted content fetched from an API.

In such scenarios, Velocity provides the unsafe function, which allows you to include unsanitized content in your rendered HTML. Here's an example:

import { unsafe } from '@nanoweb/jsx';

const UnsafeComponent = () => (
  <html>
    <body>
      This will be escaped...
      {'<script>alert("Hello XSS!");<script>'}
      ...while this will open an alert dialog box
      {unsafe('<script>alert("Hello XSS!");<script>')}
    </body>
  </html>
);

In the example above, the unsafe function is used to embed a script tag directly into the output HTML. The script enclosed within unsafe will be sent to the client without being escaped, and so it will be executed when the HTML is rendered in the browser.

It's crucial to be extremely cautious when using the unsafe function. Remember that any content passed through unsafe will be directly included in your output HTML without any sanitization. If this content is in any way derived from user input or an external source, you could be exposing your application to an XSS vulnerability. Always sanitize any user-generated or external content before rendering it, and only use unsafe when you're certain that the content you're including is safe.

As its name implies, unsafe should be your last resort when it comes to including unescaped content in your HTML. Use it sparingly, and always with a full understanding of the risks involved.

Powered by Doctave