slidify / onefile

Standalone HTML files with Slidify
http://slidify.github.io/onefile
2 stars 2 forks source link

Dependency Handling in Slidify #1

Open ramnathv opened 10 years ago

ramnathv commented 10 years ago

This is a short note explaining how slidify handles dependencies. Let us start with the YAML front matter in index.Rmd. I am only including the portions relevant to this note.


---
framework: io2012
widgets: [quiz, bootstrap]
highlighter: highlight.js
ext_widgets: {rCharts: libraries/nvd3}

---

Copy Dependencies

First, when slidify parses this front-matter, it knows that there are four dependencies. The framework and widget dependencies are hardcoded to be searched for in the slidifyLibraries package, while ext_widgets uses the package name as the key and an array of folders relative to the package. Slidify copies all these dependencies into the libraries folder.

Parse Configuration

Second, slidify looks for a config.yml file in each of these dependencies. Take for example the config.yml file in the nvd3 widget. It specifies the paths to the css and js files that need to be included. jshead is used to emphasise that these files need to be included in the head of the document, since there might be inline charts that make use of them and so sticking them at the end might lead to errors. If there is no config.yml, slidify assembles one by looking into the css and js folders and including all the css and js files it finds. The idea is convention over configuration ala ruby.

nvd3:
  css: [css/nv.d3.css, css/rNVD3.css]
  jshead: [js/jquery-1.8.2.min.js, js/d3.v3.min.js, "js/nv.d3.min-new.js", js/fisheye.js]

Here is another config.yml file from the highlight.js highlighter widget. You would notice that there is a ready field which contains javascript that needs to be loaded after the highlight.pack.js is loaded.

highlightjs:
  css: "css/solarized_light.css"
  js: "highlight.pack.js"
  ready: "hljs.initHighlightingOnLoad()"

Create Payload

The next step is to assemble a payload of assets to be injected, to be made available under page$assets. This involves combining the parsed configuration files across all dependencies, resolving duplicates (currently based only on the basename of the files). This step results in a list with four components: css, jshead, js and ready, each of which is a vector.

If the onefile option is specified, these files are converted into DataURIs, with simple or base64 encoding (requires the base64enc package). Depending on the asset type, they are converted into link or script elements, ready to be inserted into HTML.

Insert in Document

The final step is inserting these dependencies into the HTML. Slidify uses mustache templates to handle the final converstion to HTML. This provides a lot of flexibility, since mustache one of the simplest templating languages out there. The assets payload can be easily accessed in a mustache template. For example, {{{ page.assets.css }}} inserts the css payload assembled in the earlier step, into the HTML.


Notes

Overriding Defaults

There are several other features that allow default configuration of widgets to be overloaded from the main YAML front matter. For example, specifying the configuration shown below in the YAML front matter of the main Rmd document will override the default specified by the highlightjs widget. This provides a lot of flexibility to the user.

highlightjs: 
  css: "css/solarized_dark.css"

Dependency Detection

Ideally, dependencies should be automatically injected in most cases. For example, if there is an rCharts NVD3 plot, the R function should be able to communicate to knitr that the nvd3 widget needs to be used and so a user will not have to specify ext_widgets: {rCharts: libraries/nvd3}. Currently I do it via a hack, where there is a .slidifyEnv variable, which detects presence of plots and silently modifies the payload. But there is a strong need for a more robust mechanism to achieve this.

Update: One solution is for the R function creating the plot to return the list of dependencies as attributes, which can be picked up by knitr automatically. This would make dependency overriding tricky, but perhaps can be managed using chunk options.