Skip to content
This repository has been archived by the owner on Mar 27, 2022. It is now read-only.

Creates v2.0.0 - New Rendering API #140

Merged
merged 4 commits into from Oct 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,16 @@
# 2.0.0

Breaking changes introduced with rendering modes - `renderAndReplace` is now called `render` to follow common practice with SPA frameworks rendering mechanisms. Additionally, the render locations below are more explicit on where they will place the JSX output. Finally, prepend and append now support top-level JSX fragments (before and after render locations require a top-level container element still).

New methods:
```js
renderBefore(<Hello name="world" />, targetElement);
renderPrepend(<Hello name="world" />, targetElement);
render(<Hello name="world" />, targetElement);
renderAppend(<Hello name="world" />, targetElement);
renderAfter(<Hello name="world" />, targetElement);
```

# 1.1.1

Sadly the previous release had old code in it, this release fixes it.
Expand Down
38 changes: 27 additions & 11 deletions README.md
Expand Up @@ -2,14 +2,27 @@

[![Build Status](https://github.com/bitboxer/jsx-no-react/actions/workflows/node.js.yml/badge.svg?branch=main)](https://github.com/bitboxer/jsx-no-react/actions/workflows/node.js.yml)

`jsx-no-react` makes it possible to use React's JSX syntax outside of React projects.
`jsx-no-react` makes it possible to use React's JSX syntax outside of React projects. Using `renderBefore` and `renderAfter` requires a modern browser supporting `Element.insertAdjacentElement()` - all other render modes function in legacy browsers.

## Installation

```sh
yarn add jsx-no-react
```

### Upgrading

2.0.0 introduces breaking changes with rendering modes - `renderAndReplace` is now called `render` to follow common practice with SPA frameworks rendering mechanisms. Additionally, the render locations below are more explicit on where they will place the JSX output. Finally, prepend and append now support top-level JSX fragments (before and after render locations require a top-level container element still).

New methods:
```js
renderBefore(<Hello name="world" />, targetElement);
renderPrepend(<Hello name="world" />, targetElement);
render(<Hello name="world" />, targetElement);
renderAppend(<Hello name="world" />, targetElement);
renderAfter(<Hello name="world" />, targetElement);
```

### Usage in Babel

You'll also need to hook the `jsxElem` function into the JSX transformation, for which you should probably use [babel](https://www.npmjs.com/package/@babel/preset-react), which you can install and setup fairly simply:
Expand Down Expand Up @@ -114,10 +127,11 @@ render(<Hello name="world" />, document.body);
```

There are several ways to render an element:
- `render`: which renders an element just after the beginning of the parent element
- `renderBeforeEnd`: this function renders the JSX element before the end of the parent element.
- `renderAfterEnd`: this function renders the JSX element after the end of the parent element.
- `renderAndReplace`: this function renders the JSX element on parent element by replacing the content.
- `renderBefore`: this function renders the JSX element before the target - top level JSX element must not be a fragment.
- `renderPrepend`: this function renders the JSX element within the target element, prepending existing content in the target element.
- `render`: replaces the contents of the target element with the JSX element.
- `renderAppend`: this function renders the JSX element within the target element, appending existing content in the target element.
- `renderAfter`: this function renders the JSX element after after the target element - top level JSX element must not be a fragment.

```javascript
import jsxElem, { render, renderAfterEnd, renderBeforeEnd, renderAndReplace } from "jsx-no-react";
Expand All @@ -126,10 +140,11 @@ function Hello(props) {
return <h1>Hello {props.name}</h1>;
}

renderBefore(<Hello name="world" />, document.body);
renderPrepend(<Hello name="world" />, document.body);
render(<Hello name="world" />, document.body);
renderAfterEnd(<Hello name="world" />, document.body);
renderBeforeEnd(<Hello name="world" />, document.body);
renderAndReplace(<Hello name="world" />, document.body);
renderAppend(<Hello name="world" />, document.body);
renderAfter(<Hello name="world" />, document.body);
```

#### Composing Components
Expand Down Expand Up @@ -159,8 +174,11 @@ function App() {

render(<App />, document.body);
```

##### Fragments

Fragments are supported as child elements everywhere, but are also supported as top-level JSX elements when using `renderPrepend`, `render`, and `renderAppend`.

```javascript
function Hello() {
return <>
Expand All @@ -170,9 +188,7 @@ function Hello() {
}

function App() {
return <div>
<Hello />
</div>;
return <Hello />;
}

render(<App />, document.body);
Expand Down
29 changes: 18 additions & 11 deletions dist/main.iife.js
Expand Up @@ -189,19 +189,25 @@ var JSXNoReact = (function (exports) {
return element;
}

function renderBefore(elem, parent) {
if (elem.constructor === DocumentFragment) throw new Error("renderBefore does not support top-level fragment rendering");
parent.insertAdjacentElement("beforebegin", elem);
}
function renderPrepend(elem, parent) {
var parentFirstChild = parent.children ? parent.children[0] : null;
parent.insertBefore(elem, parentFirstChild);
}
function render(elem, parent) {
parent.insertAdjacentElement("afterbegin", elem);
parent.innerHTML = "";
parent.appendChild(elem);
}
function renderBeforeEnd(elem, parent) {
parent.insertAdjacentElement("beforeend", elem);
function renderAppend(elem, parent) {
parent.appendChild(elem);
}
function renderAfterEnd(elem, parent) {
function renderAfter(elem, parent) {
if (elem.constructor === DocumentFragment) throw new Error("renderAfter does not support top-level fragment rendering");
parent.insertAdjacentElement("afterend", elem);
}
function renderAndReplace(elem, parent) {
parent.innerHTML = "";
parent.insertAdjacentElement("afterbegin", elem);
}

function addAttributes(elem, attrs) {
if (attrs === null || attrs === undefined) attrs = {};
Expand Down Expand Up @@ -296,9 +302,10 @@ var JSXNoReact = (function (exports) {

exports["default"] = module;
exports.render = render;
exports.renderAfterEnd = renderAfterEnd;
exports.renderAndReplace = renderAndReplace;
exports.renderBeforeEnd = renderBeforeEnd;
exports.renderAfter = renderAfter;
exports.renderAppend = renderAppend;
exports.renderBefore = renderBefore;
exports.renderPrepend = renderPrepend;

Object.defineProperty(exports, '__esModule', { value: true });

Expand Down
24 changes: 15 additions & 9 deletions dist/module.es6.js
Expand Up @@ -186,19 +186,25 @@ function createElement(elem, attrs) {
return element;
}

function renderBefore(elem, parent) {
if (elem.constructor === DocumentFragment) throw new Error("renderBefore does not support top-level fragment rendering");
parent.insertAdjacentElement("beforebegin", elem);
}
function renderPrepend(elem, parent) {
var parentFirstChild = parent.children ? parent.children[0] : null;
parent.insertBefore(elem, parentFirstChild);
}
function render(elem, parent) {
parent.insertAdjacentElement("afterbegin", elem);
parent.innerHTML = "";
parent.appendChild(elem);
}
function renderBeforeEnd(elem, parent) {
parent.insertAdjacentElement("beforeend", elem);
function renderAppend(elem, parent) {
parent.appendChild(elem);
}
function renderAfterEnd(elem, parent) {
function renderAfter(elem, parent) {
if (elem.constructor === DocumentFragment) throw new Error("renderAfter does not support top-level fragment rendering");
parent.insertAdjacentElement("afterend", elem);
}
function renderAndReplace(elem, parent) {
parent.innerHTML = "";
parent.insertAdjacentElement("afterbegin", elem);
}

function addAttributes(elem, attrs) {
if (attrs === null || attrs === undefined) attrs = {};
Expand Down Expand Up @@ -291,4 +297,4 @@ var module = {
createElement: converter
};

export { module as default, render, renderAfterEnd, renderAndReplace, renderBeforeEnd };
export { module as default, render, renderAfter, renderAppend, renderBefore, renderPrepend };
5 changes: 3 additions & 2 deletions package.json
@@ -1,12 +1,13 @@
{
"name": "jsx-no-react",
"version": "1.1.1",
"version": "2.0.0",
"description": "Use JSX without React",
"repository": "https://github.com/bitboxer/jsx-no-react",
"author": "Terry Kerr <t@xnr.ca>",
"contributors": [
"Bodo Tasche <bodo@wannawork.de>",
"Matteo Barison <info@matteobarison.com>"
"Matteo Barison <info@matteobarison.com>",
"Jared Sartin <jared@sart.in>"
],
"license": "MPL-2.0",
"private": false,
Expand Down
27 changes: 18 additions & 9 deletions src/module.js
Expand Up @@ -38,21 +38,30 @@ function createElement(elem, attrs) {
return element;
}

export function render(elem, parent) {
parent.insertAdjacentElement("afterbegin", elem);
export function renderBefore(elem, parent) {
if (elem.constructor === DocumentFragment)
throw new Error("renderBefore does not support top-level fragment rendering");
parent.insertAdjacentElement("beforebegin", elem);
}

export function renderBeforeEnd(elem, parent) {
parent.insertAdjacentElement("beforeend", elem);
export function renderPrepend(elem, parent) {
const parentFirstChild = parent.children ? parent.children[0] : null;
parent.insertBefore(elem, parentFirstChild);
}

export function renderAfterEnd(elem, parent) {
parent.insertAdjacentElement("afterend", elem);
export function render(elem, parent) {
parent.innerHTML = "";
parent.appendChild(elem);
}

export function renderAndReplace(elem, parent) {
parent.innerHTML = "";
parent.insertAdjacentElement("afterbegin", elem);
export function renderAppend(elem, parent) {
parent.appendChild(elem);
}

export function renderAfter(elem, parent) {
if (elem.constructor === DocumentFragment)
throw new Error("renderAfter does not support top-level fragment rendering");
parent.insertAdjacentElement("afterend", elem);
}

function addAttributes(elem, attrs) {
Expand Down
105 changes: 83 additions & 22 deletions src/module.test.js
@@ -1,4 +1,4 @@
import jsxElem, { render, renderBeforeEnd, renderAfterEnd, renderAndReplace } from "./module";
import jsxElem, { render, renderAppend, renderAfter, renderBefore, renderPrepend } from "./module";
import expectExport from "expect";

describe("jsxElement usage", () => {
Expand Down Expand Up @@ -124,64 +124,125 @@ describe("jsxElement usage", () => {
});

describe("render", () => {
it("adds the output to the element", () => {
it("replaces content and adds the output", () => {
function Hello(props) {
return <h1>Hello {props.name}</h1>;
}

const mockElement = jest.fn();
render(<Hello name="world" />, { insertAdjacentElement: mockElement });
expect(mockElement.mock.calls.length).toBe(1);
expect(mockElement.mock.calls[0][0]).toBe("afterbegin");
expect(mockElement.mock.calls[0][1].outerHTML).toEqual(
const mockParent = <div><h1>Exist</h1></div>;
render(<Hello name="world" />, mockParent);
expect(mockParent.innerHTML).toEqual(
"<h1>Hello world</h1>"
);
});

it("replaces contents when top-level JSX element is a fragment", () => {
const mockParent = <div><h1>Exist</h1></div>;
const fragmentChild = <>
<h1>Hello</h1>
<h1>World</h1>
</>;
render(fragmentChild, mockParent);
expect(mockParent.innerHTML).toEqual(
"<h1>Hello</h1><h1>World</h1>"
);
});
});

describe("renderBeforeEnd", () => {
describe("renderAppend", () => {
it("adds the output before the end of the element", () => {
function Hello(props) {
return <h1>Hello {props.name}</h1>;
}

const mockElement = jest.fn();
renderBeforeEnd(<Hello name="world" />, { insertAdjacentElement: mockElement });
expect(mockElement.mock.calls.length).toBe(1);
expect(mockElement.mock.calls[0][0]).toBe("beforeend");
expect(mockElement.mock.calls[0][1].outerHTML).toEqual(
"<h1>Hello world</h1>"
const mockParent = <div><h1>Exist</h1></div>;
renderAppend(<Hello name="world" />, mockParent);
expect(mockParent.innerHTML).toEqual(
"<h1>Exist</h1><h1>Hello world</h1>"
);
});

it("appends contents when top-level JSX element is a fragment", () => {
const mockParent = <div><h1>Exist</h1></div>;
const fragmentChild = <>
<h1>Hello</h1>
<h1>World</h1>
</>;
renderAppend(fragmentChild, mockParent);
expect(mockParent.innerHTML).toEqual(
"<h1>Exist</h1><h1>Hello</h1><h1>World</h1>"
);
});
});

describe("renderAfterEnd", () => {
describe("renderPrepend", () => {
it("adds the output at the start of the element", () => {
function Hello(props) {
return <h1>Hello {props.name}</h1>;
}

const mockParent = <div><h1>Exist</h1></div>;
renderPrepend(<Hello name="world" />, mockParent);
expect(mockParent.innerHTML).toEqual(
"<h1>Hello world</h1><h1>Exist</h1>"
);
});

it("prepends contents when top-level JSX element is a fragment", () => {
const mockParent = <div><h1>Exist</h1></div>;
const fragmentChild = <>
<h1>Hello</h1>
<h1>World</h1>
</>;
renderPrepend(fragmentChild, mockParent);
expect(mockParent.innerHTML).toEqual(
"<h1>Hello</h1><h1>World</h1><h1>Exist</h1>"
);
});
});

describe("renderAfter", () => {
it("adds the output after the end of the element", () => {
function Hello(props) {
return <h1>Hello {props.name}</h1>;
}

const mockElement = jest.fn();
renderAfterEnd(<Hello name="world" />, { insertAdjacentElement: mockElement });
renderAfter(<Hello name="world" />, { insertAdjacentElement: mockElement });
expect(mockElement.mock.calls.length).toBe(1);
expect(mockElement.mock.calls[0][0]).toBe("afterend");
expect(mockElement.mock.calls[0][1].outerHTML).toEqual(
"<h1>Hello world</h1>"
);
});

it("throws an error when given a fragment", () => {
const mockElement = jest.fn();
expect(() => {
renderAfter(<></>, { insertAdjacentElement: mockElement });
}).toThrow("renderAfter does not support top-level fragment rendering");
});
});

describe("renderAndReplace", () => {
it("replace content and adds the output", () => {
describe("renderBefore", () => {
it("adds the output before the start of the element", () => {
function Hello(props) {
return <h1>Hello {props.name}</h1>;
}

const mockElement = document.createElement("div");
document.body.appendChild(mockElement);
renderAndReplace(<Hello name="world" />, document.body);
expect(document.body.innerHTML).toEqual(
const mockElement = jest.fn();
renderBefore(<Hello name="world" />, { insertAdjacentElement: mockElement });
expect(mockElement.mock.calls.length).toBe(1);
expect(mockElement.mock.calls[0][0]).toBe("beforebegin");
expect(mockElement.mock.calls[0][1].outerHTML).toEqual(
"<h1>Hello world</h1>"
);
});

it("throws an error when given a fragment", () => {
const mockElement = jest.fn();
expect(() => {
renderBefore(<></>, { insertAdjacentElement: mockElement });
}).toThrow("renderBefore does not support top-level fragment rendering");
});
});