Skip to content

Commit 732eacf

Browse files
authored
Use esimport to generate importmaps (#47)
1 parent ac4977f commit 732eacf

26 files changed

+353
-391
lines changed

.editorconfig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ charset = utf-8
1111
end_of_line = lf
1212
max_line_length = 88
1313

14-
[*.{json,yml,yaml,js,jsx,vue,toml}]
14+
[*.{json,yml,yaml,mjs,js,jsx,vue,toml}]
1515
indent_size = 2
16+
ij_javascript_force_semicolon_style = false
1617

1718
[*.{html,htm,svg,xml}]
1819
indent_size = 2

README.md

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,37 +26,29 @@ pip install django-esm
2626
# settings.py
2727
INSTALLED_APPS = [
2828
#
29-
'django_esm',
29+
"django_esm", # add django_esm before staticfiles
30+
"django.contrib.staticfiles",
3031
]
3132
```
3233

33-
Next, add a new staticfiles finder to your `STATICFILES_FINDERS` setting:
34-
35-
```python
36-
# settings.py
37-
STATICFILES_FINDERS = [
38-
# Django's default finders
39-
"django.contrib.staticfiles.finders.FileSystemFinder",
40-
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
41-
# django-esm finder
42-
"django_esm.finders.ESMFinder",
43-
]
44-
```
45-
46-
You will also need to expose your `node_modules` directory to Django's
47-
staticfiles finder. You may run `npm ci --omit=dev` prior to running
48-
`collectstatic` to avoid exposing your `devDependencies` publicly.
34+
Next, lets configure Django-ESM:
4935

5036
```python
5137
# settings.py
5238
from pathlib import Path
5339

54-
# add BASE_DIR (if not already present)
40+
# add BASE_DIR setting (if not already present)
5541
BASE_DIR = Path(__file__).resolve().parent.parent
5642

43+
ESM = {
44+
"PACKAGE_DIR": BASE_DIR, # path to a directory containing a package.json file
45+
"STATIC_DIR": BASE_DIR / "esm", # target directory to collect ES modules into
46+
"STATIC_PREFIX": "esm", # prefix for the ES module URLs
47+
}
48+
5749
STATICFILES_DIRS = [
5850
#
59-
BASE_DIR / "node_modules",
51+
(ESM["STATIC_PREFIX"], ESM["STATIC_DIR"]),
6052
]
6153
```
6254

@@ -69,12 +61,13 @@ Finally, add the import map to your base template:
6961
<html lang="en">
7062
<head>
7163
<script type="importmap">{% importmap %}</script>
64+
<title>Django ESM is awesome!</title>
7265
</head>
7366
</html>
7467
```
7568

7669
That's it!
77-
Don't forget to run `npm install` and `python manage.py collectstatic`.
70+
Remember to run `npm install` and `python manage.py esm --watch`.
7871

7972
## Usage
8073

@@ -84,8 +77,7 @@ You can now import JavaScript modules in your Django templates:
8477
<!-- index.html -->
8578
{% block content %}
8679
<script type="module">
87-
import "htmx.org"
88-
htmx.logAll()
80+
import "lit"
8981
</script>
9082
{% endblock %}
9183
```

django_esm/apps.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.apps import AppConfig
2+
3+
4+
class ESMConfig(AppConfig):
5+
name = "django_esm"
6+
verbose_name = "ESM"
7+
8+
def ready(self):
9+
from . import checks # noqa

django_esm/checks.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import pathlib
2+
3+
from django.conf import settings
4+
from django.core.checks import Error, Tags, Warning, register
5+
6+
from . import conf
7+
8+
__all__ = ["check_esm_settings"]
9+
10+
11+
@register(Tags.staticfiles)
12+
def check_esm_settings(app_configs, **kwargs):
13+
errors = []
14+
if not conf.get_settings().PACKAGE_DIR:
15+
errors.append(
16+
Error(
17+
'Setting ESM["PACKAGE_DIR"] is not configured.',
18+
hint=(
19+
'ESM["PACKAGE_DIR"] must be an absolute path or pathlib.Path object.'
20+
),
21+
id="esm.E001",
22+
)
23+
)
24+
25+
if not pathlib.Path(conf.get_settings().PACKAGE_DIR).is_absolute():
26+
errors.append(
27+
Error(
28+
'ESM["PACKAGE_DIR"] is a relative path.',
29+
hint=(
30+
'ESM["PACKAGE_DIR"] must be an absolute path or pathlib.Path object.'
31+
),
32+
id="esm.E002",
33+
)
34+
)
35+
36+
if not conf.get_settings().STATIC_DIR:
37+
errors.append(
38+
Error(
39+
'Setting ESM["STATIC_DIR"] is not configured.',
40+
hint=(
41+
'ESM["STATIC_DIR"] must be an absolute path or pathlib.Path object.'
42+
),
43+
id="esm.E003",
44+
)
45+
)
46+
47+
if not pathlib.Path(conf.get_settings().STATIC_DIR).is_absolute():
48+
errors.append(
49+
Error(
50+
'ESM["STATIC_DIR"] is a relative path.',
51+
hint=(
52+
'ESM["STATIC_DIR"] must be an absolute path or pathlib.Path object.'
53+
),
54+
id="esm.E004",
55+
)
56+
)
57+
58+
if not conf.get_settings().STATIC_PREFIX:
59+
errors.append(
60+
Error(
61+
'Setting ESM["STATIC_PREFIX"] is not configured.',
62+
hint=(
63+
'ESM["STATIC_PREFIX"] must be an absolute path or pathlib.Path object.'
64+
),
65+
id="esm.E005",
66+
)
67+
)
68+
69+
if (
70+
conf.get_settings().STATIC_PREFIX,
71+
conf.get_settings().STATIC_DIR,
72+
) not in settings.STATICFILES_DIRS:
73+
errors.append(
74+
Error(
75+
'\'(ESM["STATIC_PREFIX"], ESM["STATIC_DIR"]),\' must be in STATICFILES_DIRS.',
76+
id="esm.E006",
77+
)
78+
)
79+
80+
if not (pathlib.Path(conf.get_settings().PACKAGE_DIR) / "package.json").exists():
81+
errors.append(
82+
Error(
83+
f"package.json file not found in: {conf.get_settings().PACKAGE_DIR}",
84+
hint='Make sure check your ESM["PACKAGE_DIR"] setting.',
85+
id="esm.E007",
86+
)
87+
)
88+
89+
if not (pathlib.Path(conf.get_settings().STATIC_DIR) / "importmap.json").exists():
90+
errors.append(
91+
Warning(
92+
f"importmap.json file not found in: {conf.get_settings().STATIC_DIR}",
93+
hint=(
94+
'Make sure check your ESM["STATIC_DIR"] setting and to run the "esm" management command to generate the importmap.json file.'
95+
),
96+
id="esm.W001",
97+
)
98+
)
99+
100+
return errors
101+
102+
103+
@register(Tags.staticfiles, deploy=True)
104+
def check_deployment(app_configs, **kwargs):
105+
errors = []
106+
107+
if not (pathlib.Path(conf.get_settings().STATIC_DIR) / "importmap.json").exists():
108+
errors.append(
109+
Error(
110+
f"importmap.json file not found in: {conf.get_settings().STATIC_DIR}",
111+
hint=(
112+
'Make sure check your ESM["STATIC_DIR"] setting and to run the "esm" management command to generate the importmap.json file.'
113+
),
114+
id="esm.E008",
115+
)
116+
)
117+
118+
return errors

django_esm/conf.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from django.conf import settings
2+
3+
__all__ = ["get_settings"]
4+
5+
6+
def get_settings():
7+
return type(
8+
"Settings",
9+
(),
10+
{
11+
"PACKAGE_DIR": "",
12+
"STATIC_DIR": "",
13+
"STATIC_PREFIX": "",
14+
**getattr(settings, "ESM", {}),
15+
},
16+
)

django_esm/finders.py

Lines changed: 0 additions & 75 deletions
This file was deleted.

django_esm/management/__init__.py

Whitespace-only changes.

django_esm/management/commands/__init__.py

Whitespace-only changes.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import subprocess # nosec
2+
import sys
3+
4+
from django.contrib.staticfiles.management.commands import collectstatic
5+
6+
from django_esm.conf import get_settings
7+
8+
9+
class Command(collectstatic.Command):
10+
def add_arguments(self, parser):
11+
super().add_arguments(parser)
12+
parser.add_argument(
13+
"--no-esm",
14+
"--noesm",
15+
action="store_true",
16+
help="Do not collect ES modules before collecting static files.",
17+
)
18+
19+
def handle(self, **options):
20+
if not options["no_esm"]:
21+
subprocess.check_call( # nosec
22+
[
23+
"npx",
24+
"--yes",
25+
"esimport",
26+
get_settings().PACKAGE_DIR,
27+
get_settings().STATIC_DIR,
28+
],
29+
stdout=sys.stdout,
30+
stderr=sys.stderr,
31+
)
32+
super().handle(**options)

django_esm/management/commands/esm.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import subprocess # nosec
2+
import sys
3+
4+
from django.core.management import BaseCommand
5+
6+
from django_esm.conf import get_settings
7+
8+
9+
class Command(BaseCommand):
10+
"""Collect ES modules from the package directory and generate importmap."""
11+
12+
help = __doc__
13+
14+
def add_arguments(self, parser):
15+
parser.add_argument(
16+
"-w",
17+
"--watch",
18+
action="store_true",
19+
help="Watch for changes in the package directory and re-run collect files.",
20+
)
21+
22+
def handle(self, *args, **options):
23+
subprocess.check_call( # nosec
24+
(
25+
[
26+
"npx",
27+
"--yes",
28+
"esimport",
29+
get_settings().PACKAGE_DIR,
30+
get_settings().STATIC_DIR,
31+
]
32+
+ (["--watch"] if options["watch"] else [])
33+
),
34+
stdout=sys.stdout,
35+
stderr=sys.stderr,
36+
)

0 commit comments

Comments
 (0)