From e10c1688f96e3b2d202fe401472b7b25f6105969 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Sat, 18 Mar 2023 14:05:41 +0100 Subject: [PATCH] Fixed #34322 -- Made ES module support to ManifestStaticFilesStorage optional. Co-authored-by: Author: Claude Paroz --- django/contrib/staticfiles/storage.py | 52 +++++++----- docs/ref/contrib/staticfiles.txt | 9 +- docs/releases/4.2.txt | 6 +- tests/staticfiles_tests/test_storage.py | 108 ++++++++++++++---------- 4 files changed, 103 insertions(+), 72 deletions(-) diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index df02906ebc..02d47c56dc 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -47,6 +47,34 @@ class StaticFilesStorage(FileSystemStorage): class HashedFilesMixin: default_template = """url("%(url)s")""" max_post_process_passes = 5 + support_js_module_import_aggregation = False + _js_module_import_aggregation_patterns = ( + "*.js", + ( + ( + ( + r"""(?Pimport(?s:(?P[\s\{].*?))""" + r"""\s*from\s*['"](?P[\.\/].*?)["']\s*;)""" + ), + """import%(import)s from "%(url)s";""", + ), + ( + ( + r"""(?Pexport(?s:(?P[\s\{].*?))""" + r"""\s*from\s*["'](?P[\.\/].*?)["']\s*;)""" + ), + """export%(exports)s from "%(url)s";""", + ), + ( + r"""(?Pimport\s*['"](?P[\.\/].*?)["']\s*;)""", + """import"%(url)s";""", + ), + ( + r"""(?Pimport\(["'](?P.*?)["']\))""", + """import("%(url)s")""", + ), + ), + ) patterns = ( ( "*.css", @@ -72,34 +100,14 @@ class HashedFilesMixin: r"(?m)(?P)^(//# (?-i:sourceMappingURL)=(?P.*))$", "//# sourceMappingURL=%(url)s", ), - ( - ( - r"""(?Pimport(?s:(?P[\s\{].*?))""" - r"""\s*from\s*['"](?P[\.\/].*?)["']\s*;)""" - ), - """import%(import)s from "%(url)s";""", - ), - ( - ( - r"""(?Pexport(?s:(?P[\s\{].*?))""" - r"""\s*from\s*["'](?P[\.\/].*?)["']\s*;)""" - ), - """export%(exports)s from "%(url)s";""", - ), - ( - r"""(?Pimport\s*['"](?P[\.\/].*?)["']\s*;)""", - """import"%(url)s";""", - ), - ( - r"""(?Pimport\(["'](?P.*?)["']\))""", - """import("%(url)s")""", - ), ), ), ) keep_intermediate_files = True def __init__(self, *args, **kwargs): + if self.support_js_module_import_aggregation: + self.patterns += (self._js_module_import_aggregation_patterns,) super().__init__(*args, **kwargs) self._patterns = {} self.hashed_files = {} diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index 1b5c4b6abc..d446f833ea 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -300,6 +300,11 @@ method). The regular expressions used to find those paths * The `@import`_ rule and `url()`_ statement of `Cascading Style Sheets`_. * `Source map`_ comments in CSS and JavaScript files. + +Subclass ``ManifestStaticFilesStorage`` and set the +``support_js_module_import_aggregation`` attribute to ``True``, if you want to +use the experimental regular expressions to cover: + * The `modules import`_ in JavaScript. * The `modules aggregation`_ in JavaScript. @@ -342,8 +347,8 @@ argument. For example:: .. versionchanged:: 4.2 - Support for finding paths to JavaScript modules in ``import`` and - ``export`` statements was added. + Experimental optional support for finding paths to JavaScript modules in + ``import`` and ``export`` statements was added. .. attribute:: storage.ManifestStaticFilesStorage.manifest_hash diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt index be47767bf6..0fb3e4a2d9 100644 --- a/docs/releases/4.2.txt +++ b/docs/releases/4.2.txt @@ -202,8 +202,10 @@ Minor features ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * :class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage` now - replaces paths to JavaScript modules in ``import`` and ``export`` statements - with their hashed counterparts. + has experimental support for replacing paths to JavaScript modules in + ``import`` and ``export`` statements with their hashed counterparts. If you + want to try it, subclass ``ManifestStaticFilesStorage`` and set the + ``support_js_module_import_aggregation`` attribute to ``True``. * The new :attr:`.ManifestStaticFilesStorage.manifest_hash` attribute provides a hash over all files in the manifest and changes whenever one of the files diff --git a/tests/staticfiles_tests/test_storage.py b/tests/staticfiles_tests/test_storage.py index 759582122d..2202a5bc07 100644 --- a/tests/staticfiles_tests/test_storage.py +++ b/tests/staticfiles_tests/test_storage.py @@ -177,52 +177,6 @@ class TestHashedFiles: self.assertIn(b"https://", relfile.read()) self.assertPostCondition() - def test_module_import(self): - relpath = self.hashed_file_path("cached/module.js") - self.assertEqual(relpath, "cached/module.55fd6938fbc5.js") - tests = [ - # Relative imports. - b'import testConst from "./module_test.477bbebe77f0.js";', - b'import relativeModule from "../nested/js/nested.866475c46bb4.js";', - b'import { firstConst, secondConst } from "./module_test.477bbebe77f0.js";', - # Absolute import. - b'import rootConst from "/static/absolute_root.5586327fe78c.js";', - # Dynamic import. - b'const dynamicModule = import("./module_test.477bbebe77f0.js");', - # Creating a module object. - b'import * as NewModule from "./module_test.477bbebe77f0.js";', - # Aliases. - b'import { testConst as alias } from "./module_test.477bbebe77f0.js";', - b"import {\n" - b" firstVar1 as firstVarAlias,\n" - b" $second_var_2 as secondVarAlias\n" - b'} from "./module_test.477bbebe77f0.js";', - ] - with storage.staticfiles_storage.open(relpath) as relfile: - content = relfile.read() - for module_import in tests: - with self.subTest(module_import=module_import): - self.assertIn(module_import, content) - self.assertPostCondition() - - def test_aggregating_modules(self): - relpath = self.hashed_file_path("cached/module.js") - self.assertEqual(relpath, "cached/module.55fd6938fbc5.js") - tests = [ - b'export * from "./module_test.477bbebe77f0.js";', - b'export { testConst } from "./module_test.477bbebe77f0.js";', - b"export {\n" - b" firstVar as firstVarAlias,\n" - b" secondVar as secondVarAlias\n" - b'} from "./module_test.477bbebe77f0.js";', - ] - with storage.staticfiles_storage.open(relpath) as relfile: - content = relfile.read() - for module_import in tests: - with self.subTest(module_import=module_import): - self.assertIn(module_import, content) - self.assertPostCondition() - @override_settings( STATICFILES_DIRS=[os.path.join(TEST_ROOT, "project", "loop")], STATICFILES_FINDERS=["django.contrib.staticfiles.finders.FileSystemFinder"], @@ -646,6 +600,68 @@ class TestCollectionSimpleStorage(CollectionTestCase): self.assertIn(b"other.deploy12345.css", content) +class JSModuleImportAggregationManifestStorage(storage.ManifestStaticFilesStorage): + support_js_module_import_aggregation = True + + +@override_settings( + STORAGES={ + STATICFILES_STORAGE_ALIAS: { + "BACKEND": ( + "staticfiles_tests.test_storage." + "JSModuleImportAggregationManifestStorage" + ), + }, + } +) +class TestCollectionJSModuleImportAggregationManifestStorage(CollectionTestCase): + hashed_file_path = hashed_file_path + + def test_module_import(self): + relpath = self.hashed_file_path("cached/module.js") + self.assertEqual(relpath, "cached/module.55fd6938fbc5.js") + tests = [ + # Relative imports. + b'import testConst from "./module_test.477bbebe77f0.js";', + b'import relativeModule from "../nested/js/nested.866475c46bb4.js";', + b'import { firstConst, secondConst } from "./module_test.477bbebe77f0.js";', + # Absolute import. + b'import rootConst from "/static/absolute_root.5586327fe78c.js";', + # Dynamic import. + b'const dynamicModule = import("./module_test.477bbebe77f0.js");', + # Creating a module object. + b'import * as NewModule from "./module_test.477bbebe77f0.js";', + # Aliases. + b'import { testConst as alias } from "./module_test.477bbebe77f0.js";', + b"import {\n" + b" firstVar1 as firstVarAlias,\n" + b" $second_var_2 as secondVarAlias\n" + b'} from "./module_test.477bbebe77f0.js";', + ] + with storage.staticfiles_storage.open(relpath) as relfile: + content = relfile.read() + for module_import in tests: + with self.subTest(module_import=module_import): + self.assertIn(module_import, content) + + def test_aggregating_modules(self): + relpath = self.hashed_file_path("cached/module.js") + self.assertEqual(relpath, "cached/module.55fd6938fbc5.js") + tests = [ + b'export * from "./module_test.477bbebe77f0.js";', + b'export { testConst } from "./module_test.477bbebe77f0.js";', + b"export {\n" + b" firstVar as firstVarAlias,\n" + b" secondVar as secondVarAlias\n" + b'} from "./module_test.477bbebe77f0.js";', + ] + with storage.staticfiles_storage.open(relpath) as relfile: + content = relfile.read() + for module_import in tests: + with self.subTest(module_import=module_import): + self.assertIn(module_import, content) + + class CustomManifestStorage(storage.ManifestStaticFilesStorage): def __init__(self, *args, manifest_storage=None, **kwargs): manifest_storage = storage.StaticFilesStorage(