Stencil JS
repository·main·Indexed 11 days ago
https://github.com/stenciljs/coreA compiler for building high-performance, standard-compliant Web Components and Progressive Web Apps using TypeScript and JSX. Stencil provides a modern developer experience similar to React or Vue but outputs native custom elements that work across any frontend framework. Version 4.44.0 includes support for Server-Side Rendering (SSR), hydration, and various output targets such as Angular wrappers and lazy-loaded collections.
What's inside Stencil
- Stencil is a compiler designed to generate Web Components using TypeScript and JSX. It is built by the Ionic team and produces standard-compliant web components that can be used in any major frontend framework or as standalone elements in plain HTML.
Overview of Stencil Architecture
mainStencil is a compiler that generates Web Components and builds high-performance web applications. It operates across several distinct phases:
- Build Time: Managed by the CLI and Compiler, which uses TypeScript, Rollup, and a Build Optimizer to transform source code into optimized bundles.
- Development: Powered by a Dev Server featuring Hot Module Replacement (HMR) and a WebSocket server for real-time updates.
- Runtime: A client-side runtime that manages the Virtual DOM, component lifecycles, and state reactivity in the browser.
- Server Side: Supports Server-Side Rendering (SSR) and hydration via a Node.js rendering environment and a Mock Document implementation for DOM emulation.
Overview of Stencil Compiler Architecture
mainThe Stencil Compiler follows a modular architecture organized into four main layers:
- Input Layer: Consumes configuration, source files, and assets.
- Processing Layer: Includes the TypeScript Parser, AST Transformers, Rollup Bundler, and an Optimizer.
- Output Layer: Uses Output Generators, a File Writer, and a Build Validator to produce the final artifacts.
- Support Systems: Provides cross-cutting concerns like the Cache System, Worker Threads for parallel processing, and Diagnostics for error reporting.
What is Mock Doc and how is it used?
mainMock Doc is a lightweight DOM implementation designed for server-side rendering (SSR) in Node.js environments. It provides a subset of the DOM and Web APIs necessary to allow Stencil components to render without a real browser. It includes implementations forMockWindow,MockDocument, variousElementclasses, and basic CSS/Event handling.The Stencil lifecycle process
mainThe Stencil runtime manages component lifecycles through several distinct phases:
1. Connect
Occurs synchronously within
connectedCallback. The component climbs the DOM tree to find an ancestor with ans-initfunction. If found, the component adds itself to the ancestor'ss-al(actively loading) set.2. Initialize Component
Triggered by
initializeComponent. It asynchronously loads the component constructor and creates an instance. If an ancestor is still initializing, the component registers a callback in the ancestor'ss-rcarray instead of firing its lifecycle immediately.3. First Update
Triggered by
updateComponent. This phase includes:- Setting
s-lrtofalse. - Firing
componentWillLoadandcomponentWillRender. - Setting up Shadow DOM and scoped CSS.
- Performing the first render.
- Setting
s-lrtotrue. - Firing child render callbacks (
s-rc). - Firing
componentDidLoadandcomponentDidRender. - Adding the
.hydratedclass. - Calling
s-initon the ancestor if thes-alset becomes empty.
4. Subsequent Updates
Triggered by
@Propor@Statechanges, or manual calls toforceUpdate(). This phase includes:- Firing
componentWillUpdateandcomponentWillRender. - Patching the render.
- Firing
componentDidUpdateandcomponentDidRender.
- Setting
How lazy loading and component registration work
mainStencil components are lazy-loaded by default to reduce initial bundle size.
- Registration: During bootstrap, Stencil registers a lightweight
HostElement(extendingHTMLElement) for each component usingcustomElements.define. This host element acts as a placeholder. - Dynamic Import: When a
HostElementis connected to the DOM, Stencil triggers a dynamicimport()of the actual component bundle (e.g.,./build/[bundleId].js). - Activation: Once the module is loaded, the real component logic is instantiated and attached to the host element.
- Registration: During bootstrap, Stencil registers a lightweight
How custom types for Props and Events are exported in v3.0.0
mainStencil now automatically re-exports custom types for props and custom events from the project's
components.d.tsfile. This allows you to import these types directly from your library's entry point or the custom element output.For example, if a component defines:
export type NameType = string; export type Todo = Event; @Component({ tag: 'my-component' }) export class MyComponent { @Prop() first: NameType; @Event() todoCompleted: EventEmitter<Todo> }You can now access them via:
import { NameType, Todo } from '@my-lib/types';Test component lifecycle and events
mainWhen testing components with the legacy runner, follow these patterns:
Lifecycle Methods
Verify that methods like
componentWillLoadorcomponentDidLoadare called by checking state changes or flags within the component instance.Custom Events
To test events, you can use standard Jest spies on the
rootelement or use thespyOnEventutility provided by the E2E runner.// Lifecycle pattern import { newSpecPage } from '@stencil/core/testing'; // ... component definition ... const page = await newSpecPage({ components: [TestLifecycle], html: '<test-lifecycle></test-lifecycle>' }); expect(componentWillLoadCalled).toBe(true); // Event pattern const eventSpy = jest.fn(); page.root.addEventListener('myEvent', eventSpy); page.rootInstance.emitEvent(); expect(eventSpy).toHaveBeenCalledWith(expect.objectContaining({ detail: { message: 'Hello' } }));Understand the Stencil Dev Server Architecture
mainThe Stencil Dev Server is designed for a fast development experience using a multi-process architecture. It separates the main process (CLI/Watch Task and Compiler) from the Server Process to ensure stability and performance.
Process Roles
- Main Process: Orchestrates the CLI, watch tasks, and the compiler. It communicates with the server process via Inter-Process Communication (IPC).
- Server Process: A separate Node.js process that hosts the HTTP server (for static files and requests) and a WebSocket server (for Hot Module Replacement).
- Browser (Client): The Dev Client runs in the browser, receiving updates via WebSockets to perform DOM updates, console forwarding, and error overlays without full page reloads.
How lazy loading is implemented
mainStencil's lazy loading mechanism ensures minimal initial payload by using a proxy-to-real-element upgrade pattern:
- Proxy Generation: The compiler generates a lightweight proxy component for every component.
- Registration: The proxy is registered with the Stencil runtime loader.
- On-Demand Import: When the browser encounters the custom element tag, the loader triggers a dynamic
import()for the actual component module. - Upgrade: Once the module loads, the component initializes and upgrades the existing proxy element into the real component instance.
Understand the component lifecycle order of operations
mainStencil components follow a specific lifecycle execution order to ensure parent-child relationships are respected during hydration.
componentWillLoadfires from top to bottom (parent to child).componentDidLoadfires from bottom to top (child to parent).
Note that because components are lazy-loaded, they may finish loading in a random order. However, Stencil ensures the correct firing order by allowing
componentWillLoadto return aPromise. All child components will wait for that promise to resolve before their own lifecycles proceed.Example Execution Flow: For a hierarchy
<cmp-a><cmp-b><cmp-c></cmp-c></cmp-b></cmp-a>:cmp-a-componentWillLoad(Top)cmp-b-componentWillLoadcmp-c-componentWillLoadcmp-c-componentDidLoad(Bottom)cmp-b-componentDidLoadcmp-a-componentDidLoad(Top)
Understand Stencil Output Target Terms
mainStencil uses specific terminology to describe how components are packaged and delivered to consumers:
script: A prebuilt, stand-alone webapp ready to be loaded via a<script>tag without additional bundling.collection: Transpiled JavaScript files with component metadata attached as static getters. This format is future-proof and allows one Stencil distribution to be imported by another.host: The element in the DOM that represents the component.lazy-loaded: A webapp that creates proxied host custom elements upfront but only downloads the actual component implementation (class and CSS) on-demand. Ideal for large libraries like Ionic.module: Component code designed to be imported by other bundlers.native: A traditional custom element where the host element and the component instance are the same (the opposite of lazy-loaded).custom-element: Stand-alone, self-contained custom elements that import the shared runtime from@stencil/core. Consumers must explicitly import and define these.