Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] Enable external serving of Vizro assets #775

Merged
merged 23 commits into from
Oct 10, 2024

Conversation

antonymilne
Copy link
Contributor

@antonymilne antonymilne commented Oct 3, 2024

Description

  • Change our weird (but remarkably effective) flask blueprint scheme for serving Vizro assets into a more conventional Dash library scheme for handling resources
  • This makes it possible to serve all our resources through the CDN when serve_locally=False (previously this was possible only for the library CSS, which was just figures.css)
  • Fix serving of fonts, which previously was putting extraneous <link rel="stylesheet"> in our HTML source
  • Update JS so that it works in-browser without being a module - this is needed due to the new way assets are served

To do:

  • @antonymilne: maybe write unit tests
  • @antonymilne: check https://vizro--775.org.readthedocs.build/en/775/pages/user-guides/run/
  • @huong-li-nguyen: manual test + make sure you understand how everything works with the following - I've already done the first two but good to have a second pair of eyes on it. Please check the 404 page on these and also that the order of CSS is ok since I didn't check that.
    • serve_locally=True Vizro app (try it disconnected from Internet)
    • serve_locally=False Vizro app
    • serve_locally=True Dash app using Vizro library
    • serve_locally=False Dash app using Vizro library (less important to test, because it's an edge case and also if the above work then I'm confident this does too)

Notes (will go in updated developer docs)

When serve_locally=True (the default), Dash serves component library resources (generally CSS and JS) through the Flask server using the _dash-component-suites route.

  • For Vizro library components (currently just KPI cards), this should happen when Vizro is imported.
  • For Vizro framework components (everything else), this should happen only when Vizro() is instantiated.

This makes our footprint as small as possible and ensures there's reduced risk of CSS name clash when someone wants to use Vizro just as a library but doesn't instantiate Vizro() (not common at all now, but maybe will be in the future).

When serve_locally=False, Dash switches to serving resources through external_url, where it's specified. For Vizro components we use jsDeliver as a CDN for this.

A few complications:

  • files that aren't CSS/JS (e.g. fonts, maps) can still be served through the same routes but should not have a <script> or <link> in the HTML source. This is achieved with dynamic=True
  • the CDN minifies CSS/JS files automatically, but some we have minified manually ourselves (currently just vizro-bootstrap.min.css)
  • it's not possible to serve JS as modules this way, which means we can't easily do import/export in them

In future:

  • when we release vizro-bootstrap, nothing changes for Vizro framework. Pure Dash users would treat it like any other bootstrap theme i.e. set it through external_stylesheets that points to the stylesheet on our CDN or download the file to their own assets folder. We'd have a shortcut for this like vizro.theme or do it through bootswatch if that's possible.
  • ideally we would webpack our JS and ship the .min.js rather than just relying on the CDN to minify it. This would let us write "proper" rather than just in-browser JS and mean we benefit from tree-shaking etc. rather than just minification that the CDN does. In reality the optimisations would make very little difference to performance, but it's kind of the "right" way to do things. It's more effort than it's worth to set up at the moment, but if we end up maintaining a bigger JS codebase we might do it
  • vizro-boostrap.css map file + all the SASS should live in the repo so that it can be handled correctly through developer tools in a browser

Notice

  • I acknowledge and agree that, by checking this box and clicking "Submit Pull Request":

    • I submit this contribution under the Apache 2.0 license and represent that I am entitled to do so on behalf of myself, my employer, or relevant third parties, as applicable.
    • I certify that (a) this contribution is my original creation and / or (b) to the extent it is not my original creation, I am authorized to submit this contribution on behalf of the original creator(s) or their licensees.
    • I certify that the use of this contribution as authorized by the Apache 2.0 license does not violate the intellectual property rights of anyone else.
    • I have not referenced individuals, products or companies in any commits, directly or indirectly.
    • I have not added data or restricted code in any commits, directly or indirectly.

@antonymilne antonymilne changed the title Remove blueprint scheme for asset serving, make JS work in browser [Feat] Enable external serving of Vizro assets Oct 3, 2024
Copy link
Contributor

@petar-qb petar-qb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huong-li-nguyen
Copy link
Contributor

huong-li-nguyen commented Oct 4, 2024

When serve_locally=True, Dash switches to serving resources through external_url, where it's specified. For Vizro components we use jsDeliver as a CDN for this.

I think you meant when serve_locally=False

@huong-li-nguyen
Copy link
Contributor

