-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
191 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,95 @@ | ||
import json | ||
from pathlib import Path | ||
|
||
from django.conf import settings | ||
from django.contrib.staticfiles.finders import get_finders | ||
from django.contrib.staticfiles.storage import staticfiles_storage | ||
|
||
|
||
def parse_root_package(package_json): | ||
"""Parse a project main package.json and return a dict of importmap entries.""" | ||
imports = package_json.get("imports", {}) | ||
if isinstance(imports, (str, list)): | ||
raise ValueError(f"package.imports must be an object, {type(imports)} given") | ||
|
||
for module_name, module in imports.items(): | ||
if not module_name.startswith("#"): | ||
raise ValueError( | ||
f"package.imports keys must start with #, {module_name} given" | ||
) | ||
try: | ||
mod = module["default"] | ||
except TypeError: | ||
mod = module | ||
url = mod | ||
if mod[0] in [".", "/"]: | ||
# local file | ||
url = get_static_from_abs_path(settings.BASE_DIR / mod) | ||
if mod.endswith("/*"): | ||
url = url[:-2] + "/" | ||
module_name = module_name[:-1] | ||
yield module_name, url | ||
|
||
for dep_name, dep_version in package_json.get("dependencies", {}).items(): | ||
yield from parse_package_json(settings.BASE_DIR / "node_modules" / dep_name) | ||
|
||
|
||
def get_static_from_abs_path(path: Path): | ||
for finder in get_finders(): | ||
for storage in finder.storages.values(): | ||
try: | ||
rel_path = path.relative_to(Path(storage.location)) | ||
except ValueError: | ||
pass | ||
else: | ||
return staticfiles_storage.url(str(rel_path)) | ||
raise ValueError(f"Could not find {path} in staticfiles") | ||
|
||
|
||
# There is a long history how ESM is supported in Node.js | ||
# So we implement some fallbacks, see also: https://nodejs.org/api/packages.html#exports | ||
ESM_KEYS = ["exports", "module", "main"] | ||
|
||
|
||
def parse_package_json(path: Path = None, node_modules: Path = None): | ||
def cast_exports(package_json): | ||
exports = {} | ||
for key in ESM_KEYS: | ||
try: | ||
exports = package_json[key] | ||
except KeyError: | ||
continue | ||
else: | ||
break | ||
if not exports: | ||
exports = {} | ||
elif isinstance(exports, str): | ||
exports = {".": exports} | ||
elif isinstance(exports, list): | ||
exports = {i: i for i in exports} | ||
return exports | ||
|
||
|
||
def parse_package_json(path: Path = None): | ||
"""Parse a project main package.json and return a dict of importmap entries.""" | ||
if node_modules is None: | ||
node_modules = path / "node_modules" | ||
with (path / "package.json").open() as f: | ||
package_json = json.load(f) | ||
name = package_json["name"] | ||
dependencies = package_json.get("dependencies", {}) | ||
if path.is_relative_to(node_modules): | ||
base_path = node_modules | ||
exports = cast_exports(package_json) | ||
|
||
for module_name, module in exports.items(): | ||
try: | ||
mod = module["default"] | ||
except TypeError: | ||
mod = module | ||
|
||
yield str(Path(name) / module_name), staticfiles_storage.url( | ||
str((path / mod).relative_to(settings.BASE_DIR / "node_modules")) | ||
) | ||
|
||
if (path / "node_modules").exists(): | ||
node_modules = path / "node_modules" | ||
else: | ||
base_path = path | ||
for key in ESM_KEYS: | ||
export = package_json.get(key, None) | ||
if export: | ||
try: | ||
for module_name, module in export.items(): | ||
try: | ||
yield str(Path(name) / module_name), staticfiles_storage.url( | ||
str((path / module["default"]).relative_to(base_path)) | ||
) | ||
except TypeError: | ||
yield str(Path(name) / module_name), staticfiles_storage.url( | ||
str((path / module).relative_to(base_path)) | ||
) | ||
except AttributeError: | ||
yield name, staticfiles_storage.url( | ||
str((path / export).relative_to(base_path)) | ||
) | ||
node_modules = path / "/".join(".." for _ in Path(name).parts) | ||
for dep_name, dep_version in dependencies.items(): | ||
yield from parse_package_json(node_modules / dep_name, node_modules) | ||
yield from parse_package_json(node_modules / dep_name) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,52 @@ | ||
from pathlib import Path | ||
|
||
import pytest | ||
|
||
from django_esm import utils | ||
|
||
FIXTURE_DIR = Path(__file__).parent | ||
|
||
|
||
def test_parse_package_json(package_json): | ||
import_map = dict(utils.parse_package_json(package_json.parent)) | ||
def test_parse_root_package(package_json): | ||
import_map = dict(utils.parse_root_package(package_json)) | ||
assert import_map["htmx.org"] == "/static/htmx.org/dist/htmx.min.js" | ||
assert import_map["lit"] == "/static/lit/index.js" | ||
assert ( | ||
import_map["@lit/reactive-element"] | ||
== "/static/%40lit/reactive-element/reactive-element.js" | ||
) | ||
assert import_map["lit-html"] == "/static/lit-html/lit-html.js" | ||
assert import_map["#index"] == "/static/js/index.js" | ||
assert import_map["#components/"] == "/static/js/components/" | ||
assert import_map["#htmx"] == "https://unpkg.com/htmx.org@1.9.10" | ||
|
||
|
||
def test_parse_root_package__bad_imports(package_json): | ||
package_json["imports"] = "foo" | ||
with pytest.raises(ValueError) as e: | ||
dict(utils.parse_root_package(package_json)) | ||
assert "must be an object" in str(e.value) | ||
|
||
package_json["imports"] = ["foo"] | ||
with pytest.raises(ValueError) as e: | ||
dict(utils.parse_root_package(package_json)) | ||
assert "must be an object" in str(e.value) | ||
|
||
|
||
def test_parse_root_package__bad_keys(package_json): | ||
package_json["imports"] = {"foo": "/bar"} | ||
with pytest.raises(ValueError) as e: | ||
dict(utils.parse_root_package(package_json)) | ||
assert "must start with #" in str(e.value) | ||
|
||
|
||
def test_cast_exports(): | ||
assert utils.cast_exports({"exports": {"foo": "bar"}}) == {"foo": "bar"} | ||
assert utils.cast_exports({"exports": "foo"}) == {".": "foo"} | ||
assert utils.cast_exports({"exports": ["foo"]}) == {"foo": "foo"} | ||
|
||
|
||
def test_get_static_from_abs_path(): | ||
with pytest.raises(ValueError) as e: | ||
utils.get_static_from_abs_path(Path("/foo/bar")) | ||
assert "Could not find" in str(e.value) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters