Skip to content

Commit 9086490

Browse files
committed
Initial commit
1 parent 80983e0 commit 9086490

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+11488
-2
lines changed

.circleci/config.yml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
version: 2
2+
3+
defaults: &defaults
4+
working_directory: ~/repo
5+
docker:
6+
- image: circleci/node:8
7+
8+
jobs:
9+
build:
10+
<<: *defaults
11+
12+
steps:
13+
- checkout
14+
- restore_cache:
15+
keys:
16+
- v1-dependencies-{{ checksum "yarn.lock" }}
17+
- v1-dependencies-
18+
- run: sudo yarn global add greenkeeper-lockfile@2
19+
- run: greenkeeper-lockfile-update
20+
- run: yarn install
21+
- run: greenkeeper-lockfile-upload
22+
- save_cache:
23+
paths:
24+
- node_modules
25+
key: v1-dependencies-{{ checksum "yarn.lock" }}
26+
- persist_to_workspace:
27+
root: ~/repo
28+
paths: .
29+
deploy_packages:
30+
<<: *defaults
31+
steps:
32+
- attach_workspace:
33+
at: ~/repo
34+
- run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
35+
- run: ./node_modules/.bin/lerna publish from-git --yes
36+
lint:
37+
<<: *defaults
38+
steps:
39+
- attach_workspace:
40+
at: ~/repo
41+
- run: yarn lint
42+
# test:
43+
# <<: *defaults
44+
# steps:
45+
# - attach_workspace:
46+
# at: ~/repo
47+
# - run: yarn test --coverage
48+
# - run: bash <(curl -s https://codecov.io/bash)
49+
50+
workflows:
51+
version: 2
52+
build_test_deploy:
53+
jobs:
54+
- build:
55+
filters:
56+
tags:
57+
only: /^v.*/
58+
# - test:
59+
# requires:
60+
# - build
61+
# filters:
62+
# tags:
63+
# only: /^v.*/
64+
- lint:
65+
requires:
66+
- build
67+
filters:
68+
tags:
69+
only: /^v.*/
70+
- deploy_packages:
71+
requires:
72+
- build
73+
- test
74+
- lint
75+
filters:
76+
tags:
77+
only: /^v.*/
78+
branches:
79+
ignore: /.*/

.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
8+
# Coverage directory used by tools like istanbul
9+
coverage
10+
11+
# Dependency directories
12+
node_modules/
13+
14+
# Output of 'npm pack'
15+
*.tgz
16+
17+
# Build artifacts
18+
lib
19+
20+
# macOS system files
21+
.DS_Store
22+
23+
# docz cache
24+
.docz

README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,21 @@
1-
# react-from-markup
2-
Declare your React components with static HTML
1+
# React from Markup
2+
3+
> Declare your React components with static HTML
4+
5+
## Why?
6+
7+
Sometimes, your tech stack doesn’t speak JavaScript very well.
8+
9+
This can be a particular problem with some CMS, or legacy systems. React is a JavaScript library, and oftentimes, it’s difficult to create the bootstrapping JavaScript from your templating system.
10+
11+
`react-from-markup` is intended to make it possible to use React components on these legacy systems, _without changing the way you write your React components_. It provides tools to simplify the mapping from `data-` attributes into React props, and _can even handle React children_.
12+
13+
The result: React can be used to build a component library, useable by other React apps, but you don’t need to write a second component library for your legacy systems, or a second set of non-React JavaScript. You just need to integrate new markup into your non-React templates, and run a script on page load to initialize.
14+
15+
## Notable uses
16+
17+
- Thomson Reuters uses `react-from-markup` on its Enterprise Web Platform. It runs on Adobe Experience Manager and HTL, and powers thomsonreuters.com, business unit sites, and campaign pages. It’s also used on their blog network, a WordPress-based platform of blogs. The same React-based component library is able to back each platform, and is also used in several non-`react-from-markup` sites that were able to use React directly, such as the [2017 Annual Report](https://ar.tr.com).
18+
19+
## Documentation
20+
21+
In-depth documentation can be found [here](https://simon360.github.io/react-from-markup).

docs/api/rehydrate.mdx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
name: rehydrate()
3+
4+
menu: API
5+
route: /api/rehydrate
6+
---
7+
8+
# The rehydrate function
9+
10+
```javascript
11+
rehydrate(rootNode, rehydrators, options);
12+
```
13+
14+
## Contract
15+
16+
When run, `rehydrate` will convert the contents of any [markup containers](/containers) into React elements.
17+
18+
## Parameters
19+
20+
* `rootNode`: a node on your page where `react-from-markup` should start looking for markup containers.
21+
22+
A simple implementation would have the first child of your `<body />` element be a `<div id="root" />`, and put all your markup inside. Then, you can use `document.getElementById("root")` as your `rootNode`.
23+
* `rehydrators`: a keyed object containing your rehydrator functions, each matching the [rehydrator interface](/api/rehydrator)
24+
25+
A key of `SiteHeader` means that the provided rehydrator will be run for an element with `data-rehydratable="SiteHeader"` set.
26+
* `options`: an object that configures `rehydrate()`
27+
* `extra`: an object that will be passed to each [rehydrator](/api/rehydrator). Can be used to provide some page state information to your components, such as user information or analytics hooks.
28+
29+
## Return value
30+
31+
Returns a `Promise` that resolves when `rootNode` has been rehydrated.
32+
33+
## Example usage
34+
35+
```javascript
36+
import rehydrate from "react-from-markup";
37+
import { rehydrator as siteHeaderRehydrator } from "./components/SiteHeader";
38+
import { rehydrator as videoPlayerRehydrator } from "./components/VideoPlayer";
39+
40+
rehydrate(
41+
document.getElementById("root"),
42+
{
43+
SiteHeader: siteHeaderRehydrator,
44+
VideoPlayer: videoPlayerRehydrator
45+
},
46+
{
47+
extra: {
48+
user: {
49+
userName: document.body.dataset.userName,
50+
userId: document.body.dataset.userId
51+
}
52+
}
53+
}
54+
)
55+
```

docs/api/rehydrator.mdx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
name: Rehydrator Interface
3+
4+
menu: API
5+
route: /api/rehydrator
6+
---
7+
8+
# Rehydrator Interface
9+
10+
```javascript
11+
async (domNode, rehydrateChildren, extra) => <Element />
12+
```
13+
14+
Your rehydrators should be written to this API.
15+
16+
## Contract
17+
18+
Given `domNode`, a rehydrator will return an equivalent React element.
19+
20+
## Parameters
21+
22+
* `domNode`: an element that has matched this rehydrator.
23+
* `rehydrateChildren`: a function (returning a `Promise`) that can be used to rehydrate any child nodes that contain arbitrary markup.
24+
* `extra`: page state information, from [`rehydrate()`'s `options` parameter](/api/rehydrate#parameters).
25+
26+
## Return value
27+
28+
A React element that represents `domNode`.
29+
30+
## Example usage
31+
32+
33+
`index.js`:
34+
```javascript
35+
export { default } from "./SiteHeader";
36+
export { default as rehydrator } from "./rehydrator";
37+
```
38+
39+
`rehydrator.js`:
40+
```javascript
41+
import Banner from "./Banner";
42+
43+
export default async (domNode, rehydrateChildren, extra) => {
44+
const children = await rehydrateChildren(domNode.querySelector(".Banner-children"));
45+
46+
const props = {
47+
open: !extra.user.hasSeenBanner,
48+
title: domNode.querySelector(".Banner-title").innerText
49+
}
50+
51+
return <Banner {...props}>{children}</Banner>;
52+
}
53+
```
54+
55+
`Banner.js`:
56+
```javascript
57+
import React from "react";
58+
import PropTypes from "prop-types";
59+
60+
class Banner extends React.Component {
61+
static propTypes = {
62+
children: PropTypes.node,
63+
hasBeenSeen: PropTypes.bool,
64+
title: PropTypes.string
65+
};
66+
67+
state = {
68+
open: true
69+
}
70+
71+
static getDerivedStateFromProps(props, state) {
72+
// Only show the banner if it hasn't been seen on previous pages, and it
73+
// hasn't been closed on this page.
74+
return {
75+
open: !props.hasBeenSeen && state.open
76+
};
77+
}
78+
79+
render() {
80+
const { children } = this.props;
81+
const { open } = this.state;
82+
83+
return <div className={`Banner${open ? " is-open" : ""}`}>
84+
<h1 className="Banner-title">{title}</h1>
85+
<div className="Banner-children">{children}</div>
86+
<button onClick={() => this.setState({ open: false })}>Close banner</button>
87+
</div>;
88+
}
89+
}
90+
91+
export default Banner;
92+
```

docs/containers.mdx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
name: Markup containers
3+
4+
route: /containers
5+
---
6+
7+
# Markup containers
8+
9+
`react-from-markup` will only rehydrate elements inside a _markup container_.
10+
11+
A markup container is simply a DOM element that has a `data-react-from-markup-container` attribute on it.
12+
13+
You can have as many markup containers on your page as you like.
14+
15+
16+
## Declaring a container
17+
18+
In your markup, simply wrap any elements that you wish to rehydrate in a container:
19+
20+
```html
21+
<div data-react-from-markup-container>
22+
<p>When the page loads, I'll be turned into a static React node.</p>
23+
</div>
24+
```

docs/demos/HelloUser.mdx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
name: HelloUser
3+
4+
menu: Demos
5+
order: 4
6+
route: /demo/HelloUser
7+
---
8+
9+
import { Playground } from "docz";
10+
import HelloUser, { rehydrator } from "./components/HelloUser"
11+
import MarkupContainer from "./components/MarkupContainer"
12+
13+
# HelloUser, using extra
14+
15+
Demonstrates the use of `extra` to pass a user's name to a rehydrated component.
16+
17+
<Playground>
18+
<MarkupContainer rehydrators={{HelloUser: rehydrator}} options={{extra: {userName: "John Smith"}}}>
19+
<p>This will greet the user, whose name is passed to the rehydrator in the extra parameter.</p>
20+
21+
{/* The userName is a temporary value - it will be replaced using `extra` when the rehydrator runs. */}
22+
<HelloUser userName="" />
23+
</MarkupContainer>
24+
</Playground>

docs/demos/ShowMore.mdx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
name: ShowMore
3+
4+
menu: Demos
5+
order: 6
6+
route: /demo/ShowMore
7+
---
8+
9+
import { Playground } from "docz";
10+
import HelloUser, { rehydrator as helloUserRehydrator } from "./components/HelloUser";
11+
import ShowMore, { rehydrator } from "./components/ShowMore";
12+
import MarkupContainer from "./components/MarkupContainer"
13+
14+
# ShowMore
15+
16+
Demonstrates rehydrating a component that has arbitrary children.
17+
18+
<Playground>
19+
<MarkupContainer
20+
rehydrators={{
21+
HelloUser: helloUserRehydrator,
22+
ShowMore: rehydrator
23+
}}
24+
options={{
25+
extra: {
26+
userName: "John Smith"
27+
}
28+
}}
29+
>
30+
<ShowMore>
31+
<p>I can contain <b>complex</b> <em>DOM structures</em>. I can even contain other rehydratable components.</p>
32+
<HelloUser userName="" />
33+
</ShowMore>
34+
</MarkupContainer>
35+
</Playground>

docs/demos/ShowMoreText.mdx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
name: ShowMoreText
3+
4+
menu: Demos
5+
order: 8
6+
route: /demo/ShowMoreText
7+
---
8+
9+
import { Playground } from "docz";
10+
import ShowMoreText, { rehydrator } from "./components/ShowMoreText"
11+
import MarkupContainer from "./components/MarkupContainer"
12+
13+
# ShowMoreText
14+
15+
Demonstrates rehydrating a simple string prop.
16+
17+
A more advanced version, which rehydrates complex DOM nodes, can be seen in the [`ShowMore` demo](/demos/ShowMore).
18+
19+
<Playground>
20+
<MarkupContainer rehydrators={{ShowMoreText: rehydrator}} options={{}}>
21+
<ShowMoreText content="Hello, world!" />
22+
</MarkupContainer>
23+
</Playground>

0 commit comments

Comments
 (0)