Skip to content

Only link .css files in assets as stylesheets - #4105

Open
Thanatos9404 wants to merge 3 commits into
plotly:devfrom
Thanatos9404:fix-assets-css-extension
Open

Thanatos9404 wants to merge 3 commits into
plotly:devfrom
Thanatos9404:fix-assets-css-extension

Conversation

@Thanatos9404

@Thanatos9404 Thanatos9404 commented Nov 1, 2026 •

Copy link
Copy Markdown

Fixes #1684.

Dash decided how to load files in assets/ with suffix checks: endswith("css") linked theme.scss (or any name ending in "css") as a stylesheet, and endswith("js") added files such as nodejs or bundle.cjs as scripts. Hot reload repeated the same checks and only told the renderer whether a file was CSS.

Now one helper, _asset_type(), maps the extension (.js, .mjs, .css) to how Dash loads the file. The assets walk, the .mjs module check at render time and both hot reload branches use it, and the hot reload payload sends that type instead of is_css. The renderer swaps the stylesheet in place for type === 'css' and reloads the page for anything else, as before.

One behaviour change: saving a .scss file inside assets/ now triggers a full page reload, as other non-CSS assets do. Sass sources can be kept outside assets/ or excluded with assets_ignore.

Contributor Checklist

  • I have broken down my PR scope into the following TODO tasks
    • Load assets by extension through one helper, at startup and on hot reload
    • Send the asset type to the renderer instead of is_css
    • Test both paths
  • I have run the tests locally and they passed. (refer to testing section in contributing)
  • I have added tests, or extended existing tests, to cover any new features or bugs fixed in this PR

optionals

  • I have added entry in the CHANGELOG.md

The two tests in tests/unit/test_resources.py cover the page head and hot reload (add and delete) and fail without the change. A dash_duo check with the dev bundle patched the same way confirmed that a CSS edit still swaps the stylesheet without a reload and a .scss edit reloads the page.

The assets folder treated any file ending in "css" as a stylesheet, so .scss files were linked in the page head and hot-reloaded as CSS. Check for the .css extension in the initial walk and in hot reload. Fixes plotly#1684.
@KoolADE85

Copy link
Copy Markdown
Contributor

Thanks for submitting this. The team will take a look and follow up.

@T4rk1n T4rk1n left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The .css check is right, but it's still a suffix check and only the CSS side got fixed. The JS side next to it still uses endswith("js"), and .mjs is handled by yet another suffix check at render time. The type check is also copied in three places (_walk_assets_directory, plus the add and delete paths in _on_assets_change), so the next file type will have the same problem. I'd rather see one helper that maps a file's extension (os.path.splitext) to how Dash loads it, used by both the walk and hot reload. I'd hold the merge for that.

Comment thread dash/dash.py Outdated
self._add_assets_resource(path, full)
)
elif f.endswith("css"):
elif f.endswith(".css"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The js check just above is still endswith("js"), so a file named nodejs or bundle.cjs in assets gets added as a script tag. Can you move both checks into one helper that uses os.path.splitext(f)[1] against a set of known extensions (.js, .mjs, .css), and call it here and in _on_assets_change? That way a new type only needs adding in one place.

Comment thread dash/dash.py Outdated
"url": self.get_asset_url(asset_path),
"modified": int(modified),
"is_css": filename.endswith("css"),
"is_css": filename.endswith(".css"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is_css is the only file type the hot reload payload knows about. Everything else, .mjs included, causes a full reload. If the helper returns the file type, can we send that to the renderer instead of a CSS yes/no?

Comment thread dash/dash.py Outdated

if filename not in self._assets_files and not deleted:
res = self._add_assets_resource(asset_path, filename)
if filename.endswith("js"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same endswith("js") here and in the delete branch below. These should use the same helper as the walk, so loading and hot reload can't drift apart again.

_asset_type() maps a file's extension (.js, .mjs, .css) to how Dash
loads it. The assets walk, the .mjs check at render time and both hot
reload branches use it, so files like nodejs or bundle.cjs are no longer
added as scripts. The hot reload payload now sends that type instead of
is_css, and the renderer checks for type 'css'.
@Thanatos9404

Copy link
Copy Markdown
Author

Thanks, done in ff7e293. _asset_type() maps the extension (.js, .mjs, .css) to how the file is loaded, and the assets walk, the .mjs check at render time and both hot reload branches now use it, so nodejs and bundle.cjs are skipped. The hot reload payload sends that type instead of is_css, and the Reloader checks type === 'css'.

@sonarqubecloud

sonarqubecloud Bot commented Nov 3, 2026

Copy link
Copy Markdown

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.

Dash automatically includes SCSS files in the asset folder as stylesheets in the html head

3 participants