6
6
from __future__ import annotations
7
7
8
8
from pathlib import Path
9
- from typing import Any , Dict , List , Optional , Tuple , Union , overload
9
+ from typing import Any , Dict , List , Optional , Set , Tuple , Union , overload
10
10
from urllib .parse import urlparse
11
11
12
+ from idom .config import IDOM_CLIENT_MODULES_MUST_HAVE_MOUNT
12
13
from idom .core .vdom import ImportSourceDict , VdomDict , make_vdom_constructor
13
14
14
15
from . import _private , manage
@@ -36,6 +37,8 @@ def install(
36
37
packages : Union [str , List [str ], Tuple [str ]],
37
38
ignore_installed : bool = False ,
38
39
fallback : Optional [str ] = None ,
40
+ # dynamically installed modules probably won't have a mount so we default to False
41
+ has_mount : bool = False ,
39
42
) -> Union [Module , List [Module ]]:
40
43
return_one = False
41
44
if isinstance (packages , str ):
@@ -48,9 +51,11 @@ def install(
48
51
manage .build (packages , clean_build = False )
49
52
50
53
if return_one :
51
- return Module (pkg_names [0 ], fallback = fallback )
54
+ return Module (pkg_names [0 ], fallback = fallback , has_mount = has_mount )
52
55
else :
53
- return [Module (pkg , fallback = fallback ) for pkg in pkg_names ]
56
+ return [
57
+ Module (pkg , fallback = fallback , has_mount = has_mount ) for pkg in pkg_names
58
+ ]
54
59
55
60
56
61
class Module :
@@ -67,6 +72,12 @@ class Module:
67
72
built-in client will inject this module adjacent to other installed modules
68
73
which means they can be imported via a relative path like
69
74
``./some-other-installed-module.js``.
75
+ fallack:
76
+ What to display while the modules is being loaded.
77
+ has_mount:
78
+ Whether the module exports a ``mount`` function that allows components to
79
+ be mounted directly to the DOM. Such a mount function enables greater
80
+ flexibility in how custom components can be implemented.
70
81
71
82
Attributes:
72
83
installed:
@@ -75,41 +86,52 @@ class Module:
75
86
The URL this module will be imported from.
76
87
"""
77
88
78
- __slots__ = "url" , "fallback" , "exports" , "_export_names"
89
+ __slots__ = (
90
+ "url" ,
91
+ "fallback" ,
92
+ "exports" ,
93
+ "has_mount" ,
94
+ "check_exports" ,
95
+ "_export_names" ,
96
+ )
79
97
80
98
def __init__ (
81
99
self ,
82
100
url_or_name : str ,
83
101
source_file : Optional [Union [str , Path ]] = None ,
84
102
fallback : Optional [str ] = None ,
103
+ has_mount : bool = False ,
85
104
check_exports : bool = True ,
86
105
) -> None :
87
106
self .fallback = fallback
88
- self ._export_names : Optional [List [str ]] = None
107
+ self .has_mount = has_mount
108
+ self .check_exports = check_exports
109
+
110
+ self .exports : Set [str ] = set ()
89
111
if source_file is not None :
90
112
self .url = (
91
113
manage .web_module_url (url_or_name )
92
114
if manage .web_module_exists (url_or_name )
93
115
else manage .add_web_module (url_or_name , source_file )
94
116
)
95
117
if check_exports :
96
- self ._export_names = manage .web_module_exports (url_or_name )
118
+ self .exports = manage .web_module_exports (url_or_name )
97
119
elif _is_url (url_or_name ):
98
120
self .url = url_or_name
121
+ self .check_exports = False
99
122
elif manage .web_module_exists (url_or_name ):
100
123
self .url = manage .web_module_url (url_or_name )
101
124
if check_exports :
102
- self ._export_names = manage .web_module_exports (url_or_name )
125
+ self .exports = manage .web_module_exports (url_or_name )
103
126
else :
104
127
raise ValueError (f"{ url_or_name !r} is not installed or is not a URL" )
105
- self .exports = {name : self .declare (name ) for name in (self ._export_names or [])}
106
128
107
129
def declare (
108
130
self ,
109
131
name : str ,
110
132
has_children : bool = True ,
111
133
fallback : Optional [str ] = None ,
112
- ) -> " Import" :
134
+ ) -> Import :
113
135
"""Return an :class:`Import` for the given :class:`Module` and ``name``
114
136
115
137
This roughly translates to the javascript statement
@@ -121,19 +143,20 @@ def declare(
121
143
Where ``name`` is the given name, and ``module`` is the :attr:`Module.url` of
122
144
this :class:`Module` instance.
123
145
"""
124
- if (
125
- self ._export_names is not None
126
- # if 'default' is exported there's not much we can infer
127
- and "default" not in self ._export_names
128
- ):
129
- if name not in self ._export_names :
130
- raise ValueError (
131
- f"{ self } does not export { name !r} , available options are { self ._export_names } "
132
- )
133
- return Import (self .url , name , has_children , fallback = fallback or self .fallback )
134
-
135
- def __getattr__ (self , name : str ) -> "Import" :
136
- return self .exports .get (name ) or self .declare (name )
146
+ if self .check_exports and name not in self .exports :
147
+ raise ValueError (
148
+ f"{ self } does not export { name !r} , available options are { list (self .exports )} "
149
+ )
150
+ return Import (
151
+ self .url ,
152
+ name ,
153
+ has_children ,
154
+ has_mount = self .has_mount ,
155
+ fallback = fallback or self .fallback ,
156
+ )
157
+
158
+ def __getattr__ (self , name : str ) -> Import :
159
+ return self .declare (name )
137
160
138
161
def __repr__ (self ) -> str :
139
162
return f"{ type (self ).__name__ } ({ self .url } )"
@@ -161,11 +184,19 @@ def __init__(
161
184
module : str ,
162
185
name : str ,
163
186
has_children : bool = True ,
187
+ has_mount : bool = False ,
164
188
fallback : Optional [str ] = None ,
165
189
) -> None :
190
+ if IDOM_CLIENT_MODULES_MUST_HAVE_MOUNT .current and not has_mount :
191
+ raise RuntimeError (
192
+ f"{ IDOM_CLIENT_MODULES_MUST_HAVE_MOUNT } is set and { module } has no mount"
193
+ )
194
+
166
195
self ._name = name
167
196
self ._constructor = make_vdom_constructor (name , has_children )
168
- self ._import_source = ImportSourceDict (source = module , fallback = fallback )
197
+ self ._import_source = ImportSourceDict (
198
+ source = module , fallback = fallback , hasMount = has_mount
199
+ )
169
200
170
201
def __call__ (
171
202
self ,
0 commit comments