custom manifest static files storage that also minify scss and js files

This commit is contained in:
thomas girod 2024-08-14 18:50:46 +02:00
parent 2e1f16fa04
commit b7261ec629
8 changed files with 107 additions and 82 deletions

View File

@ -40,8 +40,7 @@ jobs:
poetry install --with prod --without docs,tests
poetry run ./manage.py install_xapian
poetry run ./manage.py migrate
echo "yes" | poetry run ./manage.py collectstatic
poetry run ./manage.py compilestatic
poetry run ./manage.py collectstatic --clear --noinput
poetry run ./manage.py compilemessages
sudo systemctl restart uwsgi

View File

@ -39,8 +39,7 @@ jobs:
poetry install --with prod --without docs,tests
poetry run ./manage.py install_xapian
poetry run ./manage.py migrate
echo "yes" | poetry run ./manage.py collectstatic
poetry run ./manage.py compilestatic
poetry run ./manage.py collectstatic --clear --noinput
poetry run ./manage.py compilemessages
sudo systemctl restart uwsgi

View File

@ -1,69 +0,0 @@
#!/usr/bin/env python3
#
# Copyright 2017
# - Sli <antoine@bartuccio.fr>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
import sys
import sass
from django.conf import settings
from django.core.management.base import BaseCommand
class Command(BaseCommand):
"""Compiles scss in static folder for production."""
help = "Compile scss files from static folder"
def compile(self, filename: str):
args = {
"filename": filename,
"include_paths": settings.STATIC_ROOT.name,
"output_style": "compressed",
}
if settings.SASS_PRECISION:
args["precision"] = settings.SASS_PRECISION
return sass.compile(**args)
def handle(self, *args, **options):
if not settings.STATIC_ROOT.is_dir():
raise Exception(
"No static folder availaible, please use collectstatic before compiling scss"
)
to_exec = list(settings.STATIC_ROOT.rglob("*.scss"))
if len(to_exec) == 0:
self.stdout.write("Nothing to compile.")
sys.exit(0)
self.stdout.write("---- Compiling scss files ---")
for file in to_exec:
# remove existing css files that will be replaced
# keeping them while compiling the scss would break
# import statements resolution
css_file = file.with_suffix(".css")
if css_file.exists():
css_file.unlink()
compiled_files = {file: self.compile(str(file.resolve())) for file in to_exec}
for file, scss in compiled_files.items():
file.replace(file.with_suffix(".css")).write_text(scss)
self.stdout.write(
"Files compiled : \n" + "\n- ".join(str(f) for f in compiled_files)
)

View File

@ -18,7 +18,7 @@
{% endblock %}
<link rel="preload" as="style" href="{{ static('vendored/font-awesome/css/font-awesome.min.css') }}" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="{{ static('vendored/font-awesome/css/font-awesome.min.css') }}"></noscript>
<script defer href="{{ static('vendored/font-awesome/js/fontawesone.min.js') }}"></script>
<script defer href="{{ static('vendored/font-awesome/js/fontawesome.min.js') }}"></script>
<!-- Jquery declared here to be accessible in every django widgets -->
<script src="{{ static('vendored/jquery/jquery-3.6.2.min.js') }}"></script>

View File

@ -107,4 +107,4 @@ def scss(path):
if storage.exists(css_path):
storage.delete(css_path)
storage.save(css_path, ContentFile(content))
return static(css_path)
return static(str(css_path))

View File

@ -11,17 +11,31 @@ Nous utilisons du SCSS dans le projet.
En environnement de développement (`DEBUG=True`),
le SCSS est compilé à chaque fois que le fichier est demandé.
Pour la production, le projet considère
que chacun des fichiers est déjà compilé et,
pour ce faire, il est nécessaire
d'utiliser les commandes suivantes dans l'ordre :
que chacun des fichiers est déjà compilé.
C'est pourquoi le SCSS est automatiquement compilé lors
de la récupération des fichiers statiques.
Les fichiers JS sont également automatiquement minifiés.
Il peut être judicieux de supprimer les anciens fichiers
statiques avant de collecter les nouveaux.
Pour ça, ajoutez le flag `--clear` à la commande `collectstatic` :
```bash
python ./manage.py collectstatic # Pour récupérer tous les fichiers statiques
python ./manage.py compilestatic # Pour compiler les fichiers SCSS qu'ils contiennent
python ./manage.py collectstatic --clear
```
!!!tip
Le dossier où seront enregistrés ces fichiers
statiques peut être changé en modifiant la variable
`STATIC_ROOT` dans les paramètres.
`STATIC_ROOT` dans les paramètres.
!!!warning
La minification des fichiers JS nécessite la présence
de `uglifyJS` sur la machine.
Pour l'installer, faites la commande suivante (nécessite nodeJS) :
```bash
npm install uglifyjs -g
```

View File

@ -273,6 +273,15 @@ STATICFILES_FINDERS = [
"sith.finders.ScssFinder",
]
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "sith.storage.SithStorage",
},
}
# Auth configuration
AUTH_USER_MODEL = "core.User"
AUTH_ANONYMOUS_MODEL = "core.models.AnonymousUser"
@ -716,7 +725,7 @@ if TESTING:
"BACKEND": "django.core.files.storage.InMemoryStorage",
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
"BACKEND": "sith.storage.SithStorage",
},
}

73
sith/storage.py Normal file
View File

@ -0,0 +1,73 @@
import logging
import subprocess
import warnings
import sass
from django.conf import settings
from django.contrib.staticfiles.storage import (
ManifestStaticFilesStorage,
)
from django.core.files.storage import Storage
class SithStorage(ManifestStaticFilesStorage):
def _compile_scss(self):
to_exec = list(settings.STATIC_ROOT.rglob("*.scss"))
if len(to_exec) == 0:
return
for file in to_exec:
# remove existing css files that will be replaced
# keeping them while compiling the scss would break
# import statements resolution
css_file = file.with_suffix(".css")
if css_file.exists():
css_file.unlink()
scss_paths = [p.resolve() for p in to_exec if p.suffix == ".scss"]
base_args = {"output_style": "compressed", "precision": settings.SASS_PRECISION}
compiled_files = {
p: sass.compile(filename=str(p), **base_args) for p in scss_paths
}
for file, scss in compiled_files.items():
file.replace(file.with_suffix(".css")).write_text(scss)
# once the files are compiled, the manifest must be updated
# to have the right suffix
new_entries = {
k.replace(".scss", ".css"): self.hashed_files.pop(k).replace(
".scss", ".css"
)
for k in list(self.hashed_files.keys())
if k.endswith(".scss")
}
self.hashed_files.update(new_entries)
self.save_manifest()
@staticmethod
def _minify_js():
try:
subprocess.run(["uglifyjs", "-v"])
except FileNotFoundError:
warnings.warn(
"Couldn't minify JS files. Make sure UglifyJs is installed.",
stacklevel=1,
)
return
to_exec = [
p for p in settings.STATIC_ROOT.rglob("*.js") if ".min" not in p.suffixes
]
for path in to_exec:
p = path.resolve()
subprocess.run(["uglifyjs", p, "-o", p])
logging.getLogger("main").info(f"Minified {path}")
def post_process(
self, paths: dict[str, tuple[Storage, str]], *, dry_run: bool = False
):
# Whether we get the files that were processed by ManifestFilesMixin
# by calling super() or whether we get them from the manifest file
# makes no difference - we have to open the manifest file anyway
# because we need to update the paths stored inside it.
yield from super().post_process(paths, dry_run)
self._compile_scss()
self._minify_js()