11from pathlib import Path
22from typing import Any , Optional , Union
3+ from urllib .parse import urlparse
34
45from idom import client
56from idom .core .vdom import VdomDict , ImportSourceDict , make_vdom_constructor
@@ -9,49 +10,72 @@ class Module:
910 """A Javascript module
1011
1112 Parameters:
12- name:
13- If the module is installed, or ``source`` is not None, then this is the name
14- the the module to import from (omit the ``.js`` file extension). Otherwise
15- this is the URl (relative or absolute) to import from.
16- source:
17- Create a module of the given name using the given source code.
18- replace:
19- Overwrite a module defined from ``source`` if one of the same ``name``
20- already exists, otherwise raise a ``ValueError`` complaining of name
21- conflict.
22-
23- Returns:
24- An :class:`Import` element for the newly defined module.
13+ path:
14+ The URL to an ECMAScript module which exports React components
15+ (*with* a ``.js`` file extension) or name of a module installed in the
16+ built-in client application (*without* a ``.js`` file extension).
17+ source_file:
18+ Only applicable if running on the built-in client app. Dynamically install
19+ the code in the give file as a single-file module. The built-in client will
20+ inject this module adjacent to other installed modules which means they can
21+ be imported via a relative path (e.g. ``./some-other-installed-module.js``).
22+
23+ .. warning::
24+
25+ Do not use the ``source_file`` parameter if not running with the client app
26+ distributed with ``idom``.
27+
28+ Examples:
29+ .. testcode::
30+
31+ import idom
32+
2533 """
2634
2735 __slots__ = "_module" , "_installed"
2836
2937 def __init__ (
3038 self ,
31- name : str ,
32- source : Optional [Union [str , Path ]] = None ,
33- replace : bool = False ,
39+ path : str ,
40+ source_file : Optional [Union [str , Path ]] = None ,
3441 ) -> None :
3542 self ._installed = False
36- if source is not None :
37- if replace :
38- client .delete_web_modules ([name ], skip_missing = True )
39- self ._module = client .register_web_module (name , source )
43+ if source_file is not None :
44+ self ._module = client .register_web_module (path , source_file )
4045 self ._installed = True
41- elif client .web_module_exists (name ):
42- self ._module = client .web_module_url (name )
46+ elif client .web_module_exists (path ):
47+ self ._module = client .web_module_url (path )
48+ self ._installed = True
49+ elif not _is_url (path ):
50+ raise ValueError (
51+ f"{ path !r} is not installed - "
52+ "only installed modules can omit a file extension."
53+ )
4354 else :
44- self ._module = name
55+ self ._module = path
4556
4657 @property
4758 def installed (self ) -> bool :
59+ """Whether or not this module has been installed into the built-in client app."""
4860 return self ._installed
4961
5062 @property
5163 def url (self ) -> str :
64+ """The path this module will be imported from"""
5265 return self ._module
5366
5467 def Import (self , name : str , * args : Any , ** kwargs : Any ) -> "Import" :
68+ """Return an :class:`Import` for the given :class:`Module` and ``name``
69+
70+ This roughly translates to the javascript statement
71+
72+ .. code-block:: javascript
73+
74+ import { name } from "module"
75+
76+ Where ``name`` is the given name, and ``module`` is the :attr:`Module.url` of
77+ this :class:`Module` instance.
78+ """
5579 return Import (self ._module , name , * args , ** kwargs )
5680
5781 def __repr__ (self ) -> str : # pragma: no cover
@@ -95,3 +119,11 @@ def __call__(
95119 def __repr__ (self ) -> str : # pragma: no cover
96120 items = ", " .join (f"{ k } ={ v !r} " for k , v in self ._import_source .items ())
97121 return f"{ type (self ).__name__ } ({ items } )"
122+
123+
124+ def _is_url (string : str ) -> bool :
125+ if string .startswith ("/" ) or string .startswith ("./" ) or string .startswith ("../" ):
126+ return True
127+ else :
128+ parsed = urlparse (string )
129+ return parsed .scheme and parsed .netloc
0 commit comments