robservable icon indicating copy to clipboard operation
robservable copied to clipboard

Offline support

Open juba opened this issue 4 years ago • 5 comments

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

juba avatar Jun 12 '20 07:06 juba

@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 avatar Jun 14 '20 14:06 timelyportfolio

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 avatar Jun 14 '20 15:06 timelyportfolio

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);"
  )
))

timelyportfolio avatar Jun 14 '20 16:06 timelyportfolio

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 avatar Jun 16 '20 13:06 juba

Other workarounds could be :

  • Asking observable to distribute notebooks in another format in their API, in addition to ES6 modules
  • Using a bundler locally such as webpack to convert ES6 module into another format (but of course the bundler has to be installed locally)

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

juba avatar Jun 16 '20 16:06 juba