Files
zulip/zerver/lib/storage.py
Tim Abbott ec2eb0edba storage: Don't double-minify webpack bundles.
What was happening before is that we built the webpack bundles in
tools/minify-js with nicely hashed filenames, and then `manage.py
collectstatic` was extending these filenames with a second hash
through the use of storage.

Removing the first one didn't seem ideal, but would probably have
worked, but seems confusing for people only familiar with webpack
(ideally, we want the Django toolchain piece to be increasingly
invisible as we replace it).

And we can't exclude the webpack bundles from being processed by
storage, since we need these bundles to be included in the manifest.
So, instead, we set the hash function to be a no-op for the bundle
files.

Fixes significant portions #5971.

More work is required to deal with versioning for some of the
image/font assets.
2018-06-03 16:49:59 -07:00

116 lines
4.5 KiB
Python

# Useful reading is https://zulip.readthedocs.io/en/latest/subsystems/front-end-build-process.html
import os
import shutil
from typing import Any, Dict, List, Optional, Tuple
from django.conf import settings
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
from pipeline.storage import PipelineMixin
from zerver.lib.str_utils import force_str
class AddHeaderMixin:
def post_process(self, paths: Dict[str, Tuple['ZulipStorage', str]], dry_run: bool=False,
**kwargs: Any) -> List[Tuple[str, str, bool]]:
if dry_run:
return []
with open(settings.STATIC_HEADER_FILE, 'rb') as header_file:
header = header_file.read().decode(settings.FILE_CHARSET)
# A dictionary of path to tuples of (old_path, new_path,
# processed). The return value of this method is the values
# of this dictionary
ret_dict = {}
for name in paths:
storage, path = paths[name]
if not path.startswith('min/') or not path.endswith('.css'):
ret_dict[path] = (path, path, False)
continue
# Prepend the header
with storage.open(path, 'rb') as orig_file:
orig_contents = orig_file.read().decode(settings.FILE_CHARSET)
storage.delete(path)
with storage.open(path, 'w') as new_file:
new_file.write(force_str(header + orig_contents, encoding=settings.FILE_CHARSET))
ret_dict[path] = (path, path, True)
super_class = super()
if hasattr(super_class, 'post_process'):
super_ret = super_class.post_process(paths, dry_run, **kwargs) # type: ignore # https://github.com/python/mypy/issues/2956
else:
super_ret = []
# Merge super class's return value with ours
for val in super_ret:
old_path, new_path, processed = val
if processed:
ret_dict[old_path] = val
return list(ret_dict.values())
class RemoveUnminifiedFilesMixin:
def post_process(self, paths: Dict[str, Tuple['ZulipStorage', str]], dry_run: bool=False,
**kwargs: Any) -> List[Tuple[str, str, bool]]:
if dry_run:
return []
root = settings.STATIC_ROOT
to_remove = ['js']
for tree in to_remove:
shutil.rmtree(os.path.join(root, tree))
is_valid = lambda p: all([not p.startswith(k) for k in to_remove])
paths = {k: v for k, v in paths.items() if is_valid(k)}
super_class = super()
if hasattr(super_class, 'post_process'):
return super_class.post_process(paths, dry_run, **kwargs) # type: ignore # https://github.com/python/mypy/issues/2956
return []
class IgnoreBundlesManifestStaticFilesStorage(ManifestStaticFilesStorage):
def hashed_name(self, name: str, content: Optional[str]=None, filename: Optional[str]=None) -> str:
if (name.startswith("webpack-bundles") and
os.path.splitext(name)[1] in ['.js', '.css', '.map']):
# Hack to avoid renaming already-hashnamed webpack bundles
# when minifying; this was causing every bundle to have
# two hashes appended to its name, one by webpack and one
# here. We can't just skip processing of these bundles,
# since we do need the Django storage to add these to the
# manifest for django_webpack_loader to work. So, we just
# use a no-op hash function for these already-hashed
# assets.
return name
return super().hashed_name(name, content, filename)
if settings.PRODUCTION:
# This is a hack to use staticfiles.json from within the
# deployment, rather than a directory under STATIC_ROOT. By doing
# so, we can use a different copy of staticfiles.json for each
# deployment, which ensures that we always use the correct static
# assets for each deployment.
ManifestStaticFilesStorage.manifest_name = os.path.join(settings.DEPLOY_ROOT,
"staticfiles.json")
orig_path = ManifestStaticFilesStorage.path
def path(self: ManifestStaticFilesStorage, name: str) -> str:
if name == ManifestStaticFilesStorage.manifest_name:
return name
return orig_path(self, name)
ManifestStaticFilesStorage.path = path
class ZulipStorage(PipelineMixin,
AddHeaderMixin, RemoveUnminifiedFilesMixin,
IgnoreBundlesManifestStaticFilesStorage):
pass