huong-li-nguyen commented Oct 4, 2024

  • @huong-li-nguyen: manual test + make sure you understand how everything works with the following - I've already done the first two but good to have a second pair of eyes on it. Please check the 404 page on these and also that the order of CSS is ok since I didn't check that.

    • serve_locally=True Vizro app (try it disconnected from Internet)
    • serve_locally=False Vizro app
    • serve_locally=True Dash app using Vizro library
    • serve_locally=False Dash app using Vizro library (less important to test, because it's an edge case and also if the above work then I'm confident this does too)

I've tested all above manually and everything seems to work fine 🥳 Just the order of reading in the vizro-bootstrap.min.css file I would change to how it was before. It's safer so that we can ensure overwrites work as expected.

Manual test case Expected Result
serve_locally=True Vizro app (w/o internet) - Looks as expected
- <link rel=stylesheet> for all our CSS/JS files via href="/_dash_component-suites/vizro/..."
- user assets via href "/assets"
- no <link rel=stylesheet> for fonts
serve_locally=False Vizro app (w/ debug=False ) - Looks as expected
- <link rel=stylesheet> for all our CSS/JS files via href="https://cdn.jsdelivr.net/gh/mckinsey..."
- user assets via href "/assets"
- no <link rel=stylesheet> for fonts
serve_locally=True Dash app using Vizro library (w/o internet) - Looks as expected
- <link rel=stylesheet> only for figures.css via href="/_dash_component-suites/vizro/..."
- user assets via href "/assets"
- no <link rel=stylesheet> for fonts or any other of our css files
serve_locally=False Dash app using Vizro library (w/ debug=False) - Looks as expected
- <link rel=stylesheet> only for figures.css via href="https://cdn.jsdelivr.net/gh/mckinsey..."
- user assets via href "/assets"
- no <link rel=stylesheet> for fonts or any other of our css files

Copy link
Contributor

@huong-li-nguyen huong-li-nguyen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval under the condition to change the order of reading in the vizro-bootstrap.min.css file (should be read in first) before figures.css or any other of our css files 🚀

vizro-core/src/vizro/__init__.py Show resolved Hide resolved
vizro-core/src/vizro/models/_dashboard.py Show resolved Hide resolved
vizro-core/src/vizro/_vizro.py Outdated Show resolved Hide resolved
This reverts commit fb4540a.
@antonymilne
Copy link
Contributor Author

@huong-li-nguyen thank you for all the testing! 🙏

@petar-qb

I guess these tickets could be closed after merging this: McK-Internal/vizro-internal#1050 and McK-Internal/vizro-internal#1267

Ah that would be a nice bonus! I don't understand these tickets well enough to check unfortunately. Please could you test when you review to see if they're fixed?

@petar-qb
Copy link
Contributor

petar-qb commented Oct 8, 2024

@antonymilne @huong-li-nguyen

Ah that would be a nice bonus! I don't understand these tickets well enough to check unfortunately. Please could you test when you review to see if they're fixed?

So, there are two tickets that I thought are solved with this PR:

  1. https://github.com/McK-Internal/vizro-internal/issues/1267

The problem was that user's js scripts added to the /assets folder were loaded before Vizro js /static scripts. This disabled the possibility to overwrite vizro js default functions.

I guess that the self.dash.scripts.append_script(...) line solved this issue. (or moving clientside_functions.js logic to other files)

Here's an example on how it can be tested.

Let's say that you want to disable automatically collapsing of the nav_panel when the screen's height/width is less than 576px (the behaviour that's implemented within the dashboard.js in the function called collapse_nav_panel that's assigned as a dash.clientside_callback in the Dashboard.build).

From the main branch:
If you add the following custom js code inside the assets/js/custom.js, execution of this script will raise an exception:
"Cannot read properties of undefined (reading 'clientside')" (this script is executed too early).

window.dash_clientside.clientside.collapse_nav_panel = function (n_clicks, is_open) {
  return [
    true,
    {
      transform: "rotate(0deg)",
      transition: "transform 0.35s ease-in-out",
    },
    "Hide Menu",
  ];
}

From the feat/allow-servering-external-assets branch:
if the same function is assigned to the window.dash_clientside.dashboard.collapse_nav_panel from the same file (assets/js/custom.js), this custom javascript function will properly overwrite default one (from the dashboard.js).

So, when this PR is merged, we can close the Issue -> https://github.com/McK-Internal/vizro-internal/issues/1267


  1. https://github.com/McK-Internal/vizro-internal/issues/1050

FYI @nadijagraca

