Skip to content

Proposal for Offline CSS and JS and Customizable index.html #265

Closed
@chriddyp

Description

@chriddyp

Opening this issue to propose the officially endorsed and documented method for embedding custom, local CSS and JavaScript in Dash apps.

This development for this issue has been sponsored by an organization. Many thanks!
If your company or organization would like to sponsor development, please reach out.

Background: In Dash, HTML tags and higher-level components are embedded in the App by assigning the app.layout property to a nested hierarchy of Dash components (e.g. the components in the dash-core-components or the dash-html-components library). These components are serialized as JSON and then rendered client-side with React.js. On page load, Dash serves a very minimal HTML string which includes the blank container for rendering the app within, the component library's JavaScript and CSS, and a few meta HTML tags like the page title and the encoding (see

dash/dash/dash.py

Lines 293 to 318 in 6a1809f

def index(self, *args, **kwargs): # pylint: disable=unused-argument
scripts = self._generate_scripts_html()
css = self._generate_css_dist_html()
config = self._generate_config_html()
title = getattr(self, 'title', 'Dash')
return '''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{}</title>
{}
</head>
<body>
<div id="react-entry-point">
<div class="_dash-loading">
Loading...
</div>
</div>
<footer>
{}
{}
</footer>
</body>
</html>
'''.format(title, css, config, scripts)
).

This architecture doesn't work well for embedding custom JavaScript scripts and CSS Stylesheets because these scripts and stylesheets usually need to be included in the HTML that is served on page load.

We will support user-supplied JavaScript and CSS through two enhancements:

Enhancement 1 - Automatic

  • This "automatic" method will template in all stylesheets (CSS) and scripts (JavaScript) that are included in a static folder
  • These links will be included in alphabetical order
  • These links will be included after the component library's stylesheets and scripts
  • The server route (/static/<path:string>) for serving these files will be configured automatically by Dash

This method will be what most Dash users will use. It's very easy to use and easy to document ("To include custom CSS, just place your CSS files in static folder. Dash will take care of the rest"). Since the files will be templated alphabetically, the user can control the order (if necessary) by prefixing ordered numbers to the files (e.g. 1-loading.css, 2-app.css).

With this method, we'll be able to add custom CSS processing middleware like minifying CSS or creating cache-busting URLs completely automatically.

If the user needs more control over the placement of these files, they can use the method in "Enhancement 2 - Manual Override".

Enhancement 2 - Manual Override

Currently on page load, Dash serves this HTML string:

dash/dash/dash.py

Lines 293 to 318 in 6a1809f

def index(self, *args, **kwargs): # pylint: disable=unused-argument
scripts = self._generate_scripts_html()
css = self._generate_css_dist_html()
config = self._generate_config_html()
title = getattr(self, 'title', 'Dash')
return '''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{}</title>
{}
</head>
<body>
<div id="react-entry-point">
<div class="_dash-loading">
Loading...
</div>
</div>
<footer>
{}
{}
</footer>
</body>
</html>
'''.format(title, css, config, scripts)
.

This enhancement will make this overridable:

  • The user will assign a HTML string or a function that returns an HTML string to a property on the app object (e.g. app.index).
  • This HTML string will include several predefined template variable names that will correspond to the stylesheets and scripts that Dash needs to include to render the component libraries.
  • The user can include extra meta tags in their template or even include other HTML around their Dash app container.

Here is an example:

def custom_index():
    return '''
    <!DOCTYPE html>
    <html>

        <head>
            <meta charset="UTF-8">
            <meta description="This is my dash app">

            <title>My Custom Dash App</title>
            <link ref="stylesheet" href="/static/my-custom-css-normalize.css">
            {dash_renderer_css_bundle}
            {dash_component_css_bundles}
            <link ref="stylesheet" href="/static/my-component-css-override.css">

        </head>

        <body>
            <div>
                My Custom Header
            </div>
            <div id="react-entry-point">
                <div class="_dash-loading">
                    Loading...
                </div>
            </div>
        </body>

        <footer>
            <script type="javascript" src="/static/my-custom-javascript-bundle.js">
            {dash_renderer_javascript_bundle}
            {dash_component_javascript_bundles}
            <script type="javascript" src="/static/my-custom-javascript-override.js">
        </footer>

    </html>
    '''

app.index = custom_index

The following items will be required in creating an index string:

  • The dash_component_css_bundles, dash_component_javascript_bundles, dash_renderer_css_bundle, dash_renderer_javascript_bundle template names. Dash will look for these names and fill them in with the appropriate component CSS and JavaScript on page load.
  • A <div/> with an id react-entry-point. Dash's front-end will render the app within this div once the JavaScript has been evaluated.

Note the level of customizability in this solution:

  • The user has full control over the location of the custom JS and CSS files with respect to the auto-templated CSS and JavaScript files
  • The user can include custom meta tags in the <head/>. In the example, see the custom <title/> and custom <meta/> description.
  • The user can include custom HTML around their Dash app container (see the <div/> with My Custom Header)
  • The user can omit the templated tags if they want to supply their own front-end JavaScript bundles. This ties in nicely with the "Custom JavaScript Hooks" requirement: if the user adds their own hooks to a custom dash-renderer build, they could remove the default dash_renderer template variable and include their own version.
  • If the tags depend on the URL of the page, they could program in different values that depend on the flask.request.url variable.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions