juba / robservable

Observable notebooks as R htmlwidgets
https://juba.github.io/robservable/
163 stars 11 forks source link

Offline support #9

Open juba opened 4 years ago

juba commented 4 years ago

See if there could be a way to provide offline support (ie functional widget in rmarkdown or shiny without an active Internet connection).

timelyportfolio commented 4 years ago

@juba Do you mean that if a user has a local stored copy of an observablehq notebook that they could use the local copy instead of an online download? Observablehq allows for download that produces a zip file that we could potentially plug in instead either with bare text/<script> tag or through a file attachment. Also, this notebook offers a ui to help a user get exactly what they want.

Or, we could set up an htmlDependency on the R side to handle local or offline with src.

It seems like we would need a mechanism here to choose local instead of online.

I'm not sure yet if this might be helpful, but htmlwidget simplification https://github.com/ramnathv/htmlwidgets/issues/305 might aid in this feature.

timelyportfolio commented 4 years ago

After a little experimentation, offline will be possible unless we can get http server in R to serve .js as text/javascript. Here is the code for reference.

library(robservable)
library(htmltools)

# get temp directory
tmpd <- tempdir()
# create directory for notebook
nb <- file.path(tmpd, "notebook")
dir.create(nb)
# download notebook locally in our newly created directory
path_file <- file.path(nb,"notebook.js")
download.file(
  "https://api.observablehq.com/@jashkenas/inputs.js?v=3",
  path_file
)
# see contents of our new notebook directory
list.files(nb)

# does not work because server not configured to serve module
# Failed to load module script: The server responded with a non-JavaScript MIME type of "text/plain". Strict MIME type checking is enforced for module scripts per HTML spec.
browsable(tagList(
  htmlDependency(
    name = "notebook",
    version = "0.0.0",
    src = list(file = nb),
    all_files = TRUE
  ),
  tags$script(
"
(async () => {
  let nb = await import('./lib/notebook-0.0.0/notebook.js');
  let notebook = nb.default;
})()
"
  )
))

# also does not work because of mime type
browsable(tagList(
  htmlDependency(
    name = "notebook",
    version = "0.0.0",
    src = list(file = nb),
    all_files = TRUE
  ),
  tags$script(
    type = "module",
    "import {define} from './lib/notebook-0.0.0/notebook.js'"
  )
))

# also does not work because of mime type
browsable(tagList(
  htmlDependency(
    name = "notebook",
    version = "0.0.0",
    src = list(file = nb),
    script = "notebook.js"
  ),
  tags$script(
    type = "module",
    "import {define} from './lib/notebook-0.0.0/notebook.js'"
  )
))

# works in that no error but no way to import that I know of with script type="module"
browsable(
  tagList(
    tags$script(
      type="module",
      HTML(paste(readLines(path_file), collapse="\n"))
    )
  )
)
timelyportfolio commented 4 years ago

This is a less than ideal way to make this work, but if we only expect one notebook then perhaps we could replace export default and then define becomes available on window/global.

library(robservable)
library(htmltools)

# get temp directory
tmpd <- tempdir()
# create directory for notebook
nb <- file.path(tmpd, "notebook")
dir.create(nb)
# download notebook locally in our newly created directory
path_file <- file.path(nb,"notebook.js")
download.file(
  "https://api.observablehq.com/@jashkenas/inputs.js?v=3",
  path_file
)

# since mime type seems to be blocking with module
#  try to replace export default
#  but this means we will likely face namespace issues
path_file2 <- file.path(nb,"notebook_no_export.js")
cat(
  paste(
    # replace export default
    gsub(
      x = readLines(path_file),
      pattern = "export default ",
      replacement = ""
    ), collapse="\n"
  ),
  file = path_file2
)
browsable(tagList(
  htmlDependency(
    name = "notebook",
    version = "0.0.0",
    src = list(file = nb),
    script = "notebook_no_export.js",
    all_files = TRUE
  ),
  tags$script(
    "console.log(define);"
  )
))
juba commented 4 years ago

Thanks for taking the time to experiment on this. You're right, the MIME type issue is a real problem here as modules are not expected to be loaded from local files. In my browser even downloading a notebook via Observable "Download code" features doesn't work outside of a local HTTP server.

Your workaround is quite clever if not ideal !

The only use case of an offline support I can think of is an rmarkdown document which would be completely self_contained, ie would work without an active Internet connection. But maybe it is a case not frequent enough to justify investing time in it or using workarounds right now ?

juba commented 4 years ago

Other workarounds could be :

In other words, none of these workarounds are ideal either...