Stencil JS

repository·main·Indexed 11 days ago

https://github.com/stenciljs/core

A 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.

Tokens
48.7K
Snippets
141
Records
223
Agent score
95%

What's inside Stencil

  1. What is Stencil?

    main
    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.
  2. Overview of Stencil Architecture

    main

    Stencil is a compiler that generates Web Components and builds high-performance web applications. It operates across several distinct phases:

    1. Build Time: Managed by the CLI and Compiler, which uses TypeScript, Rollup, and a Build Optimizer to transform source code into optimized bundles.
    2. Development: Powered by a Dev Server featuring Hot Module Replacement (HMR) and a WebSocket server for real-time updates.
    3. Runtime: A client-side runtime that manages the Virtual DOM, component lifecycles, and state reactivity in the browser.
    4. Server Side: Supports Server-Side Rendering (SSR) and hydration via a Node.js rendering environment and a Mock Document implementation for DOM emulation.
  3. Overview of Stencil Compiler Architecture

    main

    The Stencil Compiler follows a modular architecture organized into four main layers:

    1. Input Layer: Consumes configuration, source files, and assets.
    2. Processing Layer: Includes the TypeScript Parser, AST Transformers, Rollup Bundler, and an Optimizer.
    3. Output Layer: Uses Output Generators, a File Writer, and a Build Validator to produce the final artifacts.
    4. Support Systems: Provides cross-cutting concerns like the Cache System, Worker Threads for parallel processing, and Diagnostics for error reporting.
  4. What is Mock Doc and how is it used?

    main
    Mock 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 for MockWindow, MockDocument, various Element classes, and basic CSS/Event handling.
  5. The Stencil lifecycle process

    main

    The 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 an s-init function. If found, the component adds itself to the ancestor's s-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's s-rc array instead of firing its lifecycle immediately.

    3. First Update

    Triggered by updateComponent. This phase includes:

    • Setting s-lr to false.
    • Firing componentWillLoad and componentWillRender.
    • Setting up Shadow DOM and scoped CSS.
    • Performing the first render.
    • Setting s-lr to true.
    • Firing child render callbacks (s-rc).
    • Firing componentDidLoad and componentDidRender.
    • Adding the .hydrated class.
    • Calling s-init on the ancestor if the s-al set becomes empty.

    4. Subsequent Updates

    Triggered by @Prop or @State changes, or manual calls to forceUpdate(). This phase includes:

    • Firing componentWillUpdate and componentWillRender.
    • Patching the render.
    • Firing componentDidUpdate and componentDidRender.
  6. How lazy loading and component registration work

    main

    Stencil components are lazy-loaded by default to reduce initial bundle size.

    1. Registration: During bootstrap, Stencil registers a lightweight HostElement (extending HTMLElement) for each component using customElements.define. This host element acts as a placeholder.
    2. Dynamic Import: When a HostElement is connected to the DOM, Stencil triggers a dynamic import() of the actual component bundle (e.g., ./build/[bundleId].js).
    3. Activation: Once the module is loaded, the real component logic is instantiated and attached to the host element.
  7. How custom types for Props and Events are exported in v3.0.0

    main

    Stencil now automatically re-exports custom types for props and custom events from the project's components.d.ts file. 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';
  8. Test component lifecycle and events

    main

    When testing components with the legacy runner, follow these patterns:

    Lifecycle Methods

    Verify that methods like componentWillLoad or componentDidLoad are called by checking state changes or flags within the component instance.

    Custom Events

    To test events, you can use standard Jest spies on the root element or use the spyOnEvent utility 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' } }));
  9. Understand the Stencil Dev Server Architecture

    main

    The 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.
  10. How lazy loading is implemented

    main

    Stencil's lazy loading mechanism ensures minimal initial payload by using a proxy-to-real-element upgrade pattern:

    1. Proxy Generation: The compiler generates a lightweight proxy component for every component.
    2. Registration: The proxy is registered with the Stencil runtime loader.
    3. On-Demand Import: When the browser encounters the custom element tag, the loader triggers a dynamic import() for the actual component module.
    4. Upgrade: Once the module loads, the component initializes and upgrades the existing proxy element into the real component instance.
  11. Understand the component lifecycle order of operations

    main

    Stencil components follow a specific lifecycle execution order to ensure parent-child relationships are respected during hydration.

    1. componentWillLoad fires from top to bottom (parent to child).
    2. componentDidLoad fires 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 componentWillLoad to return a Promise. 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 - componentWillLoad
    • cmp-c - componentWillLoad
    • cmp-c - componentDidLoad (Bottom)
    • cmp-b - componentDidLoad
    • cmp-a - componentDidLoad (Top)
  12. Understand Stencil Output Target Terms

    main

    Stencil 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.