Skip to content

Fix 502 #503

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 30, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add exports_default parameter
  • Loading branch information
rmorshea committed Sep 30, 2021
commit ec218f636a560ab0019cb353976397453092dc4c
3 changes: 2 additions & 1 deletion docs/source/examples/cytoscape.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

react_cytoscapejs = idom.web.module_from_template(
# we need to use this template because react-cytoscapejs uses a default export
"react-default",
"react",
"react-cytoscapejs",
exports_default=True,
fallback="⌛",
)
Cytoscape = idom.web.export(react_cytoscapejs, "default")
Expand Down
8 changes: 0 additions & 8 deletions src/client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions src/idom/web/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def module_from_template(
package: str,
cdn: str = "https://esm.sh",
fallback: Optional[Any] = None,
exports_default: bool = False,
resolve_exports: bool = IDOM_DEBUG_MODE.current,
resolve_exports_depth: int = 5,
unmount_before_update: bool = False,
Expand Down Expand Up @@ -114,6 +115,8 @@ def module_from_template(
Where the package should be loaded from. The CDN must distribute ESM modules
fallback:
What to temporarilly display while the module is being loaded.
exports_default:
Whether the module has a default export.
resolve_imports:
Whether to try and find all the named exports of this module.
resolve_exports_depth:
Expand All @@ -132,7 +135,12 @@ def module_from_template(
# downstream code assumes no trailing slash
cdn = cdn.rstrip("/")

template_file_name = f"{template}{module_name_suffix(package_name)}"
template_file_name = (
template
+ (".default" if exports_default else "")
+ module_name_suffix(package_name)
)

template_file = Path(__file__).parent / "templates" / template_file_name
if not template_file.exists():
raise ValueError(f"No template for {template_file_name!r} exists")
Expand All @@ -150,7 +158,7 @@ def module_from_template(
default_fallback=fallback,
file=target_file,
export_names=(
resolve_module_exports_from_url(f"{cdn}/{package}", resolve_exports_depth)
resolve_module_exports_from_file(target_file, resolve_exports_depth)
if resolve_exports
else None
),
Expand Down
49 changes: 39 additions & 10 deletions src/idom/web/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,41 @@ def module_name_suffix(name: str) -> str:
return PurePosixPath(tail or head).suffix or ".js"


def resolve_module_exports_from_file(file: Path, max_depth: int) -> Set[str]:
def resolve_module_exports_from_file(
file: Path,
max_depth: int,
is_re_export: bool = False,
) -> Set[str]:
if max_depth == 0:
logger.warning(f"Did not resolve all exports for {file} - max depth reached")
return set()
elif not file.exists():
logger.warning(f"Did not resolve exports for unknown file {file}")
return set()

export_names, references = resolve_module_exports_from_source(file.read_text())
export_names, references = resolve_module_exports_from_source(
file.read_text(), exclude_default=is_re_export
)

for ref in references:
if urlparse(ref).scheme: # is an absolute URL
export_names.update(resolve_module_exports_from_url(ref, max_depth - 1))
export_names.update(
resolve_module_exports_from_url(ref, max_depth - 1, is_re_export=True)
)
else:
path = file.parent.joinpath(*ref.split("/"))
export_names.update(resolve_module_exports_from_file(path, max_depth - 1))
export_names.update(
resolve_module_exports_from_file(path, max_depth - 1, is_re_export=True)
)

return export_names


def resolve_module_exports_from_url(url: str, max_depth: int) -> Set[str]:
def resolve_module_exports_from_url(
url: str,
max_depth: int,
is_re_export: bool = False,
) -> Set[str]:
if max_depth == 0:
logger.warning(f"Did not resolve all exports for {url} - max depth reached")
return set()
Expand All @@ -50,16 +64,22 @@ def resolve_module_exports_from_url(url: str, max_depth: int) -> Set[str]:
logger.warning("Did not resolve exports for url " + url + reason)
return set()

export_names, references = resolve_module_exports_from_source(text)
export_names, references = resolve_module_exports_from_source(
text, exclude_default=is_re_export
)

for ref in references:
url = _resolve_relative_url(url, ref)
export_names.update(resolve_module_exports_from_url(url, max_depth - 1))
export_names.update(
resolve_module_exports_from_url(url, max_depth - 1, is_re_export=True)
)

return export_names


def resolve_module_exports_from_source(content: str) -> Tuple[Set[str], Set[str]]:
def resolve_module_exports_from_source(
content: str, exclude_default: bool
) -> Tuple[Set[str], Set[str]]:
names: Set[str] = set()
references: Set[str] = set()

Expand All @@ -69,7 +89,9 @@ def resolve_module_exports_from_source(content: str) -> Tuple[Set[str], Set[str]
# Exporting functions and classes
names.update(_JS_FUNC_OR_CLS_EXPORT_PATTERN.findall(content))

print(content)
for export in _JS_GENERAL_EXPORT_PATTERN.findall(content):
print(export)
export = export.rstrip(";").strip()
# Exporting individual features
if export.startswith("let "):
Expand Down Expand Up @@ -100,7 +122,14 @@ def resolve_module_exports_from_source(content: str) -> Tuple[Set[str], Set[str]
)
elif not (export.startswith("function ") or export.startswith("class ")):
logger.warning(f"Unknown export type {export!r}")
return {n.strip() for n in names}, {r.strip() for r in references}

names = {n.strip() for n in names}
references = {r.strip() for r in references}

if exclude_default and "default" in names:
names.remove("default")

return names, references


def _resolve_relative_url(base_url: str, rel_url: str) -> str:
Expand All @@ -126,5 +155,5 @@ def _resolve_relative_url(base_url: str, rel_url: str) -> str:
r";?\s*export\s+(?:function|class)\s+([a-zA-Z_$][0-9a-zA-Z_$]*)"
)
_JS_GENERAL_EXPORT_PATTERN = re.compile(
r";?\s*export(?=\s+|{)(.*?)(?:;|$)", re.MULTILINE
r"(?:^|;)\s*export(?=\s+|{)(.*?)(?=;|$)", re.MULTILINE
)
2 changes: 2 additions & 0 deletions tests/test_web/js_fixtures/export-resolution/one.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export {one as One};
// use ../ just to check that it works
export * from "../export-resolution/two.js";
// this default should not be exported by the * re-export in index.js
export default 0;
14 changes: 12 additions & 2 deletions tests/test_web/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,15 @@ def test_resolve_module_exports_from_url_log_on_bad_response(caplog):
],
)
def test_resolve_module_default_exports_from_source(text):
names, references = resolve_module_exports_from_source(text)
names, references = resolve_module_exports_from_source(text, exclude_default=False)
assert names == {"default"} and not references


def test_resolve_module_exports_from_source():
fixture_file = JS_FIXTURES_DIR / "exports-syntax.js"
names, references = resolve_module_exports_from_source(fixture_file.read_text())
names, references = resolve_module_exports_from_source(
fixture_file.read_text(), exclude_default=False
)
assert (
names
== (
Expand All @@ -145,3 +147,11 @@ def test_resolve_module_exports_from_source():
)
and references == {"https://source1.com", "https://source2.com"}
)


def test_log_on_unknown_export_type(caplog):
assert resolve_module_exports_from_source(
"export something unknown;", exclude_default=False
) == (set(), set())
assert len(caplog.records) == 1
assert caplog.records[0].message.startswith("Unknown export type ")