Skip to content
This repository has been archived by the owner on Jun 3, 2024. It is now read-only.

Ability to choose partial plotly.js bundles #462

Closed
anders-kiaer opened this issue Feb 19, 2019 · 5 comments
Closed

Ability to choose partial plotly.js bundles #462

anders-kiaer opened this issue Feb 19, 2019 · 5 comments
Labels
dash-type-bug Something isn't working as intended

Comments

@anders-kiaer
Copy link

anders-kiaer commented Feb 19, 2019

Great library 💃 One small improvement 💡 : Currently dcc is shipped with the full plotly.min.js bundle. Do you see an easy/elegant way of enabling the user to specify one of the partial plotly.js bundles, and let e.g. the full bundle be the default?

I currently see two use cases:

  1. Reduced size (full plotly.min.js 2.8 MB, while e.g. plotly.cartesian.js, covering many applications, is 0.9 MB).
  2. Make it possible to enforce a strong CSP configuration (useful for applications where increased security is wanted).

Regarding 2), Dash alone works beautifully with a strong CSP configuration. You can e.g. do

pip install dash dash_html_components dash_core_components flask-talisman

and then run

import dash
import dash_html_components as html
import dash_core_components as dcc
from flask_talisman import Talisman

app = dash.Dash(__name__)

app.css.config.serve_locally = True
app.scripts.config.serve_locally = True

csp = {
       'default-src': '\'self\'',
       'prefetch-src': '\'self\'',
       #'script-src': ['\'self\'', '\'unsafe-eval\''],   # [1]
       #'style-src': ['\'self\'', '\'unsafe-inline\''],  # [2]
       'navigate-to': '\'self\'',
       'base-uri': '\'self\'',
       'form-action': '\'self\'',
       'frame-ancestors': '\'none\'',
       'object-src': '\'none\''
      }

# [1]: https://github.com/plotly/plotly.js/issues/897
# [2]: https://github.com/plotly/plotly.js/issues/2355

Talisman(app.server, content_security_policy=csp, force_https=False)

app.layout = html.Div(children=['Hello Dash!'])
#app.layout = html.Div(children=['Hello Dash!', dcc.Graph()])

if __name__ == '__main__':
    app.run_server(host='localhost')

This works beautifully in Dash.

However if dcc.Graph() is added to app.layout you will in the browser get "Error loading dependencies" due to violation of CSP directives script-src and style-src (these comes from plotly/plotly.js#897 and plotly/plotly.js#2355 respectively).

This can be "solved" by adding unsafe-eval and unsafe-inline, but the script-src part can be solved in a safer way by using one of the partial bundles of plotly.js instead, for applications where you don't need gl3d and gl2d.

I quickly tested overwriting the [...]/lib/python3.7/site-packages/dash_core_components/plotly[...].min.js installed in the environment with one of the partial plotly.js bundles, and that enabled (as expected) a stricter CSP without opening for eval() and its relatives.

@Marc-Andre-Rivet
Copy link
Contributor

@anders-kiaer Thanks for your suggestions. Regarding partial dcc, we are investigating a few possibilities at the moment regarding splitting & dynamically loading the libraries instead of packaging it all together. This is made more complex by the competing usage scenarios that have to all work in the final solution. We are not currently looking into making a partial distributable of dcc with a subset of features.

For CSP, thanks for including the related issues. We can keep an eye on them but there's not much that can be done there from dcc's side.

@Marc-Andre-Rivet Marc-Andre-Rivet added the dash-type-bug Something isn't working as intended label Feb 19, 2019
@alexcjohnson
Copy link
Collaborator

Support for partial plotly.js bundles though is an interesting one - I suppose there's a chance that plotly.js will implement its own lazy loading system, but barring that it would need to be up to the user to specify a bundle that they know will suit their needs.

I could certainly see implementing something like dcc.set_plotlyjs(external_url, local_path) that would override the built-in version. It looks to me as though the external version would be easy to plug in, the local one might take a little more massaging.

@mjclawar
Copy link
Contributor

Support for partial plotly.js bundles though is an interesting one - I suppose there's a chance that plotly.js will implement its own lazy loading system, but barring that it would need to be up to the user to specify a bundle that they know will suit their needs.

I could certainly see implementing something like dcc.set_plotlyjs(external_url, local_path) that would override the built-in version. It looks to me as though the external version would be easy to plug in, the local one might take a little more massaging.

@alexcjohnson I'm wondering if there's a generic function that handles this similar to how the React version can be changed in dash-renderer (plotly/dash-renderer#45 and plotly/dash-renderer#45 (comment) more specifically). Otherwise, there would likely be little functions that do similar tasks all over the Dash ecosystem.

For our use case, we would like to be able to handle things such as:

  1. One changeable version of React as an external dependency so that we can use caching across multiple Dash applications and our other non-Dash apps
  2. Modify the plotly javascript bundle (we only need cartesian as well, and in fact we just wrote our own shim to do that)
  3. Potentially modify other scripts in addition to React (like lodash) for the same reason as 1

Thoughts?

@alexcjohnson
Copy link
Collaborator

Otherwise, there would likely be little functions that do similar tasks all over the Dash ecosystem.

@mjclawar that's a good point. At the moment I'm not sure where else this would be used, I don't see anything offhand, but there could certainly be cases added in the future either within the core or in additional components folks write.

The challenge is that these resources aren't named, they're currently just collected as a list of urls/paths. So the plotlyjs bundle is currently brought in here:

_js_dist = [
{
'external_url': 'https://cdn.plot.ly/plotly-1.44.3.min.js',
'relative_package_path': 'plotly-1.44.3.min.js',
'namespace': 'dash_core_components'
},

If we were to add a name/id to this, something like 'resource_name': 'plotly.js', then we could imagine adding a feature like:

dash.replace_resource(dcc, 'plotly.js', {'external_url': 'https://cdn.plot.ly/plotly-basic-1.44.4.js'})
# or even just put the new file in your assets/ so it's loaded automatically,
# and tell us to ignore the default one:
dash.replace_resource(dcc, 'plotly.js', None)

@alexcjohnson
Copy link
Collaborator

As discussed in plotly/dash#655, since we added async resources you can simply put the plotly.js bundle you want into your assets folder - or a link to a CDN version in dash.Dash(external_scripts=[...]) - and the built-in version will not be loaded, only your new one will. Documentation to follow https://github.com/plotly/dash-docs/issues/723

The only additional piece to consider is if we want to include a predefined set of plotly.js bundles with the package; I don't think we should do that though, there are just too many different possibilities users may want.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
dash-type-bug Something isn't working as intended
Projects
None yet
Development

No branches or pull requests

4 participants