A TypeScript-based template for building custom Webflow site extensions using the Sygnal Site Engine (SSE2) framework.
- TypeScript - Type-safe development with modern ES6+ features
- SCSS Support - Write maintainable styles with variables, nesting, and mixins
- Fast Build System - TypeScript type checking + esbuild bundler, Dart Sass for SCSS
- Watch Mode - Auto-rebuild on file changes during development
- Component System - Reusable, attribute-based components with automatic context detection
- Route Management - Page-based architecture with automatic Webflow context
- Source Maps - Debug TypeScript and SCSS in browser DevTools
- Development Server - Local testing with hot reload
npm installUpdate package.json:
- Set
nameto your project name - Set
versionappropriately
Update src/index.ts:
- Edit
SITE_NAMEconstant - Edit
VERSIONconstant
npm run build # Development build with type checking
npm run build:prod # Production build with minificationThis performs type checking, bundles all TypeScript into a single dist/index.js, and compiles SCSS files to dist/.
npm run watch # Start watch mode for auto-rebuild
npm run serve # Start local dev server (http://127.0.0.1:3000)Run both commands in separate terminals for the best development experience.
sse-template/
├── src/
│ ├── index.ts # Main entry point
│ ├── site.ts # Site-level module
│ ├── routes.ts # Route & component imports
│ ├── types.ts # Template-specific types
│ ├── version.ts # Version constant
│ ├── site.scss # Global styles
│ ├── pages/
│ │ ├── home.ts # Home page module
│ │ ├── blog.ts # Blog page (wildcard example)
│ │ └── about.ts # About page (multi-route example)
│ └── components/
│ ├── test.ts # Test component
│ └── example.ts # Example component
├── dist/ # Compiled output (git-ignored)
├── build.js # Build script (tsc + esbuild + sass)
├── generate-imports.js # Optional: Auto-generate imports
├── package.json # Dependencies & scripts
├── tsconfig.json # TypeScript config
├── MIGRATION.md # Migration guide for v0.3.0+
└── README.md # This file
| Command | Description |
|---|---|
npm run build |
Development build with type checking + bundling |
npm run build:prod |
Production build with minification (for deployment) |
npm run watch |
Watch mode - auto-rebuild TypeScript and SCSS |
npm run typecheck |
Run TypeScript type checking only |
npm run serve |
Start local dev server on port 3000 |
npm run format |
Format TypeScript and SCSS with Prettier |
npm run clean |
Remove dist directory |
- Create
.scssfiles anywhere in thesrc/directory - The build process automatically compiles them to
dist/with the same folder structure - Output is compressed (minified) for production
Example:
src/site.scss → dist/site.css
src/pages/home.scss → dist/pages/home.css
@use "sass:color";
@use "sass:math";
// Variables
$primary: #007bff;
$spacing: 1rem;
// Nesting
.component {
padding: $spacing;
&:hover {
background: color.scale($primary, $lightness: 80%);
}
}
// Mixins
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}The SSE framework can auto-load CSS files. See src/site.ts for an example:
Page.loadEngineCSS("site.css");Pages are automatically discovered using the @page decorator. Just create and import!
// src/pages/about.ts
import { PageBase, page } from '@sygnal/sse-core';
@page('/about') // ← Decorator auto-registers this route!
export class AboutPage extends PageBase {
protected onPrepare(): void {
// Synchronous setup - called during <head> load
// Access Webflow page context automatically
console.log('Page ID:', this.pageInfo.pageId);
console.log('Collection:', this.pageInfo.collectionId);
}
protected async onLoad(): Promise<void> {
// Asynchronous execution - called after DOM ready
console.log('About page loaded');
console.log('Current path:', this.pageInfo.path);
}
}// ============================================================
// PAGES - Import all pages to trigger @page decorator
// ============================================================
import "./pages/home";
import "./pages/about"; // ← Add your import here!The route is automatically registered. No manual route mapping needed!
Note: All pages and components are imported in routes.ts - this is the central registry location.
When you extend PageBase, your page automatically gets access to this.pageInfo:
protected async onLoad(): Promise<void> {
// All available automatically:
console.log(this.pageInfo.path); // Current path
console.log(this.pageInfo.url); // Full URL
console.log(this.pageInfo.pageId); // Webflow page ID
console.log(this.pageInfo.siteId); // Webflow site ID
console.log(this.pageInfo.collectionId); // CMS collection ID (if applicable)
console.log(this.pageInfo.itemId); // CMS item ID (if applicable)
console.log(this.pageInfo.itemSlug); // CMS item slug (if applicable)
console.log(this.pageInfo.queryParams); // URLSearchParams object
console.log(this.pageInfo.hash); // URL hash
console.log(this.pageInfo.domain); // Webflow domain
console.log(this.pageInfo.lang); // Page language
}You can use multiple @page decorators on a single class to handle multiple routes:
// src/pages/about.ts
import { PageBase, page } from '@sygnal/sse-core';
@page('/about') // All three routes
@page('/about-us') // use the same
@page('/team') // page class!
export class AboutPage extends PageBase {
protected onPrepare(): void {
console.log('Preparing about page...');
}
protected async onLoad(): Promise<void> {
// Check which route was accessed via pageInfo
const currentPath = this.pageInfo.path;
if (currentPath === '/team') {
// Show team-specific content
}
}
}Use cases:
- Route aliases (
/shop,/store,/products) - Localized URLs (
/en/contact,/es/contacto) - Legacy URL support
- Similar pages with shared logic
Wildcard routes are supported using * for dynamic paths:
// src/pages/blog.ts
import { PageBase, page } from '@sygnal/sse-core';
@page('/blog/*') // ← Matches /blog/post-1, /blog/category/tech, etc.
export class BlogPage extends PageBase {
protected onPrepare(): void {
console.log('Blog page preparing...');
}
protected async onLoad(): Promise<void> {
// Access the full path via pageInfo
const fullPath = this.pageInfo.path;
const slug = fullPath.replace('/blog/', '');
console.log('Blog slug:', slug);
// If this is a Webflow CMS collection page
if (this.pageInfo.itemSlug) {
console.log('CMS Item Slug:', this.pageInfo.itemSlug);
console.log('Collection ID:', this.pageInfo.collectionId);
}
}
}Routes are matched in the order they're registered, with exact matches taking precedence over wildcards.
Components are automatically discovered using the @component decorator and extend ComponentBase for automatic element context.
// src/components/my-component.ts
import { ComponentBase, component, PageBase } from '@sygnal/sse-core';
@component('my-component') // ← Decorator auto-registers this component!
export class MyComponent extends ComponentBase {
protected onPrepare(): void {
// Synchronous setup - runs before DOM ready
// Access element and context automatically
console.log('Component name:', this.context.name);
console.log('Component ID:', this.context.id);
console.log('Data attributes:', this.context.dataAttributes);
}
protected async onLoad(): Promise<void> {
// Asynchronous execution - runs after DOMContentLoaded
console.log('MyComponent initialized on:', this.element);
// Access current page info via singleton
const page = PageBase.getCurrentPage();
if (page) {
console.log('Component on page:', page.pageInfo.pageId);
console.log('Collection item:', page.pageInfo.itemSlug);
}
// Add event listeners
this.element.addEventListener('click', () => {
console.log('Component clicked!');
});
}
}// ============================================================
// COMPONENTS - Import all components to trigger @component decorator
// ============================================================
import "./components/test";
import "./components/my-component"; // ← Add your import here!<div data-component="my-component" data-component-id="nav-main">
<!-- Component content -->
</div>The component is automatically discovered and instantiated!
When you extend ComponentBase, your component automatically gets:
this.element- The HTMLElement the component is bound tothis.context.name- Component name fromdata-componentattributethis.context.id- Component ID fromdata-component-idattribute (if present)this.context.dataAttributes- All data-* attributes on the element
Components can access the current page via the singleton pattern:
protected async onLoad(): Promise<void> {
const page = PageBase.getCurrentPage();
if (page) {
// Access all page info
console.log('Page ID:', page.pageInfo.pageId);
console.log('Collection:', page.pageInfo.collectionId);
console.log('Item Slug:', page.pageInfo.itemSlug);
// Component logic based on page context
if (page.pageInfo.collectionId === 'products') {
// Product-specific component behavior
}
}
}- Auto-discovery: Components are automatically found and initialized
- Type-safe: Full TypeScript support with strict typing
- Component Manager: All instances registered in
window.componentManager - Error handling: Graceful error handling with console warnings
- Lifecycle hooks:
onPrepare()andonLoad()methods for different initialization phases - Automatic context: Element and data attributes automatically detected
// Get all instances of a specific component type
const myComponents = window.componentManager.getComponentsByType<MyComponent>('my-component');
// Get all registered component types
const types = window.componentManager.getComponentTypes();
// Get total count
const total = window.componentManager.getTotalCount();Add to your Webflow site's Custom Code section (before </head> or before </body>):
Replace REPO with your GitHub repository name and update the version number:
<!-- Site Engine -->
<script
src="https://cdn.jsdelivr.net/gh/sygnaltech/REPO@0.1.0/dist/index.js"
dev-src="http://127.0.0.1:3000/dist/index.js"
></script><!-- Site Engine -->
<script src="http://127.0.0.1:3000/dist/index.js"></script>The SSE framework supports query parameters for switching modes:
?engine.mode=dev- Force development mode?engine.mode=prod- Force production mode
The template uses a hybrid build approach combining TypeScript's type checking with esbuild's fast bundling.
- Type Check - TypeScript compiler validates all types (
tsc --noEmit) - Bundle - esbuild bundles
src/index.tsand all imports into singledist/index.js - SCSS Compile - Dart Sass compiles all
.scssfiles to.css
Development Build (npm run build):
- Full type checking
- Unminified output for easier debugging
- Source maps included
- ~15KB bundle size
Production Build (npm run build:prod):
- Full type checking
- Minified output
- Source maps included
- ~6.8KB bundle size (smaller, faster loading)
npm run watch- TypeScript auto-rebuilds on file changes (no type check in watch for speed)
- SCSS auto-compiles on file changes
- Runs continuously until stopped
- tsc provides comprehensive type checking
- esbuild provides extremely fast bundling
- Single bundle (
dist/index.js) loads all dependencies - No duplicate code (all imports bundled once)
- Best of both worlds: safety + speed
The SSE framework uses a two-phase initialization:
-
onPrepare()- Runs synchronously during<head>load, before DOM is ready- Good for: Configuration, early initialization, reading data attributes
-
onLoad()- Runs asynchronously afterDOMContentLoaded- Good for: DOM manipulation, event binding, API calls
All pages extend PageBase and components extend ComponentBase:
// Pages get automatic Webflow context
export abstract class PageBase {
protected pageInfo: WebflowPageInfo; // Auto-populated
protected abstract onPrepare(): void;
protected abstract onLoad(): Promise<void>;
}
// Components get automatic element context
export abstract class ComponentBase {
protected element: HTMLElement; // Auto-provided
protected context: ComponentContext; // Auto-populated
protected abstract onPrepare(): void;
protected abstract onLoad(): Promise<void>;
}Components are automatically discovered via the data-component attribute and instantiated by the framework.
- @sygnal/sse-core - SSE framework core with PageBase and ComponentBase
- gsap - Animation library (optional)
- js-cookie - Cookie handling utility
- esbuild - Fast JavaScript bundler
- sass - Dart Sass compiler
- typescript - TypeScript compiler
- prettier - Code formatter
- serve - Development server
Test Project: https://webflow.com/dashboard/sites/sygnal-site-engine/general
See package.json for license information.