Black Lives Matter. Please consider donating to Black Girls Code today.
Learn how to use COVID-19 data in open source Dash apps. Register for the Sept 23rd webinar with IQT!

📣 Dash 1.5.0 released

Update: version 1.16 has been released since this was posted.

Dash 1.5.0 is a feature release focused on improving app performance.

Changelog
v1.5.0

Highlights

Previous Releases

Delving Deeper

Performance improvements

  • Asynchronously loaded xlsx (DataTable export) and plotlyjs (dcc.Graph) libraries reducing initial JS load by ~30% if unused in the initial layout
  • New async plugin and decorator to help out developing async components in the future (to be documented & added to boilerplate in the future, when usage and API stabilize)
  • Improved caching for component resources now correctly prevents refetching resources on reload
  • More improvements to come as async rolled out to more components and component packages
  • Lazy-loading can be disabled with app = Dash(__name__, eager_loading=True), making all async resources load eagerly for the app

Client-side callbacks
With this file in the assets folder

if (!window.dash_clientside) {
    window.dash_clientside = {};
}

window.dash_clientside.clientside = {
    display: function (value) {
        if (value && value.indexOf('--') === 0) {
            throw window.dash_clientside.PreventUpdate;
        }

        if (value && value.indexOf('__') === 0) {
            return window.dash_clientside.no_update;
        }

        return 'Client says "' + value + '"';
    }
}

And this app

import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import ClientsideFunction, Input, Output

app = dash.Dash(__name__)

app.layout = html.Div([
    dcc.Input(id='input'),
    html.Div(id='output-clientside'),
    html.Div(id='output-serverside')
])

@app.callback(
    Output('output-serverside', 'children'),
    [Input('input', 'value')])
def update_output(value):
    return 'Server says "{}"'.format(value)


app.clientside_callback(
    ClientsideFunction(
        namespace='clientside',
        function_name='display'
    ),
    Output('output-clientside', 'children'),
    [Input('input', 'value')]
)

if __name__ == "__main__":
    app.run_server(port=8070)

clientside

7 Likes

:trophy: :trophy: :trophy:

Really excited about this release! Some more context about “asynchronous loading”:
In Dash < 1.5.0, import any_dash_component_library (e.g. import dash_core_components or import dash_table) will automatically register that components’ JavaScript files with the Dash app's "index html" so that when you load the Dash app in your web browser, all of the imported component’s JavaScript files will be loaded as well.

For most apps, this is fine: loading the Dash app will render components from the imported component libraries right away. However, for multi-page & multi-tab applications, the imported components might not be visible on the initial page – the home page may have a dcc.Graph but the DataTable might not be displayed until you visit the second page. In this scenario, Dash 1.5.0 will actually skip loading the DataTable JavaScript files until Dash needs them to render the component. So, your initial page will load a bit faster because it’s loading & evaluating less JavaScript. When you visit the second page, Dash will “asynchronously” load the necessary dash_table.DataTable JavaScript needed to render that component.

This is particularly severe on the Dash user guide (https://dash.plot.ly) where we import every component library that we document. The home page (https://dash.plot.ly) only uses dcc.Location, dcc.Link, and some html components but the JavaScript bundles for every single component library were loaded and evaluated. This is several megabytes of content! Loading https://dash.plot.ly on a 3G network currently takes around 15 seconds - when we bring async to the rest of the component libraries (perhaps in 1.6.0) we expect to get this down to 5-6 seconds :racehorse:

This async framework also enables certain components to serve different JavaScript bundles depending on which features are used. For example, the export/download capability in dash_table.DataTable uses a heavy JavaScript file to create the downloadable Excel file. If you aren’t using this feature, then the new async famework won’t load that extra JavaScript file, enabling your app to load a bit faster. In the future, we’ll load the smallest possible plotly.js bundle (https://github.com/plotly/plotly.js/blob/master/dist/README.md#partial-bundles) necessary to render your charts.

In an ideal world, we’ll only serve the JavaScript that is absolutely necessary for rendering the components in the current view of your Dash app.That is, for your Dash apps to load as fast as possible without sacrificing the functionality you need. The async feature in 1.5.0 brings us closer to that vision. Huge :clap: to @Marc-Andre for shipping this monumental feature.

6 Likes

As always this is great to see my apps get better and improve performance without having to do much, we’re really spoiled.

One small suggestion, could you put together a small tutorial with several examples on client side callbacks: https://dash.plot.ly/

In particular details like how you would suggest setting up a developer environment to write the JavaScript? I’ve got my toes wet with client side callbacks a couple of times but I often get stuck as soon as I try something that involves more than one step as I don’t know how to inspect what properties objects have etc. And it’s usually quicker for me to run back to Python and do it on the server side.

1 Like

📣 Dash 1.5.1 released with fixes for nested JS resources fingerprinting and support for more complex apps (gunicorn, multi-page, etc.)

1 Like

I was literally about to post a support request for help handling a React error message pertaining to duplicate keys of a container with multiple children. Setting explicit keys on my Dash components made no difference to React because those keyed components weren’t the issue; it was some ancestral wrapper component generated somewhere further down the stack. (At issue was its behavior of invariably selecting a key value of “.$”.)

Thankfully I noticed this pinned post before asking for help, because after updating to dash==1.5.1 those same components are now keyed uniquely to [’.0’, ‘.1’, ‘.n’] where n is the number of items in the container! This resolves not only the console error messages I was seeing, it also, made my application significantly faster. How much faster? I was curious, so I ran a quick A/B test with Chrome Dev Tools Profiler and observed a 60-70% decrease in active CPU time for a moderately complex GUI application. Before, scripting and rendering together comprised more than 50% of overall cycles, with the greatest cost by far being scripting. Now, rendering is practically immediate and the overhead associated with JS execution has more than halved.

I want to thank you for addressing a very serious concern I had with Dash. It makes me much more optimistic that other little gripes I have with Dash will eventually be dealt with. (The two that I consider to be A Big Deal are: (1) hyphenated wildcard prefixes, and (2) callback functions, which aren’t consistent with the React philosophy of a universal application state that is simply diffed by the browser on successive state updates.) It seems like there’s a lot of developer activity and momentum to grow the framework into a general purpose Python-to-React transpiler for a wide variety of SSR applications. IMHO it is already very close, maybe 80% close, to being a better choice than Django for most SPA applications. I’m looking forward to telling my developer friends next year, “Heck, I’ve been using Dash since the early days – back when it was just a container for Plotly figures!”

1 Like

Thanks for the feedback @neoncontrails! We will get there :fist:

1 Like