npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@servicenow/eslint-plugin-aiux-ssr

v1.3.0

Published

ESLint rules for SSR-compatible Lit components in AIUX

Downloads

225

Readme

@servicenow/eslint-plugin-aiux-ssr

ESLint rules for SSR-compatible Lit components in the AIUX framework.

These rules prevent common SSR issues: hydration mismatches, server-side crashes from browser API access, and non-deterministic rendering.

Rules

| Rule | Severity | Fixable | Description | Suggested Fix | | -------------------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | no-browser-globals-in-render | error | no | Detects window, document, navigator, localStorage, sessionStorage, location, history, screen, alert, confirm, prompt access in render() and constructor. Differentiates crash globals from mismatch globals (navigator). | Move to connectedCallback()/firstUpdated() + reactive property for client-only data. Use static loader() + createContext() for server-needed data. | | no-module-level-browser-access | error | yes | Catches browser globals at module level without a guard. Supports typeof, globalThis.<global>, !isServer, and isServer else-branch guards. | Auto-fix: wraps with globalThis.<global>?.. Or move into a function/class method. | | no-dom-reads-in-render | error | no | Catches getBoundingClientRect(), querySelector(), getComputedStyle(), offsetWidth/offsetHeight etc. in render and constructor. | Move DOM query to firstUpdated() or updated() and store result in a reactive property. | | no-non-deterministic-apis | error | no | Flags Math.random(), new Date(), Date.now(), crypto.randomUUID(), performance.now() in render/constructor. | Compute in connectedCallback() and store as a reactive property, or pass a deterministic value via a context (createContext() + context.set() in loader). | | no-side-effects-in-render | error | no | Prevents this.prop = val mutations, console.*, fetch(), dispatchEvent(), requestUpdate() in render. | Move mutations to updated(), data fetching to static loader(), event dispatch to connectedCallback(). | | no-side-effects-in-constructor | error | no | Blocks addEventListener, setTimeout/setInterval, requestAnimationFrame/cancelAnimationFrame, requestIdleCallback/cancelIdleCallback, new IntersectionObserver/ResizeObserver/MutationObserver in constructors. Differentiates crash APIs (don't exist in Node.js) from leak APIs (exist but leak resources). | Move to connectedCallback() for setup and disconnectedCallback() for teardown. | | require-loader-for-data | warn | no | Warns if fetch() is found in render/constructor of a component without a static loader() method. | Add static async loader(ctx) { ... }, create a context with createContext(), set data via context.set() in the loader, and read via context.get() in render. | | prefer-isserver-guard | warn | no | Suggests using isServer from lit or globalThis.window instead of typeof window !== 'undefined' checks. | Replace with import { isServer } from 'lit' for Lit rendering context, or globalThis.window for runtime detection. | | prefer-nothing | warn | yes | Flags empty string '' alternates in ternaries inside html tagged templates in render methods. Skips property (.prop=), attribute, and event (@event=) bindings where '' is semantically correct. | Auto-fix: replaces '' with nothing and adds the import if missing. | | no-loader-data-in-render | warn | no | Detects this.loaderData and this.getLayoutData(...) usage in render methods of Lit components. Skips getter/method definitions in base class, non-render methods, and non-Lit classes. | Replace with createContext() + context.set() in the loader, declare static contexts = [...], and read via context.get() in render. | | no-return-from-loader | warn | no | Detects return <expression> inside static loader() methods. Skips bare return;, return {} (no-op base class pattern), returns inside nested functions, and non-Lit classes. | Use createContext() + context.set() to share data instead of returning from loader. | | require-context-declaration | warn | no | Detects identifier.get() (zero-argument .get() on a module-level identifier) in render methods of Lit classes that lack static contexts = [...]. Skips classes with contexts, .get(arg), this.get(), non-render methods, and non-Lit classes. | Add static contexts = [contextName] to the class so the framework can hydrate context values during SSR. | | no-context-in-class | warn | no | Detects createContext() calls inside Lit component class bodies (static fields, methods, loader). Contexts are shared across components via import — creating inside a class couples the context to that class and prevents sharing. | Move createContext() to module level and export it. |

Auto-fix Details

Two rules support --fix: no-module-level-browser-access and prefer-nothing.

no-module-level-browser-access

Replaces direct browser global access with globalThis.<global>?. optional chaining, which safely returns undefined in Node.js instead of crashing.

Expression context (variable init, assignment RHS, template expression):

// before
const width = window.innerWidth;
this.theme = localStorage.getItem('theme');
const href = window.location.href;

// after --fix
const width = globalThis.window?.innerWidth;
this.theme = globalThis.localStorage?.getItem('theme');
const href = globalThis.window?.location.href;

Statement context (standalone call, assignment to browser global property):

// before
document.title = 'Hello';

// after --fix
if (globalThis.document) {
  document.title = 'Hello';
}

The fixer walks the full member/call expression chain so window.navigator.userAgent and localStorage.getItem('theme') are replaced as complete units.

prefer-nothing

Replaces empty string alternates in html template ternaries with Lit's nothing sentinel, and adds import {nothing} from 'lit' if not already present.

// before
render() {
  return html`${this.show ? html`<span>Badge</span>` : ''}`;
}

// after --fix
import {nothing} from 'lit';
render() {
  return html`${this.show ? html`<span>Badge</span>` : nothing}`;
}

The fixer skips property/attribute/event bindings (.value=, class=, @click=) where '' is the correct value to clear or unset.

Why the other 11 rules are not fixable

| Rule | Reason | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | no-browser-globals-in-render | Mechanical fixes (globalThis/typeof guard) still produce hydration mismatches — requires architectural change to context pattern or connectedCallback + reactive property | | no-dom-reads-in-render | Needs code movement to firstUpdated/updated | | no-non-deterministic-apis | No substitute for Math.random() or new Date() | | no-side-effects-in-render | Mutations/fetches need architectural redesign | | no-side-effects-in-constructor | setTimeout/addEventListener exist in Node — typeof guard is semantically wrong; needs move to connectedCallback | | require-loader-for-data | Creating a static loader() method is not mechanical | | prefer-isserver-guard | isServer from lit and typeof window have different semantics (Lit rendering context vs JS runtime) | | no-loader-data-in-render | Requires creating a context with createContext(), moving data population to context.set() in the loader, adding static contexts declaration, and updating render to use context.get() — multi-step architectural change | | no-return-from-loader | Requires replacing return with context.set() — not a mechanical transformation | | require-context-declaration | Adding static contexts = [...] requires knowing which contexts are used — not fully mechanical | | no-context-in-class | Moving createContext() out of a class requires restructuring exports and imports across consumers |

Key Design Decisions

  • Render context includes render() and any method starting with render/_render (e.g., renderLayout(), _renderPrimaryNav())
  • Constructor context is also checked by no-browser-globals-in-render, no-dom-reads-in-render, and no-side-effects-in-constructor
  • Loader methods (static loader()) are excluded from all checks — they run server-side only
  • Safe contexts (not flagged): connectedCallback, disconnectedCallback, firstUpdated, updated, event handlers
  • Guard patterns suppress violations. Supported patterns:
    • typeof <global> !== 'undefined' in if/ternary/logical-AND
    • globalThis.<global> truthiness in if/ternary/logical-AND
    • !isServer from lit in if/ternary/logical-AND
    • isServer in else/alternate branch (e.g., if (isServer) { ... } else { /* guarded */ })
  • Lit classes detected by: @customElement() decorator OR extending LitElement/AIUXElement/AIUXAppLayoutElement
  • Browser globals expanded to include: window, document, navigator, localStorage, sessionStorage, location, history, screen, alert, confirm, prompt
  • Crash vs mismatch: navigator exists in Node.js 21+ but returns different values (hydration mismatch), while all other globals are undefined in Node.js (crash). Error messages differentiate these two categories.
  • Crash vs leak (constructor rule): APIs like requestAnimationFrame, IntersectionObserver don't exist in Node.js (crash), while setTimeout, addEventListener exist but leak resources during SSR (leak). Error messages differentiate these two categories.

Usage

The plugin is configured in the root eslint.config.mjs via @servicenow/eslint-config-aiux/ssr. Rules only apply to:

  • components/*/src/**/*.js
  • applications/*/pages/**/*.js
  • applications/*/components/**/*.js
  • examples/*/pages/**/*.js

Running

# Lint all workspace packages
pnpm lint

# Lint from an application directory
cd applications/aict && npx aiux lint

# Lint with auto-fix (fixes module-level browser globals + built-in rules like semi/quotes)
cd applications/aict && npx aiux lint --fix

IDE Setup

VSCode

Install the recommended ESLint extension (dbaeumer.vscode-eslint). The workspace .vscode/settings.json enables auto-fix on save.

WebStorm

ESLint is built in. Go to Settings > Languages & Frameworks > JavaScript > Code Quality Tools > ESLint and select "Automatic ESLint configuration".

Testing

cd tools/eslint-plugin-aiux-ssr
npm test

Examples

Violation: Browser global in render

@customElement('my-page')
class MyPage extends AIUXElement {
  render() {
    const w = window.innerWidth; // Error: browser global in render
    return html`<div style="width: ${w}px"></div>`;
  }
}

Fix: Move to safe lifecycle

@customElement('my-page')
class MyPage extends AIUXElement {
  connectedCallback() {
    super.connectedCallback();
    this.width = window.innerWidth; // OK: connectedCallback is client-only
  }

  render() {
    return html`<div style="width: ${this.width || 0}px"></div>`;
  }
}

Why not globalThis in render?

globalThis.window?.innerWidth won't crash during SSR, but it returns undefined on the server while the client gets the real value. This causes a hydration mismatch (server HTML differs from client HTML), defeating the purpose of SSR. Use the context pattern (createContext() + context.set() in loader) for server-needed data, or connectedCallback + reactive property for client-only data.