mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	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.
		
			
				
	
	
		
			116 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			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
 |