Repository: plotly/dash

Issue:
Dash components suites doesn't have a cache headers.
When `app.scripts.config.serve_locally=True`, the components bundles are served without a `Cache-Control` header. They are loaded from the server everytime. The same is true for the css served that way.



Discussion:

Resolving patch:
diff --git a/dash/_configs.py b/dash/_configs.py
--- a/dash/_configs.py
+++ b/dash/_configs.py
@@ -18,7 +18,8 @@ def env_configs():
         'DASH_REQUESTS_PATHNAME_PREFIX',
         'DASH_SUPPRESS_CALLBACK_EXCEPTIONS',
         'DASH_ASSETS_EXTERNAL_PATH',
-        'DASH_INCLUDE_ASSETS_FILES'
+        'DASH_INCLUDE_ASSETS_FILES',
+        'DASH_COMPONENTS_CACHE_MAX_AGE'
     )})
 
 
diff --git a/dash/dash.py b/dash/dash.py
--- a/dash/dash.py
+++ b/dash/dash.py
@@ -86,6 +86,7 @@ def __init__(
             external_scripts=None,
             external_stylesheets=None,
             suppress_callback_exceptions=None,
+            components_cache_max_age=None,
             **kwargs):
 
         # pylint-disable: too-many-instance-attributes
@@ -136,6 +137,9 @@ def __init__(
                 True),
             'assets_external_path': _configs.get_config(
                 'assets_external_path', assets_external_path, env_configs, ''),
+            'components_cache_max_age': int(_configs.get_config(
+                'components_cache_max_age', components_cache_max_age,
+                env_configs, 2678400))
         })
 
         # list of dependencies
@@ -302,11 +306,18 @@ def _relative_url_path(relative_package_path='', namespace=''):
             else:
                 self.registered_paths[namespace] = [relative_package_path]
 
-            return '{}_dash-component-suites/{}/{}?v={}'.format(
+            module_path = os.path.join(
+                os.path.dirname(sys.modules[namespace].__file__),
+                relative_package_path)
+
+            modified = int(os.stat(module_path).st_mtime)
+
+            return '{}_dash-component-suites/{}/{}?v={}&m={}'.format(
                 self.config['requests_pathname_prefix'],
                 namespace,
                 relative_package_path,
-                importlib.import_module(namespace).__version__
+                importlib.import_module(namespace).__version__,
+                modified
             )
 
         srcs = []
@@ -422,9 +433,16 @@ def serve_component_suites(self, package_name, path_in_package_dist):
             'js': 'application/JavaScript',
             'css': 'text/css'
         })[path_in_package_dist.split('.')[-1]]
+
+        headers = {
+            'Cache-Control': 'public, max-age={}'.format(
+                self.config.components_cache_max_age)
+        }
+
         return Response(
             pkgutil.get_data(package_name, path_in_package_dist),
-            mimetype=mimetype
+            mimetype=mimetype,
+            headers=headers
         )
 
     def index(self, *args, **kwargs):  # pylint: disable=unused-argument
@@ -435,7 +453,7 @@ def index(self, *args, **kwargs):  # pylint: disable=unused-argument
         title = getattr(self, 'title', 'Dash')
         if self._favicon:
             favicon = '<link rel="icon" type="image/x-icon" href="{}">'.format(
-                flask.url_for('assets.static', filename=self._favicon))
+                self.get_asset_url(self._favicon))
         else:
             favicon = ''
 
diff --git a/dash/version.py b/dash/version.py
--- a/dash/version.py
+++ b/dash/version.py
@@ -1 +1 @@
-__version__ = '0.26.5'
+__version__ = '0.26.6'


Test patch: