Opened 5 months ago

Closed 4 months ago

#35553 closed Bug (fixed)

HashedFilesMixin for ES modules does not work with `import*as ...` syntax

Reported by: Michael Owned by: Farhan Ali
Component: contrib.staticfiles Version: 5.0
Severity: Normal Keywords: Manifest Static Files Storage, javascript module scripts
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Django's regex does not work with the following:

import*as l from "/static/jsapp/jsapp/dtmod.min.js";import*as h from "/static/jsapp/jsapp/nummod.min.js";import*as m from "/static/leave/jsapp/fetcher.min.js";import {BaseComponent as g} from "/static/wcapp/jsapp/wc-base.min.425310100bce.js";

As you can see only the 4th import was correctly altered, the first 3 werent even detected, (below is the same as above but placed the imports on seprate lines for readability):

import*as l from "/static/jsapp/jsapp/dtmod.min.js";
import*as h from "/static/jsapp/jsapp/nummod.min.js";
import*as m from "/static/leave/jsapp/fetcher.min.js";
import {BaseComponent as g} from "/static/wcapp/jsapp/wc-base.min.425310100bce.js";

This regex handles the missing case:

            (
                r"""(?P<matched>(?P<import_as>import\s*\*as\s\S+)\s+from\s*["'](?P<url>[./].*?)["']\s*;)""",
                """%(import_as)s from "%(url)s";""",
            ),

Change History (13)

in reply to:  description comment:1 by Sarah Boyce, 5 months ago

Resolution: invalid
Status: newclosed

Replying to Michael:

import*as l from "/static/jsapp/jsapp/dtmod.min.js";
import*as h from "/static/jsapp/jsapp/nummod.min.js";
import*as m from "/static/leave/jsapp/fetcher.min.js";
import {BaseComponent as g} from "/static/wcapp/jsapp/wc-base.min.425310100bce.js";

This doesn't look valid to me, shouldn't it be import * as not import*as?

comment:2 by Michael, 5 months ago

Hi, no it is not invalid, that is the result of minification, removing any extra whitespace, most production system will serve minified files, so it's much more likely to not have the space.

in reply to:  2 comment:3 by Sarah Boyce, 5 months ago

Resolution: invalid
Status: closednew
Triage Stage: UnreviewedAccepted
Type: UncategorizedBug

Replying to Michael:

Hi, no it is not invalid, that is the result of minification, removing any extra whitespace, most production system will serve minified files, so it's much more likely to not have the space.

Ah, TIL 😁 confirmed that this is the output of many minifiers

