"Type-safe template string literals"
A type-safe string templating library for TypeScript that provides strongly-typed variable references with support for nested paths. Create template strings with compile-time type checking and automatic context type inference.
Typelit allows you to write template strings that are both type-safe and easy to read. Variables are referenced using a familiar template literal syntax while maintaining full type information about the required context object structure.
Install using npm:
npm install typelit
Or using yarn:
yarn add typelit
The library is available in multiple module formats to support different environments:
- CommonJS (Node.js):
const typelit = require('typelit');
const greeting = typelit`Hello ${typelit.string('name')}!`;
- ESM (Modern JavaScript):
import typelit from 'typelit';
const greeting = typelit`Hello ${typelit.string('name')}!`;
- UMD (Browser):
<script src="https://unpkg.com/typelit"></script>
<script>
const greeting = typelit`Hello ${typelit.string('name')}!`;
</script>
Each format provides identical functionality - pick the one that best suits your environment. The library includes TypeScript type definitions that work automatically with all formats.
First, import the library:
import typelit from 'typelit';
Let's start with a simple greeting template:
// Create a simple template with one variable
const greeting = typelit`Hello ${typelit.string('name')}!`;
// Use the template with a context object
const result = greeting({ name: 'Alice' }); // "Hello Alice!"
You can also create templates with nested variable paths:
// Create a template with nested variables
const template = typelit`Hello ${typelit.string('user', 'name')}! You are ${typelit.number('user', 'age')} years old.`;
// TypeScript knows exactly what shape the context object needs to have
const result = template({
user: {
name: 'Alice',
age: 30,
},
}); // "Hello Alice! You are 30 years old."
// This would cause a type error:
template({
user: {
name: 'Bob',
// Error: missing required property 'age'
},
});
The typelit
tag function creates template functions that evaluate a string
template using values from a context object. The template function takes a
context object as input and returns the evaluated string with all variables
replaced by their values.
When creating a template, TypeScript infers the required shape of the context object from the variables used in the template.
// Example showing type inference
const welcome = typelit`Welcome back, ${typelit.string('username')}!`;
// TypeScript infers that the context must have a 'username' property
welcome({ username: 'alice' }); // OK
welcome({ user: 'bob' }); // Type error: missing username
welcome({ username: 123 }); // Type error: number is not assignable to string
The typelit
function serves a dual purpose:
- As a template tag function that creates typed template functions
- As a namespace that provides variable creators (
string
,number
, etc.)
Template functions created with typelit
offer:
- Type Safety: All variables are fully typed, ensuring you can't pass the wrong type of value.
- Path Inference: TypeScript automatically infers the required structure of your context object.
- Composition: Templates can be composed to build more complex strings:
const firstName = typelit`${typelit.string('user', 'firstName')}`;
const lastName = typelit`${typelit.string('user', 'lastName')}`;
const fullName = typelit`${firstName} ${lastName}`;
// Both templates require the same context structure
firstName({ user: { firstName: 'John' } });
fullName({ user: { firstName: 'John', lastName: 'Doe' } });
- Compile-Time Validation: TypeScript catches errors before runtime:
- Missing or misspelled variable paths
- Incorrect value types
- Missing context properties
- Invalid template syntax
Typelit provides built-in variable creators for common data types. These are used to define variables in your templates with type-safe paths.
Use typelit.string()
to create string variables in your templates:
// Simple string variable
const greeting = typelit`Hello ${typelit.string('name')}!`;
greeting({ name: 'Alice' }); // "Hello Alice!"
// Nested string variable
const userEmail = typelit`Contact: ${typelit.string('user', 'email')}`;
userEmail({ user: { email: '[email protected]' } }); // "Contact: [email protected]"
Use typelit.number()
to create number variables:
// Simple number variable
const age = typelit`Age: ${typelit.number('age')} years old`;
age({ age: 25 }); // "Age: 25 years old"
// Nested number variable
const score = typelit`Score: ${typelit.number('game', 'score')} points`;
score({ game: { score: 100 } }); // "Score: 100 points"
Use typelit.boolean()
to create boolean variables:
// Simple boolean variable
const status = typelit`Status: ${typelit.boolean('isActive')}`;
status({ isActive: true }); // "Status: true"
// Nested boolean variable
const accountStatus = typelit`Account active: ${typelit.boolean('user', 'account', 'enabled')}`;
accountStatus({ user: { account: { enabled: false } } }); // "Account active: false"
Use typelit.bigint()
to create bigint variables:
// Simple bigint variable
const id = typelit`ID: ${typelit.bigint('userId')}`;
id({ userId: 9007199254740991n }); // "ID: 9007199254740991"
// Nested bigint variable
const transactionId = typelit`Transaction: ${typelit.bigint('payment', 'transactionId')}`;
transactionId({ payment: { transactionId: 123456789n } }); // "Transaction: 123456789"
Use typelit.date()
to create Date variables that automatically convert
JavaScript Date objects to strings:
// Simple date variable
const eventDate = typelit`Event date: ${typelit.date('date')}`;
eventDate({ date: new Date('2024-12-25') }); // "Event date: Wed Dec 25 2024 00:00:00 GMT+0000"
// Nested date variable
const appointmentTime = typelit`Appointment scheduled for: ${typelit.date('calendar', 'appointment')}`;
appointmentTime({
calendar: { appointment: new Date('2024-12-25T15:30:00Z') },
}); // "Appointment scheduled for: Wed Dec 25 2024 15:30:00 GMT+0000"
Use typelit.json()
to create variables that automatically stringify any value
to JSON with proper formatting:
// Simple JSON variable
const data = typelit`Data: ${typelit.json('config')}`;
data({ config: { enabled: true, count: 42 } });
// "Data: {
// "enabled": true,
// "count": 42
// }"
// Nested JSON variable
const userProfile = typelit`Profile: ${typelit.json('user', 'profile')}`;
userProfile({
user: {
profile: {
name: 'Alice',
preferences: {
theme: 'dark',
notifications: true,
},
},
},
});
// "Profile: {
// "name": "Alice",
// "preferences": {
// "theme": "dark",
// "notifications": true
// }
// }"
You can create your own variable creators for any type using the createType
function. Here's a basic example creating a variable creator for JavaScript's
Date
type:
import { createType } from 'typelit';
// Create a variable creator for Date
const typelitDate = createType<Date>();
// Use it in a template
const template = typelit`Event starts at ${typelitDate('event', 'startTime')}`;
// The context object requires a Date instance
const result = template({
event: {
startTime: new Date('2024-12-25T10:00:00Z'),
},
}); // "Event starts at Wed Dec 25 2024 10:00:00 GMT+0000"
// Type error: string is not assignable to Date
template({
event: {
startTime: '2024-12-25', // Error!
},
});
Like built-in variable creators, custom ones:
- Support nested paths
- Provide full type inference for the context object
- Enforce the correct type at compile time
The createType
function accepts an optional options
. The options.stringify
parameter is a function that takes a value of your type and returns a string.
Here are some examples of customizing string conversion:
// Custom date formatting
const typelitDate = createType<Date>({
stringify: (date) =>
date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
year: 'numeric',
}),
});
const event = typelit`Event: ${typelitDate('date')}`;
event({ date: new Date('2024-12-25') }); // "Event: Wed, Dec 25, 2024"
// Currency formatting
const typelitPrice = createType<number>({
stringify: (price) =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(price),
});
const price = typelit`Total: ${typelitPrice('amount')}`;
price({ amount: 42.99 }); // "Total: $42.99"
// Custom object formatting
type User = { id: number; name: string };
const typelitUser = createType<User>({
stringify: (user) => `#${user.id} ${user.name}`,
});
const user = typelit`Created by: ${typelitUser('author')}`;
user({ author: { id: 123, name: 'Alice' } }); // "Created by: #123 Alice"
Without a custom stringify
function, createType
uses JavaScript's built-in
String()
function to convert values to strings. This is equivalent to:
createType<T>({ stringify: String });
You might want to provide a custom stringify
function when:
- Formatting dates in a specific way
- Formatting numbers (currency, percentages, fixed decimal places)
- Creating a custom string representation for objects
- Adding prefixes, suffixes, or other decorations to values
- Internationalizing or localizing output
We welcome contributions! Please see our Contributing Guide for details on how to set up the development environment and our contribution process.
Copyright 2024 Charles Francoise
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.