Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions scripts/define-shims.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Make Shiny's global dependencies require()-able. A non-trivial amount of Shiny's
// legacy JS code depends on these globals, but it seems reasonable to think other
// downstream code want to require('jquery') (panel_absolute() does that via
// require('jquery-ui')).
define('jquery', [], function() { return jQuery });
define('bootstrap', [], function() { return bootstrap });

// Since HTMLDependency()s are designed to be loaded via a <script> tag, we do our best
// to avoid anonymous define() calls (which will error out in a script tag)
// https://requirejs.org/docs/errors.html#mismatch
//
// One way to approach this is to lean on the data-requiremodule attribute, which
// requirejs happens to set when it loads scripts in the browser
// https://github.com/requirejs/requirejs/blob/898ff9/require.js#L1897-L1902
const oldDefine = window.define;
window.define = function define(name, deps, callback) {
if (typeof name !== 'string') {
callback = deps;
deps = name;
name = document.currentScript.getAttribute('data-requiremodule')
}
return oldDefine.apply(this, [name, deps, callback]);
}
for(var prop in oldDefine) {
if (oldDefine.hasOwnProperty(prop)) {
window.define[prop] = oldDefine[prop];
}
}

// Users can still encounter this anonymous define() error, and we've actually
// encountered this in shiny-server-client.js because we were inlining an internal
// dependency that contained an anonymous define()
// https://github.com/rstudio/shiny-server-client/blob/5ee5aac/dist/shiny-server-client.js#L4271-L4272).
// In this case, it doesn't seem like there is anything we can do to help the situation
// other than to add some more context to the error.
window.requirejs.onError = function(err) {
if (err.message.includes('Mismatched anonymous define()')) {
err.message = err.message + '\n\nConsider either adding a data-requiremodule attribute (with the module name) to the <script> tag containing the anonymous define() or removing the anonymous define() altogether.';
}
throw err;
}
16 changes: 16 additions & 0 deletions scripts/htmlDependencies.R
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,19 @@ withr::with_options(
# we still do have bs3compat's CSS on the page, which
# comes in via the bootstrap HTMLDependency()
unlink("shiny/www/shared/bs3compat/", recursive = TRUE)

requirejs <- file.path(www, "shared", "requirejs")
dir.create(requirejs)
download.file(
"https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js",
file.path(requirejs, "require.min.js")
)

shims <- file.path(getwd(), "scripts", "define-shims.js")

cat(
"\n\n",
paste(readLines(shims), collapse = "\n"),
file = file.path(requirejs, "require.min.js"),
append = TRUE
)
9 changes: 7 additions & 2 deletions shiny/_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

from ._autoreload import autoreload_url, InjectAutoreloadMiddleware
from ._connection import Connection, StarletteConnection
from .html_dependencies import jquery_deps, shiny_deps
from .html_dependencies import require_deps, jquery_deps, shiny_deps
from .ui._html_dependencies import bootstrap_deps
from .http_staticfiles import StaticFiles
from .reactive import on_flushed
from .session import Inputs, Outputs, Session, session_context
Expand Down Expand Up @@ -327,5 +328,9 @@ def _register_web_dependency(self, dep: HTMLDependency) -> None:

def _render_page(ui: Union[Tag, TagList], lib_prefix: str) -> RenderedHTML:
ui_res = copy.copy(ui)
ui_res.insert(0, [jquery_deps(), shiny_deps()])
# N.B. make sure that jQuery, Shiny, and Bootstrap all come before requirejs
# since most of our JS code currently assumes they are attached on the window
# (if these dependencies come after requirejs, they define() themselves rather
# than attaching to the window, which will mess up some of our legacy JS code)
ui_res.insert(0, [jquery_deps(), shiny_deps(), bootstrap_deps(), require_deps()])
return HTMLDocument(ui_res).render(lib_prefix=lib_prefix)
13 changes: 13 additions & 0 deletions shiny/html_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,16 @@ def jquery_deps() -> HTMLDependency:
source={"package": "shiny", "subdir": "www/shared/jquery/"},
script={"src": "jquery-3.6.0.min.js"},
)


# N.B. py-shiny has requirejs as a 'core' dependency since it's currently a pretty
# fundamental dependency for any Jupyter widget project (i.e., ipyshiny). And, for
# requirejs to work at all with our HTMLDependency() model (i.e., loading JS via
# <script> tags), we need to shim define() to avoid anonymous module errors
def require_deps() -> HTMLDependency:
return HTMLDependency(
name="requirejs",
version="2.3.6",
source={"package": "shiny", "subdir": "www/shared/requirejs/"},
script={"src": "require.min.js"},
)
9 changes: 7 additions & 2 deletions shiny/ui/_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
h2,
css,
span,
HTML,
)

from .._docstring import add_example
Expand Down Expand Up @@ -449,7 +448,13 @@ def panel_absolute(
divTag.add_class("draggable")
deps = jqui_deps()
deps.stylesheet = []
return TagList(deps, divTag, tags.script(HTML('$(".draggable").draggable();')))
return TagList(
deps,
divTag,
# N.B. when jquery-ui define()s itself, it mutates the global jQuery object
# so even though we may be require()ing it, we still need to use the global $.
tags.script('require(["jquery-ui"], () => $(".draggable").draggable() )'),
)


def help_text(*args: TagChildArg, **kwargs: TagAttrArg) -> Tag:
Expand Down
8 changes: 7 additions & 1 deletion shiny/ui/_html_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ def jqui_deps() -> HTMLDependency:
name="jquery-ui",
version="1.12.1",
source={"package": "shiny", "subdir": "www/shared/jqueryui/"},
script={"src": "jquery-ui.min.js"},
# N.B. With help of our monkey-patched version of define(), this
# data-requiremodule prevents an anonymous define() error from happening when
# the script executes https://github.com/rstudio/py-shiny/pull/160
script={
"src": "jquery-ui.min.js",
"data-requiremodule": "jquery-ui",
},
stylesheet={"href": "jquery-ui.min.css"},
)
43 changes: 43 additions & 0 deletions shiny/www/shared/requirejs/require.min.js

Large diffs are not rendered by default.

Loading