This issue is not introduced with Vizro, and it exists in the dash app as well (for multi page apps). Also, this issue is not solved with this PR (and probably can't be solved because it's not the issue at all 😄). The issue says that if you, for example, add something like document.getElementById("page-1-component-1-id").addEventListener("click", myScript); directly to the assets/js/custom.js, this will be executed before the page-1 is opened. As javascript can't find the "page-1-component-1-id" component, it means that myScript will not be executed when the component is clicked. All we can do is to explain to user different ways to overcome this problem (as it's already explained in the issue description)

So, when this PR is merged, we should not close the Issue -> https://github.com/McK-Internal/vizro-internal/issues/1050

@antonymilne antonymilne enabled auto-merge (squash) October 9, 2024 15:09
@antonymilne
Copy link
Contributor Author

Thanks very much for the careful reviewing @petar-qb and @huong-li-nguyen, much appreciated! I wrote some tests in 1ac4535 and now it's merging :shipit:

@antonymilne
Copy link
Contributor Author

antonymilne commented Oct 10, 2024

Phew, I really thought I had got this right before but there was a screenshot test failing and so I had to go back again to change the order slightly 😬

The order Dash inserts stylesheets is always:

  1. external_stylesheets
  2. library stylesheets as we add on import vizro
  3. stylesheets added through append_css as we do in Vizro()
  4. user assets (also go through append_css but only when server gets its first request)

The problem was that figures.css was served in stage 2 and therefore could come before vizro-bootstrap.min.css. I hoped this wouldn't cause any issues but unfortunately it did...

So now what we do is remove the library stylesheets in Vizro() and then add them using the framework's append_css mechanism. This means that vizro-boostrap.min.css always comes first for a framework user because we sort the stylesheets added in stage 3 to put it first (the rest are in alphabetical order). For a Dash user, it will be specified using external_stylesheets so always come first anyway.

Here's the results. Crucially vizro-bootstrap comes first for all of them.

Vizro app with serve_locally=False

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/vizro-bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/aggrid.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/bootstrap_overwrites.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/code.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/collapse.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/container.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/datepicker.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/dropdown.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/figures.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/images.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/index.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/layout.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/loading.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/scroll_bar.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/slider.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/table.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/tabs.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/tiles.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/toggle.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/token_names.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/tooltip.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/variables.min.css">
<link rel="stylesheet" href="/assets/css/custom.css?m=1727862730.5144594">

Vizro app with serve_locally=True

<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/vizro-bootstrap.v0_1_25_dev0m1728550858.min.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/aggrid.v0_1_25_dev0m1717170653.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/bootstrap_overwrites.v0_1_25_dev0m1727862730.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/code.v0_1_25_dev0m1724416429.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/collapse.v0_1_25_dev0m1712321054.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/container.v0_1_25_dev0m1712321054.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/datepicker.v0_1_25_dev0m1712321054.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/dropdown.v0_1_25_dev0m1717170653.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/figures.v0_1_25_dev0m1727984056.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/images.v0_1_25_dev0m1712321054.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/index.v0_1_25_dev0m1724152368.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/layout.v0_1_25_dev0m1728554951.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/loading.v0_1_25_dev0m1724061146.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/scroll_bar.v0_1_25_dev0m1724061146.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/slider.v0_1_25_dev0m1717170653.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/table.v0_1_25_dev0m1726500262.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/tabs.v0_1_25_dev0m1728554951.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/tiles.v0_1_25_dev0m1726500262.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/toggle.v0_1_25_dev0m1717170653.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/token_names.v0_1_25_dev0m1724163095.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/tooltip.v0_1_25_dev0m1724061146.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/variables.v0_1_25_dev0m1724084501.css">
<link rel="stylesheet" href="/assets/css/custom.css?m=1727862730.5144594">

Dash app with serve_locally=False

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/vizro-bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/figures.min.css">
<link rel="stylesheet" href="/assets/css/custom.css?m=1727862730.5144594">

Dash app with serve_locally=True

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mckinsey/vizro@feat/allow-servering-external-assets/vizro-core/src/vizro/static/css/vizro-bootstrap.min.css">
<link rel="stylesheet" href="/_dash-component-suites/vizro/static/css/figures.v0_1_25_dev0m1727984056.css">
<link rel="stylesheet" href="/assets/css/custom.css?m=1727862730.5144594">

@antonymilne antonymilne disabled auto-merge October 10, 2024 11:11
@antonymilne antonymilne enabled auto-merge (squash) October 10, 2024 11:11
@antonymilne antonymilne disabled auto-merge October 10, 2024 11:11
@antonymilne antonymilne merged commit 2d41b0b into main Oct 10, 2024
33 of 34 checks passed
@antonymilne antonymilne deleted the feat/allow-servering-external-assets branch October 10, 2024 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants