Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React for rendering website with plugins #1543

Merged
merged 183 commits into from
Dec 22, 2016
Merged

React for rendering website with plugins #1543

merged 183 commits into from
Dec 22, 2016

Conversation

SamyPesse
Copy link
Member

@SamyPesse SamyPesse commented Sep 27, 2016

Hello ✨

This PR is a reimplementation of the client side of GitBook. See the Goals section for details about the ambition of this PR.

This PR is still early and a 👷 WIP. The goal is to gain feedback from plugin developers about the proposed API and from users about the expected website features.


Problem

The mission of the GitBook toolchain is to provide an extensible/pluggable static generator with theming, suited for documentation.

Since version 3.0.0, GitBook "Node.js side" is already getting more stable and faster over time.

But GitBook frontend is still backed by jQuery and is hard to extend (theming, functionalities). Interactive functionalities are hard to implement (ex: theme-api, code-tabs, comment, etc), and even harder to make compatible with different themes (ex: plugin comment only works on the default theme an API theme).


Goals

  1. Make it easier to build plugins that works on all themes
  2. Make it easier to build interactive plugins
    • Define an API that should stay for the next 10 years
    • Easy to use frontend API:
    • Follow FLUX / Redux convention
    • Don't reinvent the wheel and rest on top of great libraries
    • View should not be separated from event-binding and interactivity
  3. Make it easier to style GitBook without conflicting with plugins
  4. Improve documentation for creating plugins, with a set of good practices

Some idea of plugins that can easily be implemented:

  • Hover a glossary term, display a popup with the description of the term
  • Suggest edition right from the reading view

Inspirations


Proposal

From the user point of view, this PR will change nothing, expect that the website output by GitBook will gain more interactive features.

From the plugin developer point of view, this PR will change the whole API to extend the website. Plugin will define:

  • a Node.js entry point (main in the package.json), it will define generation hooks and blocks
  • a React entry point (browser in the package.json), it will define how the plugin extend the website

Technologies

  1. React for components/views
    • Node.js and browser rendering
    • Stable and well documented
    • Very well maintained
  2. Redux for managing the store
    • Simple concept and clean implementation
    • Stable and well documented
    • Very well maintained
  3. Other helpers:

Workflow for generating a page

  1. GitBook read the markdown and compile it with plugins into HTML (same as v3)
  2. GitBook generates a JSON representation of the page (same as v3)
  3. gitbook-core creates a redux store with the JSON
  4. gitbook-core render the injected component Body
  5. GitBook creates a complete HTML string from the react output and react-helmet head data

API of gitbook-core

gitbook-core will be the core node.js / browser API, exposing:

GitBook {
    // Create a plugin
    createPlugin: Function(
        onInitialState<Function(dispatch: Function, getState: Function)>,
        onReduceState<Function(state, action)>
    ): Module

    // Register a component in the store
    registerComponent: Function(Component: React.Class, rule: Object): Action

    // Helper to create a reducer
    createReducer: Function(name: String, reducer: Function(state, action)): Function

    // Connect a component to the store
    connect: Function(Component: React.Class, mapStateToProps: Function(state, ownProps)): React.Class

    // Render components (composed) matching a rule
    InjectedComponent: React.Class

    // Render a set of component matching a rule
    InjectedComponentSet: React.Class

    // Map of core actions
    ACTIONS: Map<String: string>

    // Binding of react-helmet to extend the head/scripts/links/styles
    Head: React.Class
}

Components injection

Extensibility is based on the concept taken from Nylas N1 (Building Plugins for React Apps).

A plugin can register a component for a certain role. For example this plugin will register a panel to render in the sidebar:

const React = require('react');
const GitBook = require('gitbook-core');

const MyPanel = React.createClass({
    render() {
        return <div>Hello World</div>;
    }
});

module.exports = GitBook.createPlugin((dispatch, state) => {
    dispatch(GitBook.registerComponent(MyPanel, { role: 'sidebar:panel' }));
});

Plugins and themes can also render components that matched a certain criteria:

const Sidebar = React.createClass({
    render() {
        return (
            <div className="Sidebar">
                <InjectedComponentSet matching={{ role: 'sidebar:panel' }} />
            </div>
        );
    }
});

Connect to the store

gitbook-core is backed by Redux and store the state of the page (same as the JSON output).

Components can connect themselves to the store (backed by react-redux):

let MyPanel = React.createClass({
    propTypes: {
        page: GitBook.Shapes.Page.isRequired
    },

    render() {
        const { page } = this.props;
        return <div>Current page is {page.title}</div>;
    }
});

MyPanel = GitBook.connect(MyPanel, ({page}) => {
    return { page };
});

Extend the store

The store contains the state of current page (page, summary, readme, etc).

But plugins can extend the store:

const ACTIONS = {
    TOGGLE: 'panel:toggle'
};

const customReducer = GitBook.createReducer('myPanel', (state, action) => {
    switch (action.type) {
    case ACTIONS.TOGGLE:
        return !state;
    default:
        return state;
    }
});;

module.exports = GitBook.createPlugin(
    // On initial state
    (dispatch, state) => {
        dispatch(GitBook.registerComponent(MyPanel, { role: 'sidebar:panel' }));
    },
    // Reduce state
    customReducer
);

Template blocks and interactivity

In GitBook <= 3.x.x, blocks were node.js code returning HTML code to replace Liquid tags:

This is **my markdown**:

{% svgtopng src="http://example.com/test.svg" %}

With this new implementation, blocks are defined in the node.js entry point of the plugin to map props from block's arguments:

// this code runs in node.js
module.exports = {
    blocks: {
        svgtopng: (block) => {
            return convertSVGToPNG(block.kwargs.src)
            .then((filePath) => {
                return {
                    src: filePath
                }
            });
        }
    }
}

Then the plugin code can register a component to render this block:

const BlockSVGToPNG = React.createClass({
    render() {
        return <img className="svgtopng" src={this.props.src} />;
    }
});

module.exports = GitBook.createPlugin((dispatch, state) => {
    dispatch(GitBook.registerComponent(BlockSVGToPNG, { role: 'block:svgtopng' }));
});

Themes

GitBook with gitbook-core will render as an entry point the component matching the role body using InjectedComponent.

Similar to GitBook 3.x.x, themes will be plugins extending the default one or replacing the role body. Themes are really the one in control of calling/rendering most of the roles. For example, the default theme would define standard roles such as sidebar:x, toolbar:left, etc.


How to test it

Clone the repository, then run:

$ npm install
$ npm run bootstrap

@nagim
Copy link

nagim commented Nov 4, 2016

A feature idea: it is often that a larger table looks good on a bigger monitor, but when looking at it on a smaller screen (like a laptop screen), it's hard to notice that some parts of the table are not shown at all.

My idea is to make it to resize dynamically with the screen size/zoom/font size, so the table fits always on the screen (even if it's not pretty).

@SamyPesse SamyPesse merged commit 194ebc3 into master Dec 22, 2016
@SamyPesse SamyPesse deleted the dream branch December 22, 2016 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants