{"action":"created","issue":{"url":"https://api.github.com/repos/GitbookIO/gitbook/issues/1543","repository_url":"https://api.github.com/repos/GitbookIO/gitbook","labels_url":"https://api.github.com/repos/GitbookIO/gitbook/issues/1543/labels{/name}","comments_url":"https://api.github.com/repos/GitbookIO/gitbook/issues/1543/comments","events_url":"https://api.github.com/repos/GitbookIO/gitbook/issues/1543/events","html_url":"https://github.com/GitbookIO/gitbook/pull/1543","id":179573919,"number":1543,"title":"React for rendering website with plugins","user":{"login":"SamyPesse","id":845425,"avatar_url":"https://avatars.githubusercontent.com/u/845425?v=3","gravatar_id":"","url":"https://api.github.com/users/SamyPesse","html_url":"https://github.com/SamyPesse","followers_url":"https://api.github.com/users/SamyPesse/followers","following_url":"https://api.github.com/users/SamyPesse/following{/other_user}","gists_url":"https://api.github.com/users/SamyPesse/gists{/gist_id}","starred_url":"https://api.github.com/users/SamyPesse/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SamyPesse/subscriptions","organizations_url":"https://api.github.com/users/SamyPesse/orgs","repos_url":"https://api.github.com/users/SamyPesse/repos","events_url":"https://api.github.com/users/SamyPesse/events{/privacy}","received_events_url":"https://api.github.com/users/SamyPesse/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/GitbookIO/gitbook/labels/in%20progress","name":"in progress","color":"ededed"}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":{"url":"https://api.github.com/repos/GitbookIO/gitbook/milestones/6","html_url":"https://github.com/GitbookIO/gitbook/milestones/GitBook%204.0.0%20-%20React%20and%20better%20plugins","labels_url":"https://api.github.com/repos/GitbookIO/gitbook/milestones/6/labels","id":2034062,"number":6,"title":"GitBook 4.0.0 - React and better plugins","description":"The goal of this version is to provide better APIs for plugins to extend the features of the website.\n\nSince version 3.0.0 focused on the Node.js side, this version will be entirely focused on the website output.","creator":{"login":"SamyPesse","id":845425,"avatar_url":"https://avatars.githubusercontent.com/u/845425?v=3","gravatar_id":"","url":"https://api.github.com/users/SamyPesse","html_url":"https://github.com/SamyPesse","followers_url":"https://api.github.com/users/SamyPesse/followers","following_url":"https://api.github.com/users/SamyPesse/following{/other_user}","gists_url":"https://api.github.com/users/SamyPesse/gists{/gist_id}","starred_url":"https://api.github.com/users/SamyPesse/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SamyPesse/subscriptions","organizations_url":"https://api.github.com/users/SamyPesse/orgs","repos_url":"https://api.github.com/users/SamyPesse/repos","events_url":"https://api.github.com/users/SamyPesse/events{/privacy}","received_events_url":"https://api.github.com/users/SamyPesse/received_events","type":"User","site_admin":false},"open_issues":9,"closed_issues":0,"state":"open","created_at":"2016-09-28T07:49:13Z","updated_at":"2016-10-10T22:17:08Z","due_on":null,"closed_at":null},"comments":7,"created_at":"2016-09-27T18:24:57Z","updated_at":"2016-10-11T07:42:58Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/GitbookIO/gitbook/pulls/1543","html_url":"https://github.com/GitbookIO/gitbook/pull/1543","diff_url":"https://github.com/GitbookIO/gitbook/pull/1543.diff","patch_url":"https://github.com/GitbookIO/gitbook/pull/1543.patch"},"body":"Hello ✨ \r\n\r\nThis PR is a reimplementation of the client side of GitBook. See the [Goals](#goals) section for details about the ambition of this PR.\r\n\r\nThis 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.\r\n\r\n---\r\n\r\n# Problem\r\n\r\nThe mission of the GitBook toolchain is to provide an extensible/pluggable static generator with theming, suited for documentation.\r\n\r\nSince version 3.0.0, GitBook \"Node.js side\" is already getting more stable and faster over time.\r\n\r\nBut 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).\r\n\r\n---\r\n\r\n# Goals\r\n\r\n1. Make it easier to build plugins that works on all themes\r\n2. Make it easier to build interactive plugins\r\n    - Define an API that should stay for the next 10 years\r\n    - Easy to use frontend API:\r\n    - Follow FLUX / Redux convention\r\n    - Don't reinvent the wheel and rest on top of great libraries\r\n    - View should not be separated from event-binding and interactivity\r\n3. Make it easier to style GitBook without conflicting with plugins\r\n4. Improve documentation for creating plugins, with a set of good practices\r\n\r\nSome idea of plugins that can easily be implemented:\r\n\r\n- Hover a glossary term, display a popup with the description of the term\r\n- Suggest edition right from the reading view\r\n\r\n---\r\n\r\n# Inspirations\r\n\r\n- [Nylas N1: Building Plugins for React Apps](https://nylas.com/blog/react-plugins)\r\n- [exerslide](https://github.com/facebookincubator/exerslide)\r\n\r\n---\r\n\r\n# Proposal\r\n\r\nFrom the user point of view, this PR will change nothing, expect that the website output by GitBook will gain more interactive features.\r\n\r\nFrom the plugin developer point of view, this PR will change the whole API to extend the website. Plugin will define:\r\n- a Node.js entry point (`main` in the `package.json`), it will define generation hooks and blocks\r\n- a React entry point (`browser` in the `package.json`), it will define how the plugin extend the website\r\n\r\n### Technologies\r\n\r\n1. **React** for components/views\r\n    - Node.js and browser rendering\r\n    - Stable and well documented\r\n    - Very well maintained\r\n2. **Redux** for managing the store\r\n    - Simple concept and clean implementation\r\n    - Stable and well documented\r\n    - Very well maintained\r\n3. Other helpers:\r\n    - [`react-helmet`](https://github.com/nfl/react-helmet)\r\n    - [`react-redux`](https://github.com/reactjs/react-redux)\r\n\r\n### Workflow for generating a page\r\n\r\n1. GitBook read the markdown and compile it with plugins into HTML (same as v3)\r\n2. GitBook generates a JSON representation of the page (same as v3)\r\n3. `gitbook-core` creates a redux store with the JSON\r\n4. `gitbook-core` render the injected component `Body`\r\n5. GitBook creates a complete HTML string from the react output and `react-helmet` head data\r\n\r\n### API of `gitbook-core`\r\n\r\n`gitbook-core` will be the core node.js / browser API, exposing:\r\n\r\n```js\r\nGitBook {\r\n    // Create a plugin\r\n    createPlugin: Function(\r\n        onInitialState<Function(dispatch: Function, getState: Function)>,\r\n        onReduceState<Function(state, action)>\r\n    ): Module\r\n\r\n    // Register a component in the store\r\n    registerComponent: Function(Component: React.Class, rule: Object): Action\r\n\r\n    // Helper to create a reducer\r\n    createReducer: Function(name: String, reducer: Function(state, action)): Function\r\n\r\n    // Connect a component to the store\r\n    connect: Function(Component: React.Class, mapStateToProps: Function(state, ownProps)): React.Class\r\n\r\n    // Render components (composed) matching a rule\r\n    InjectedComponent: React.Class\r\n\r\n    // Render a set of component matching a rule\r\n    InjectedComponentSet: React.Class\r\n\r\n    // Map of core actions\r\n    ACTIONS: Map<String: string>\r\n\r\n    // Binding of react-helmet to extend the head/scripts/links/styles\r\n    Head: React.Class\r\n}\r\n```\r\n\r\n### Components injection\r\n\r\nExtensibility is based on the concept taken from Nylas N1 ([Building Plugins for React Apps](https://nylas.com/blog/react-plugins)).\r\n\r\nA plugin can register a component for a certain role. For example this plugin will register a panel to render in the sidebar:\r\n\r\n```js\r\nconst React = require('react');\r\nconst GitBook = require('gitbook-core');\r\n\r\nconst MyPanel = React.createClass({\r\n    render() {\r\n        return <div>Hello World</div>;\r\n    }\r\n});\r\n\r\nmodule.exports = GitBook.createPlugin((dispatch, state) => {\r\n    dispatch(GitBook.registerComponent(MyPanel, { role: 'sidebar:panel' }));\r\n});\r\n```\r\n\r\nPlugins and themes can also render components that matched a certain criteria:\r\n\r\n```js\r\nconst Sidebar = React.createClass({\r\n    render() {\r\n        return (\r\n            <div className=\"Sidebar\">\r\n                <InjectedComponentSet matching={{ role: 'sidebar:panel' }} />\r\n            </div>\r\n        );\r\n    }\r\n});\r\n```\r\n\r\n### Connect to the store\r\n\r\n`gitbook-core` is backed by Redux and store the state of the page (same as the JSON output).\r\n\r\nComponents can connect themselves to the store (backed by `react-redux`):\r\n\r\n```js\r\nlet MyPanel = React.createClass({\r\n    propTypes: {\r\n        page: GitBook.Shapes.Page.isRequired\r\n    },\r\n\r\n    render() {\r\n        const { page } = this.props;\r\n        return <div>Current page is {page.title}</div>;\r\n    }\r\n});\r\n\r\nMyPanel = GitBook.connect(MyPanel, ({page}) => {\r\n    return { page };\r\n});\r\n```\r\n\r\n### Extend the store\r\n\r\nThe store contains the state of current page (`page`, `summary`, `readme`, etc).\r\n\r\nBut plugins can extend the store:\r\n\r\n```js\r\nconst ACTIONS = {\r\n    TOGGLE: 'panel:toggle'\r\n};\r\n\r\nconst customReducer = GitBook.createReducer('myPanel', (state, action) => {\r\n    switch (action.type) {\r\n    case ACTIONS.TOGGLE:\r\n        return !state;\r\n    default:\r\n        return state;\r\n    }\r\n});;\r\n\r\nmodule.exports = GitBook.createPlugin(\r\n    // On initial state\r\n    (dispatch, state) => {\r\n        dispatch(GitBook.registerComponent(MyPanel, { role: 'sidebar:panel' }));\r\n    },\r\n    // Reduce state\r\n    customReducer\r\n);\r\n```\r\n\r\n\r\n### Template blocks and interactivity\r\n\r\nIn GitBook `<= 3.x.x`, blocks were node.js code returning HTML code to replace Liquid tags:\r\n\r\n```md\r\nThis is **my markdown**:\r\n\r\n{% svgtopng src=\"http://example.com/test.svg\" %}\r\n```\r\n\r\nWith this new implementation, blocks are defined in the node.js entry point of the plugin to map props from block's arguments:\r\n\r\n```js\r\n// this code runs in node.js\r\nmodule.exports = {\r\n    blocks: {\r\n        svgtopng: (block) => {\r\n            return convertSVGToPNG(block.kwargs.src)\r\n            .then((filePath) => {\r\n                return {\r\n                    src: filePath\r\n                }\r\n            });\r\n        }\r\n    }\r\n}\r\n```\r\n\r\nThen the plugin code can register a component to render this block:\r\n\r\n```js\r\nconst BlockSVGToPNG = React.createClass({\r\n    render() {\r\n        return <img className=\"svgtopng\" src={this.props.src} />;\r\n    }\r\n});\r\n\r\nmodule.exports = GitBook.createPlugin((dispatch, state) => {\r\n    dispatch(GitBook.registerComponent(BlockSVGToPNG, { role: 'block:svgtopng' }));\r\n});\r\n```\r\n\r\n### Themes\r\n\r\nGitBook with `gitbook-core` will render as an entry point the component matching the role `body` using `InjectedComponent`.\r\n\r\nSimilar 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.\r\n\r\n---\r\n\r\n# How to test it\r\n\r\nClone the repository, then run:\r\n\r\n```\r\n$ npm install\r\n$ npm run bootstrap\r\n```\r\n"},"comment":{"url":"https://api.github.com/repos/GitbookIO/gitbook/issues/comments/252836769","html_url":"https://github.com/GitbookIO/gitbook/pull/1543#issuecomment-252836769","issue_url":"https://api.github.com/repos/GitbookIO/gitbook/issues/1543","id":252836769,"user":{"login":"Soreine","id":5644953,"avatar_url":"https://avatars.githubusercontent.com/u/5644953?v=3","gravatar_id":"","url":"https://api.github.com/users/Soreine","html_url":"https://github.com/Soreine","followers_url":"https://api.github.com/users/Soreine/followers","following_url":"https://api.github.com/users/Soreine/following{/other_user}","gists_url":"https://api.github.com/users/Soreine/gists{/gist_id}","starred_url":"https://api.github.com/users/Soreine/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Soreine/subscriptions","organizations_url":"https://api.github.com/users/Soreine/orgs","repos_url":"https://api.github.com/users/Soreine/repos","events_url":"https://api.github.com/users/Soreine/events{/privacy}","received_events_url":"https://api.github.com/users/Soreine/received_events","type":"User","site_admin":false},"created_at":"2016-10-11T07:42:58Z","updated_at":"2016-10-11T07:42:58Z","body":"Yes, let's make a `eslint-config-gitbook-plugin`, so we can inherit (or not) our own config."}}
{"actor":{"display_login":"Soreine"}}