*import
Summary
*import loads HTML from an external URL and injects it into the current element’s innerHTML. The imported HTML is then processed by Sercrod in the same render pass, so any directives inside the imported content (*if, *for, *each, *include, *template, and so on) are evaluated as usual.
Key points:
*importandn-importare aliases.- The directive resolves a URL string, fetches HTML with a synchronous XMLHttpRequest, caches the response per URL, and replaces the host’s inner content.
- Infinite or deeply nested
*include/*importchains are guarded by a depth limit. - A single element must not combine
*importwith*eachor*include. Putting multiple structural directives on the same host is unsupported and leads to undefined behavior.
Basic example
Load a simple partial file:
<serc-rod id="app" data='{"user":{"name":"Alice"}}'>
<section *import="'/partials/user-card.html'"></section>
</serc-rod>
Behavior:
- Sercrod evaluates the expression
'/partials/user-card.html'and resolves it to a URL string. - It performs a synchronous HTTP request to that URL (with optional configuration).
- If the request succeeds and returns non-empty HTML, Sercrod assigns that HTML to
section.innerHTML. - Sercrod then continues rendering the
<section>subtree, so any directives insideuser-card.htmlare processed in the same pass.
Behavior
At a high level, *import does the following when encountered on an element:
- It resolves a URL from the attribute value, using expression evaluation and simple heuristics.
- It checks the current include/import nesting depth and refuses to exceed a configured
max_depth. - It performs a synchronous HTTP request to download HTML from the resolved URL.
- If HTML is successfully obtained, it replaces the element’s
innerHTMLwith the fetched HTML. - It removes
*import/n-importfrom the element. - It does not return early after filling
innerHTMLso that normal child rendering (including directives inside the imported HTML) can run immediately.
Alias:
*importandn-importbehave identically.- Use one style consistently across your project.
URL resolution
The value of *import is interpreted as a URL in two steps:
-
Expression evaluation
Sercrod first tries to evaluate the raw attribute value as an expression:
- It calls
eval_expr(raw_text, scope, { el: node, mode: "import", quiet: true }). - If the evaluation result is not
nullorundefined, Sercrod converts it to a string and trims it. - If the trimmed string is non-empty, that string becomes the URL.
This allows patterns such as:
<serc-rod id="app" data='{"base":"/partials","name":"user-card.html"}'> <section *import="base + '/' + name"></section> </serc-rod> - It calls
-
Fallback to raw text
If expression evaluation does not yield a usable string, Sercrod falls back to the raw attribute text and checks whether it “looks like a URL”:
- The raw text must not contain whitespace.
- If it starts with
http://orhttps://, or - If it starts with
./,../, or/, or - If it contains a dot
.or a slash/,
then the raw text is treated as the URL.
If neither step produces a non-empty URL string, or the URL becomes the literal "false", the import is treated as invalid:
- The element’s
*import/n-importattributes are removed. - Depending on configuration, Sercrod may mark the element with
sercrod-import-invalid. - The element may or may not be kept in the DOM, depending on
remove_element_if_empty(see configuration below).
Network loading and caching
Once a URL is resolved, *import uses a synchronous XMLHttpRequest to fetch HTML:
-
It reads
this.constructor._config.import(if present) to configure the request:method: HTTP method, default is"GET".credentials: boolean, default isfalse. Iftrue,xhr.withCredentialsis set.headers: plain object with additional HTTP headers.
-
Responses are cached per URL in a class-level map:
- If a cached entry exists and is non-empty, Sercrod skips the network request and uses the cached HTML.
- If the cache is empty for that URL, Sercrod performs a network request and, on success, stores the response text.
Error handling:
- If the HTTP status code is not in the
2xxrange, Sercrod does not cache the response and may mark the element withsercrod-import-error="<status>", depending onwarn_on_element. - If an exception occurs during the request, Sercrod may mark the element with
sercrod-import-error="exception", again depending on configuration. - If no HTML is obtained (empty string), the import is considered failed:
*import/n-importare removed.- The element may be kept or dropped based on
remove_element_if_empty(see below).
Note:
- Because
*importuses a synchronous request, it can block the main thread during network I/O. - For large or remote resources, consider server-side rendering, static generation, or pre-fetching strategies instead of heavy runtime imports.
Depth management and recursion guard
*import shares its depth tracking with *include:
- Sercrod maintains a numeric “include/import depth” in an internal WeakMap.
- Each
*includeor*importincrements the depth relative to the nearest ancestor*include/*import.
Configuration:
-
this.constructor._config.include.max_depth(or an internal_include_max_depth) limits the allowed depth. -
If a
*importwould exceed thismax_depth:- When
warn_on_elementis true, the element is marked withsercrod-import-depth-overflow="<max_depth>". - The
*import/n-importattributes are removed. - Depending on configuration, the element may be appended in its current (unfilled) state, or import is simply skipped.
- When
Practical meaning:
- You can safely use nested includes and imports, but extremely deep or cyclic chains will be stopped.
- This prevents infinite recursion between templates that import or include each other.
Evaluation timing
Within the host rendering pipeline, *import runs after template registration and *include, but before literal-only blocks:
*templateon the same element is processed first.- If an element has
*template, it is treated as a template declaration and is not rendered;*importon the same element will effectively never run.
- If an element has
*includeis handled before*import.*importthen resolves and injects HTML into the element’sinnerHTML.- After that, Sercrod continues normal child rendering, which means:
- Directives inside the imported HTML are evaluated in the same pass.
- Binding directives, event handlers, and nested loops inside the imported content work as expected.
The *import directive itself is one-time per render pass:
- Once processed,
*import/n-importare removed, so the same element will not re-import content during the same render cycle. - Re-imports only occur if the surrounding Sercrod host re-renders from scratch or if the element is re-created.
Execution model
The internal steps for *import are roughly:
-
Depth and configuration
- Read include-related config (
max_depth,warn_on_element,remove_element_if_empty). - Compute this element’s depth based on the nearest ancestor
*include/*import.
- Read include-related config (
-
Depth check
- If depth exceeds
max_depth, handle overflow (mark the element if configured, remove directive, optionally keep the element) and stop.
- If depth exceeds
-
Resolve raw text
- Read
*import/n-importasraw_text. - If
raw_textis empty, remove the attribute and stop.
- Read
-
Resolve URL
- Attempt expression evaluation for
raw_text. - If that yields no usable string, treat
raw_textas a potential URL and apply simple heuristics. - If no URL can be chosen or the result is
"false", treat import as invalid, remove the directive, and stop (optionally marking the element).
- Attempt expression evaluation for
-
Fetch HTML
- Use class-level cache for the URL.
- If not cached yet, perform a synchronous HTTP request with configured method, headers, and credentials.
- If response status is
2xx, treatresponseTextas HTML and cache it. - On error or exception, optionally mark the element, then stop if there is no HTML.
-
Inject HTML and clean up
- Assign
node.innerHTML = html. - Remove
*import/n-importattributes from the element. - Do not return here; instead, let the renderer continue to process the new children so that directives inside the imported HTML are executed.
- Assign
Scope and interaction with imported HTML
*import does not create new variables or new scope layers by itself:
-
The imported HTML is treated as if it had been written inside the element from the start.
-
Directives inside the imported content see the same scope as any other child of the host element:
- They can access host data (for example
user,items,state). - They can access
$data,$root,$parent, and any injected methods.
- They can access host data (for example
If you want imported content to have a specific scope:
- Place
*importon an element that already has the right data and context. - Or combine import with
*letor*stageon the same Sercrod host (but not on the same element if the directives would conflict structurally).
*import itself does not inject any new names into the scope.
Use with *template, *include, and *each
*import is one of several structural directives that take control of a host element’s children:
*templateregisters a template and skips rendering the original element.*includeresolves a template name and replaces the host’sinnerHTMLwith the template’s body.*importfetches HTML from a URL and replaces the host’sinnerHTMLwith the fetched content.*eachtransforms the host’s children into a loop body.
Because they all try to own the same inner content, there are important restrictions:
-
*importwith*templateon the same element- In the current implementation,
*templateis processed first and returns early. - That means
*importon the same element never runs. - Treat this combination as unsupported; pick one or separate the responsibilities into different elements.
- In the current implementation,
-
*importwith*includeon the same element*includeand*importboth want to replace the host’sinnerHTML.- The runtime does not merge their behaviors.
- Putting both on the same element is unsupported and leads to undefined behavior.
- Use either
*include(for local templates) or*import(for external HTML), but not both on one tag.
-
*importwith*eachon the same element*eachexpects to use the host’s original children as a loop body.*importwants to overwrite those children with imported HTML.- The implementation does not coordinate these operations.
- Therefore, a single element must not combine
*eachand*import. This combination is explicitly unsupported and should be avoided.
Supported pattern examples:
-
*importon a container element, then use loops inside the imported HTML:<section *import="'/partials/user-list.html'"></section> -
*eachon the container and*importon a child:<ul *each="user of users"> <li *import="'/partials/user-row.html'"></li> </ul>Here:
<ul>is the loop container.<li>is the template body per iteration.- The imported snippet can freely use
userfrom the loop scope.
-
*importto bring in a block that defines or uses*templateinside:<section *import="'/partials/templates.html'"></section>The imported HTML may register templates with
*template, which can be used later by*includeelsewhere.
Configuration
*import reads settings from the Sercrod constructor’s config object:
-
this.constructor._config.include:max_depth: maximum allowed depth for nested*include/*import(default is 16 if not overridden).warn_on_element: if true, errors related to include/import depth or URL resolution are recorded as attributes on the element (for examplesercrod-import-depth-overflow,sercrod-import-invalid,sercrod-import-error).remove_element_if_empty: if true, elements whose include/import fails may be omitted entirely instead of being kept as empty placeholders.
-
this.constructor._config.import:method: HTTP method used for import requests (default"GET").credentials: boolean, mapped toxhr.withCredentials.headers: object with additional headers, applied viaxhr.setRequestHeader.
These configuration values are optional; if you do not set them, Sercrod uses sensible defaults.
Best practices
-
Prefer
*includefor in-document templates:- When your content already lives in the same document,
*templateplus*includeis usually simpler and faster than*import.
- When your content already lives in the same document,
-
Use
*importfor coarse-grained external HTML:- Import whole blocks or sections (cards, lists, layout pieces) rather than tiny fragments.
- Each
*importimplies a synchronous request when not cached.
-
Keep URLs simple and explicit:
- Use quoted string literals or short expressions.
- If you need to encode multiple pieces of information (for example file and section name), do it in the URL and let the server interpret it; Sercrod does not parse
":name"postfixes in any special way.
-
Avoid heavy or large imports during interaction:
- Because
*importis synchronous, it can introduce noticeable pauses for remote or slow resources. - For interactivity, prefer data-driven updates, JSON APIs, or pre-rendered HTML instead of repeated imports.
- Because
-
Respect structural restrictions:
- Do not put
*importon the same element as*eachor*include. - Avoid combining
*importand*templateon the same element; only*templatewill take effect.
- Do not put
Additional examples
Dynamic URL based on data:
<serc-rod id="app" data='{"lang":"en","page":"about"}'>
<main *import="`/pages/${lang}/${page}.html`"></main>
</serc-rod>
Using headers and credentials via config (conceptual sketch):
Sercrod._config = Sercrod._config || {};
Sercrod._config.import = {
method: "GET",
credentials: true,
headers: {
"X-Requested-With": "XMLHttpRequest"
}
};
Then:
<section *import="'/secure/partial.html'"></section>
Notes
*importandn-importare aliases.- The directive uses Sercrod’s expression sandbox for URL resolution but ultimately treats the resolved value as a plain string.
- Sercrod does not implement any special parsing for suffixes like
:cardin URLs. Such patterns are treated as part of the URL string and must be interpreted by your server if you use them. - Infinite include/import recursion is prevented by a configurable depth limit that applies to both
*includeand*import. - Structural combinations where multiple directives try to control the same host’s children (such as
*importplus*each, or*importplus*include) are not supported and should be considered invalid usage.