Confirmed that your suggested patch works for me, here is also a test:

  • tests/staticfiles_tests/project/documents/cached/module.js

    diff --git a/tests/staticfiles_tests/project/documents/cached/module.js b/tests/staticfiles_tests/project/documents/cached/module.js
    index 7764e740d6..30ca25e9b6 100644
    a b  
    22import rootConst from "/static/absolute_root.js";
    33import testConst from "./module_test.js";
    44import * as NewModule from "./module_test.js";
     5import*as m from"./module_test.js";
    56import { testConst as alias } from "./module_test.js";
    67import { firstConst, secondConst } from "./module_test.js";
    78import {
  • tests/staticfiles_tests/test_storage.py

    diff --git a/tests/staticfiles_tests/test_storage.py b/tests/staticfiles_tests/test_storage.py
    index 469d5ec690..956341a858 100644
    a b class TestCollectionJSModuleImportAggregationManifestStorage(CollectionTestCase)  
    674674
    675675    def test_module_import(self):
    676676        relpath = self.hashed_file_path("cached/module.js")
    677         self.assertEqual(relpath, "cached/module.55fd6938fbc5.js")
     677        self.assertEqual(relpath, "cached/module.d16a17156de1.js")
    678678        tests = [
    679679            # Relative imports.
    680680            b'import testConst from "./module_test.477bbebe77f0.js";',
    class TestCollectionJSModuleImportAggregationManifestStorage(CollectionTestCase)  
    686686            b'const dynamicModule = import("./module_test.477bbebe77f0.js");',
    687687            # Creating a module object.
    688688            b'import * as NewModule from "./module_test.477bbebe77f0.js";',
     689            # Creating a minified module object.
     690            b'import*as m from "./module_test.477bbebe77f0.js";',
    689691            # Aliases.
    690692            b'import { testConst as alias } from "./module_test.477bbebe77f0.js";',
    691693            b"import {\n"

Would you like to raise a PR?

comment:4 by Sarah Boyce, 5 months ago

Summary: HashedFilesMixin for ES modules does not work with `import * as ...` syntaxHashedFilesMixin for ES modules does not work with `import*as ...` syntax

comment:5 by Michael, 5 months ago

Okay great, thanks for handling it. I dont know what "TIL" means, but cheers!

in reply to:  5 comment:6 by Sarah Boyce, 5 months ago

Replying to Michael:

Okay great, thanks for handling it. I dont know what "TIL" means, but cheers!

Sorry it's short for "Today I learned"

comment:7 by Farhan Ali, 5 months ago

Owner: changed from nobody to Farhan Ali
Status: newassigned

comment:8 by Farhan Ali, 5 months ago

output of this produces import *as m from "./module_test.477bbebe77f0.477bbebe77f0.js"; for me.
I don't know why.

Version 0, edited 5 months ago by Farhan Ali (next)

comment:9 by Farhan Ali, 5 months ago

  • django/contrib/staticfiles/storage.py

    diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py
    index 85172ea42d..394975c9de 100644
    a b class HashedFilesMixin:  
    7373                r"""(?P<matched>import\(["'](?P<url>.*?)["']\))""",
    7474                """import("%(url)s")""",
    7575            ),
     76            (
     77                r"""(?P<matched>(?P<import_as>import\s*\*as\s\S+)\s+from\s*["'](?P<url>[./].*?)["']\s*;)""",
     78                """%(import_as)s from "%(url)s";""",
     79            ),
    7680        ),
    7781    )
    7882    patterns = (
    class HashedFilesMixin:  
    287291
    288292        # where to store the new paths
    289293        hashed_files = {}
    290 
    291294        # build a list of adjustable files
    292295        adjustable_paths = [
    293296            path for path in paths if matches_patterns(path, self._patterns)
    294297        ]
    295 
    296298        # Adjustable files to yield at end, keyed by the original path.
    297299        processed_adjustable_paths = {}
    298300
  • tests/staticfiles_tests/project/documents/cached/module.js

    diff --git a/tests/staticfiles_tests/project/documents/cached/module.js b/tests/staticfiles_tests/project/documents/cached/module.js
    index 7764e740d6..602561798f 100644
    a b  
    22import rootConst from "/static/absolute_root.js";
    33import testConst from "./module_test.js";
    44import * as NewModule from "./module_test.js";
     5import *as m from "./module_test.js";
    56import { testConst as alias } from "./module_test.js";
    67import { firstConst, secondConst } from "./module_test.js";
    78import {
  • tests/staticfiles_tests/test_storage.py

    diff --git a/tests/staticfiles_tests/test_storage.py b/tests/staticfiles_tests/test_storage.py
    index dc8607a307..6290d9d51a 100644
    a b class TestCollectionJSModuleImportAggregationManifestStorage(CollectionTestCase)  
    643643
    644644    def test_module_import(self):
    645645        relpath = self.hashed_file_path("cached/module.js")
    646         self.assertEqual(relpath, "cached/module.55fd6938fbc5.js")
     646        self.assertEqual(relpath, "cached/module.0415cd43ac63.js")
    647647        tests = [
    648648            # Relative imports.
    649649            b'import testConst from "./module_test.477bbebe77f0.js";',
    class TestCollectionJSModuleImportAggregationManifestStorage(CollectionTestCase)  
    655655            b'const dynamicModule = import("./module_test.477bbebe77f0.js");',
    656656            # Creating a module object.
    657657            b'import * as NewModule from "./module_test.477bbebe77f0.js";',
     658            # Creating a minified module object.
     659            b'import*as m from "./module_test.477bbebe77f0.js";',
    658660            # Aliases.
    659661            b'import { testConst as alias } from "./module_test.477bbebe77f0.js";',
    660662            b"import {\n"

comment:10 by Farhan Ali, 4 months ago

Has patch: set

comment:11 by Sarah Boyce, 4 months ago

Patch needs improvement: set

comment:12 by Sarah Boyce, 4 months ago

Patch needs improvement: unset
Triage Stage: AcceptedReady for checkin

comment:13 by Sarah Boyce <42296566+sarahboyce@…>, 4 months ago

Resolution: fixed
Status: assignedclosed

In 6993c9d8:

Fixed #35553 -- Handled import*as in HashedFilesMixin.

Note: See TracTickets for help on using tickets.
Back to Top