
Four of the five most requested Edge user-defined platform features on User Voice (Shadow DOM, Template, Custom Elements, HTML Imports) belong to a family of APIs called Web Components. In this article, we want to talk about web components and our view of them, some in-house kitchen, for those who are not familiar with them, as well as speculate on where all this may evolve in the future. This is a rather long story, so sit back, take a coffee (or not a caffeine drink) and start reading.
Content:
- Component implementation: old design practice that has become new to the web
- How to break into components
- This is not the first time: previous approaches to the implementation of components
- Modern web components
- Web components: the next generation
')
Component implementation: old design practice that has become new to the web
Modern web applications are as complex as any other software applications, and are often created by several people joining forces to create the final product. In such conditions, in order to increase efficiency, it is natural to look for the right ways to divide work into areas with minimal intersections between people and subsystems. The implementation of the component approach (as a whole) is how this problem is usually solved. Any component system must
reduce the overall complexity through the provision of
isolation , or natural barriers that hide the complexity of some systems from others. Good insulation also facilitates reuse and implementation of service paradigms.
Initially, the complexity of web applications was mainly regulated by the server by dividing the application into separate pages, which required the user to switch from one page to another in the browser accordingly. With the introduction of AJAX and related technologies, developers were able to abandon the need to make "transitions" between different pages of a web application. For typical scenarios such as reading mail or news, user expectations have changed. For example, after login to the mail, you can “use the mail application” from the same address (URL) and stay on this page for a whole day (the so-called
Single-Page Applications , SPA). The logic of client-side web applications in such situations becomes much more complicated, sometimes it even becomes more complicated than on the server side. A possible resolution of this complexity may be a further division into components and isolation of logic within a single page or document.
The goal of web components is to reduce complexity by isolating related groups of HTML, CSS, and JavaScript code to perform common functionality within the context of a single page.How to break into components?
Since web components must tie together HTML, CSS, and JavaScript, it is necessary to take into account the existing isolation models inherent in each of the technologies, as they affect the scripts and integrity of the web components. These independent insulation models include:
- CSS Isolation
- Javascript and scope (closures)
- Isolation of a global object
- Element encapsulation (iframe)
CSS Isolation
In today's platform, there is no perfect and natural way to split CSS into components (although tools like
Sass can help a lot). The component model should offer a mechanism to isolate one CSS subset from another, so that the rules will not affect each other. In addition, component styles should apply only to the direct parts of the component and nothing else. Easier said than done!
Inside the style sheets, CSS rules are applied to the document using selectors. Selectors are always considered as potentially applicable to the whole document, therefore their scope is, in essence, global. Global application leads to real conflicts when several people working on a project mix their CSS files together. C intersections and repetitions of selectors can be dealt with in a precise order (for example, cascades, specificity, order of source) for resolving conflicts, however, such actions are quite likely - not at all what the developers wanted. There are many potential ways to solve this problem. A simple solution is to transfer elements and related styles that participate in the formation of a component from the main document to another document (shadow document) so that they will no longer “react” to other people's selectors. This leads to the second problem: now that we have separated them, how can a certain style cross the border (to control the outside of the component)? The obvious possible solution is to use JavaScript explicitly, but it looks like something awful: relying on JavaScript to transfer styles across the border, which seems more like a space in CSS.
To transfer styles across a component border in an efficient manner and at the same time protect the structure of a component (for example, to allow the freedom to change the structure without affecting styles), there are two general approaches that many prefer: “partial” styling using pseudo-elements and
custom properties (previously known as “variable” CSS). For a while, the super-powerful cross-border selector '>>>' (defined in
CSS Scoping ) was also considered, but today it is not universally accepted as a very good idea, as it easily breaks the isolation of components.
Partial stylization will allow the authors of the component to create their own pseudo-elements for stylization, thus exposing the outside world only a part of its internal structure. This is similar to the model that browsers use to place
"parts" of native controls . For the integrity of this scenario, authors will also need some way to limit the set of styles that can apply to a pseudo-element. Additional research of this “partial model” based on pseudo-elements may lead to the appearance of convenient stylistic primitives, although the elaboration of details will still require effort. Further work on the partial model should also rationalize the
styling of native controls in browsers (an area that clearly needs attention).
Custom properties will allow authors to describe the values ​​of the styles that they want to reuse in style sheets (defined as their own double-dash property names as a prefix). Custom properties are inherited through the document sub-tree, allowing selectors to redefine the value of the custom property for a particular sub-tree without affecting other sub-trees. Custom properties can also be inherited across component boundaries, providing an elegant component styling mechanism that avoids the disclosure of the internal structural nature of the component. Custom properties were evaluated when developing various component frameworks in Google and, according to reports, allow to cover most of the stylization needs.
Of all the approaches currently being considered for styling, the future “partial” model and the current specification of custom properties seem to have the greatest chance of implementation. We consider custom properties as a new key member of the web component specification.
Other approaches to isolating CSS styles
To complete the picture, the scope and isolation of CSS is not such a black and white area as it might seem above. In fact, several past and current approaches offer options for limiting the scope and isolation with varying applicability to web components.
CSS offers some limited forms of isolation of selectors in specific scenarios. For example, the @ ‍media rule groups a set of selectors together and applies them when conditions match the media context (for example, the size or resolution of the viewport, or the media type — print, etc.); The @ ‍page rule defines some styles that are applicable only in the context of printing;
The @ ‍supports rule brings together selectors for use only when support for specific CSS functionality is implemented (a new form of determining the presence of functionality in CSS); the proposed
@ ‍document rule groups selectors for use only when the document in which the styles are loaded meets the conditions.
CSS scopes (originally written as part of working on web components) offer a way to limit the applicability of CSS selectors within a single HTML document. The specification introduces the new @ ‍scope rule, which allows the selector to determine the root (s) of the application and further leads to the fact that applying all selectors within the @ ‍scope rule will work only in the subtree of this root (and not on the entire document). The specification allows you to specify the root of the domain declaratively in HTML (for example, the <style scoped> attribute is suggested, as long as it is implemented only in Firefox; this functionality was previously available as an experimental feature in Chrome, but was completely removed later). Some aspects of this functionality (for example,: scope, defined in
Selectors L4 ) can also be applied to the relative evaluation of selectors in the
new query API in the DOM specification .
It is important to note here that @ ‍scope establishes only
one-directional isolation of boundaries: selectors contained within @ ‍scope are limited to this area, while any other selectors (outside @ scope) can quietly penetrate inside @ ‍scope (although they can be - in various ordered cascade styles). This is a somewhat unfortunate design, as it does not provide for limiting the scope and isolation from any styles that are not in the @ ‍scope subset - all CSS should still “fit well” to avoid styling inside another @ ‍scope rule. See also the
@ ‍in-shafow-of draft from Taba , which is better aligned with the component insulation protection model.
Another suggestion of visibility limitations is
containment in CSS . Holding the field of view is less about isolating styles and selectors and more about isolating “composition”. Inside the “contain” property, the behavior of some CSS features that have natural inheritance (in the sense of applicability from the parent to the child in the document, for example, counters) will be blocked. The main use of this for developers is to indicate that some elements imply strict “containment”, so a composition applicable to this element and its subtree will never affect the composition of other elements of the document. These promises of containment (indicated by the use of the "contain" property) allow browsers to optimize the composition and rendering so that the "new" composition of the held subtree will only require updating this subtree, and not the entire document.
As technology implementations for web components mature among browsers and find more and more public applications, additional styling patterns and problems may appear; we look forward to further investment and subsequent progress in the various proposals in CSS to improve the styling of web components.
Javascript and scopes
All JavaScript code that is included on the page has access to the same global object. Like other programming languages, JavaScript has scopes that provide some level of “privacy” for function code. These lexical scopes are used to isolate variables and functions from the rest of the global environment. The “modular template” in JavaScript, which is popular today (using lexical scopes), evolved from the need of many JavaScript frameworks to “coexist” in a single global environment without “stepping on the heels” of each other (depending on the order of loading).
JavaScript lexical visibility areas are a unidirectional isolation of borders: the code inside the region can have access to both the internal content and the content of any parent area up to the global one, while the code outside the area does not have access to its content.
An important principle is that the unidirectional method of isolation gives preference to the code inside the region, that is, protects it. The code inside the lexical area has the ability to protect / hide itself from the rest of the environment (or not).
The contribution that the lexical scopes of JavaScript make to the implementation of a web component meets the requirement to have a way to “close” a component so that its contents can be reasonably private.
Isolation of a global object
For some code, it may not be desirable for it to have global access to the global environment, as described above. For example, an application developer may not trust some JavaScript code, although it provides significant value. A typical case is advertising and advertising frameworks. For security reasons, it is necessary that the untrusted code be executed in a separate clean script environment (with its own global object). To achieve this behavior today (without the inclusion of iframe elements in the game), developers can use
workers . However, the disadvantage of this solution is that the workers do not have access to the elements, that is, the UI.
There are a number of considerations that need to be taken into account when designing components that support the isolation of a global object — especially if the isolation implies protected boundaries (more details below). Today, we expect that the isolated components will not be fully accessible until the basic set of web component specifications is fixed (that is, it is “postponed until the next version”). However, if we spend some time exploring how the isolated components may look, it can put the current work in the right direction.
Some offers really worth paying attention to.
Isolating a global object is an important unimplemented script for web components. In the meantime, we are working on implementation, for example, we can rely on the most successful and widely used way of introducing componentuality to the web: the iframe element.
Element encapsulation (iframe)
Iframe elements and their close relatives: the object, frameset and imperative API windows.open () elements already provide the ability to work in an isolated subtree of elements. However, if components imply work within a single document, the iframe includes an entire HTML document within it; as if two separate web applications were co-located, just one inside the other. Each has a unique document address, global environment for scripts and CSS scope; each document is completely separate from the other.
Iframe is currently the most successful (and the only widely implemented) form of Web component authorization. Iframe allows you to interact with various web applications. For example, many sites use iframe precisely as a form of a component for all sorts of scenarios from advertising to user login. However, the iframe ran into a number of challenges and some ways to deal with these challenges appeared:
- JavaScript code inside one HTML document can potentially invade the isolation boundaries of other documents (for example, through the contentWindows property of the iframe element). This possibility of trespassing may be a necessary need, but it also represents a security risk when the contents of the iframe contain sensitive information that you would not want to share. Today, unwanted violations can be regulated by common source policies : documents with URLs from one source can violate default boundaries, while documents from different sources have limited ability to interact with each other.
- Border violation is not the only security risk. Using the <iframe sandbox> attribute imposes further restrictions on iframes from other sources in order to protect the host environment from unwanted scripts, pop-ups, navigation changes and other features available in the iframe.
- External document CSS styles cannot be applied to an internal document. This architectural solution follows the principle of isolation. However, the isolation of styles creates a significant gap in the integration of the iframe as a component (within the common source of origin). HTML addresses this problem with the proposed <iframe seamless> attribute for a common source iframe. This “seamlessness” attribute removes the isolation of the frame's content styles; seamlessly included documents take a copy of the stylesheets of the host document and are displayed as if their restrictions to the iframe element in which they were included were not.
With good security policies and the ability to insert a frame seamlessly, using an iframe as a model for components seems like a very attractive solution. However, several properties that are desirable in the web components model are still missing:
- Deep integration. Iframe limits (and basically disables) the integration and interaction of models between the host and the frame document. For example, with respect to the host: the focus and allocation model is independent, and the transmission of events is isolated by one or another document. For components that suggest closer integration, support for this behavior is not possible without the introduction of some “agent” in the host document that would forward information across the border.
- The multiplication of global objects. For each iframe entity created on the page, there will be a unique global object. A global object and its associated complete type system is not cheap to create and can lead to a large amount of memory consumption and an excessive browser load. Multiple copies of the same component used on the same page do not need to be isolated from each other; in practice, having a common global object may be desirable, especially if they need to maintain a certain general condition.
- Host Content Usage Model. Iframe does not allow the reuse of the content model of the host element within the frame document. (For simplicity: the content model of an element is its supported subtree of elements and text.) For example, select-elements have a content model that includes option-elements. A select element implemented as a component will want to interact in some way with its child elements.
- Selective styling. Seamless iframe does not work with documents from different sources. There are clear security risks if this were allowed. The main problem is that the “seamlessness” is controlled by the host, and not by the frame document (the frame document is often the victim of attacks). For a component, the two-digit possibility of including "seamless" (whether or not) may be too expensive; components would rather choose to decide which styles from the host are applicable to their content (instead of automatically inheriting all styles, that is, how seamlessness works). In general, the question of what should be styled should be resolved by the component itself .
- Expose API. Many scripts for web components involve the creation of full-fledged custom elements with their own exposed API set, display semantics and lifecycle management. Using an iframe restricts the developer to work within the iframe API, with all its features. For example, you cannot influence the parameters of the iframe itself and its life cycle.
This is not the first time.
We can not fail to note that in the past several technologies have already been proposed and even implemented in an attempt to improve the work of the iframe in HTML and the associated encapsulation capabilities. However, none of them got accustomed to a large extent in the modern web:
- HTML Components (1998) have been proposed and implemented by Microsoft since IE5.5 (out of date in IE10). The intention was to use a declarative model to add events and APIs to the host element (including isolation) and parse the components into some “viewlink” (like the “shadow DOM”). Two types of behavior of components were available: they were simultaneously attached to an element, the other was dynamically linked through the CSS property “behavior”.
- XBL (2001) and its successor XBL2 (2007) were proposed by Mozilla as an addition to XUL to describe interfaces. It is a declarative language with two linking capabilities (similar to Microsoft’s HTML components), XBL also supported additional access to the host content model and content generation.
Modern web components
After the failure of the first two attempts, it was time to try to launch the game into components again, this time Google took up the business. Using the concepts described in XBL as a starting point, the monolithic component system was broken down into a collection of
building blocks for components . These building blocks allowed web developers to experiment with individual useful features before the overall vision for the web components is fully defined. The component of the approach itself and the development of individual useful features allowed us to move closer to success. Almost everyone can find something useful in their web components for their application!
This new wave of web components has led to a set of
concrete use cases explaining how existing embedded elements work within today's web platform. In theory, web components will allow developers to prototype new types of HTML elements with the same accuracy and characteristics as native elements (in practice, ensuring accessibility in HTML is especially difficult to achieve today).
It is clear that the full set of all technologies necessary to cover all scenarios for the use of web components will not be implemented immediately in browsers. Browser developers work together to align a basic set of technologies that can be consistently implemented before moving on to additional scenarios.
The first generation of web components includes:
- Custom elements . Custom elements define the extension point of the HTML parser so that it can recognize the names of the new “custom elements” and automatically provide them with the necessary JavaScript object model. Custom elements do not create component boundaries, but they do provide the browser with a way to attach APIs and behaviors to original elements. Browsers that do not support custom elements can simulate them (through polyfiles) with some accuracy, using events and change monitors and adjusting the prototype. Proper planning and understanding of the implications is a key element of our upcoming meetings.
- The “is” attribute . It is hidden inside the specification of custom elements, but provides critical functionality — the ability to specify that the embedded element should receive the name of custom elements and new APIs. In the usual case, a custom element is based on some common element; using “is”, the native element can be used as a basis (for example, <input is = ”custom-input”>). Although this functionality is a great way to inherit the benefits of embedded rendering, accessibility, parsing, etc., the syntax of this functionality is perceived rather as a hack, and it is believed that perhaps the primitives for accessibility and the stylization of native elements is more appropriate in the long run. plan for standardization.
- Shadow DOM (Shadow DOM). Provides an imperative API for creating a separate element tree that can be connected (one-time) to the host element. Such "shadow" descendants replace the "real" descendants when displaying a document. The shadow DOM also provides a mechanism for using the content model of host elements using new slot elements (recently proposed), solves the problem with the target element of events and adds open / closed modes of operations (also recently added). This relatively trivial idea has a surprisingly large number of third-party effects in everything, starting with the focus model and selection and ending with composition and propagation (for a shadow DOM inside a shadow DOM).
- CSS scopes define various pseudo-elements relevant to the styling of a shadow DOM, including: host, :: content (it may soon become :: slot), and the former “>>>” (permeating the shadow DOM combinator), which is now officially disavowed .
- Template element . It is included for completeness, this functionality was previously part of the web components, and is now part of the HTML5 recommendation . The template element introduces the concept of inertia (the child elements of the template do not lead to the loading of resources, do not respond to user input, etc.). This is a way to declaratively create an unlinked subtree of an element in HTML. A template can be used for different tasks: from the actual template samples and data binding to the provision of content for the shadow DOM.
- HTML Imports . Specifies the declarative syntax for “import” (request, load, or parse) HTML into a document. Import requests (using a link element with rel = "import") execute the scripts of the document being imported in the context of the host page (thus having access to the same global object and state). HTML, JavaScript, and CSS parts of a web component can, respectively, be loaded using a single import.
- Custom properties . As described in more detail above, custom properties described outside the component and available for use inside the component are a simple and convenient model for styling components today. Considering this, we have included custom properties in the first generation of technologies for web components.
Web components: the next generation
As we noted at the beginning of this article, building full-featured web components is a great adventure. Several ideas for developing and filling gaps in the current generation of opportunities have already begun to circulate among developers (this is not a complete list!):
- Declarative Shadow DOM . It becomes important when you are thinking about how to transfer components in a serialized manner. Without a declarative form, techniques like innerHTML or XMLSerializer will not be able to build a string representation of the DOM that includes any shadow content. In other words, the shadow DOM today cannot be converted to and from a string without the help of a script. Mozilla's Anne suggested the <shadowdom> element as a topic for discussion. Similarly, a template element is already a declarative way to build "shadow" markup and the serialization methods in the browser have already adjusted to this feature and, accordingly, serialize the "shadow" content of the template in the correct way.
- Fully insulated components . Three browser makers made three different offers in this area. These proposals are already quite well coordinated, which is good news in terms of reaching a consensus. As mentioned earlier, isolated components will use new global objects and can be imported from other sources. They will also have a reasonable model for exposing APIs and related behaviors across their isolation boundary.
- Primitives accessibility . Many accessibility communities sympathize with the “is” idea (from custom elements) as a way of extending existing native elements, as they already contain accessibility mechanisms that are not always available to JavaScript developers. Ideally, regular web components (without using “is”) could include aspects of accessibility in much the same way as native elements, among other things, including sending forms and the ability to focus. Such expansion points are not possible today, but must be worked out and defined.
- Single stylization of native controls . The gap in the presence of a stylized control model between browsers is an interoperability problem that interferes, for example, with the proliferation of simple “theme change” extensions. This leads to the fact that developers often consider a shadow DOM as an alternative solution (although creating markup in a shadow DOM with behavior similar to that of native elements may be nontrivial). CSS-, .
- CSS . CSS -, , , , . , , .
- . , . -.
, -, iframe . , iframe – -, , . «» iframe. , <iframe seamless> .
- – . .
@msedgedev .