CDN Integration (No Build Tools)
Learn how to integrate the Tiro.health Web SDK using CDN links for quick prototyping and simple applications without build tools.
Overview
The CDN approach provides:
- Zero build tools required
- Quick prototyping for demos and testing
- Simple HTML + JavaScript structure
- Global variable access to SDK components
- Fast iteration for learning and development
This approach is ideal for quick testing, demos, and educational purposes.
When to Use CDN
Good Use Cases
- Quick prototyping and proof of concepts
- Learning the SDK and experimenting
- Simple demos and presentations
- Internal tools with limited complexity
- Testing integration before full implementation
When to Use NPM Instead
For production applications, use NPM-based integration:
- Better tree-shaking and bundle optimization
- Type safety with TypeScript
- Dependency management with package.json
- Version control and reproducible builds
- Security updates through package manager
See React, Angular, or Vanilla JS integration guides for production-ready setups.
Quick Start
Minimal HTML Example
Create a single index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Patient Form - Tiro.health</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
background-color: #f5f5f5;
}
h1 {
color: #333;
}
#form-container {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
</style>
</head>
<body>
<h1>Patient Questionnaire</h1>
<div id="form-container"></div>
<!-- Load Web SDK from CDN -->
<script src="https://unpkg.com/@tiro-health/web-sdk@latest/dist/index.umd.js"></script>
<script>
// Access SDK through global TiroWebSDK variable
const { FormFiller } = window.TiroWebSDK
// Configuration
const config = {
questionnaire: 'your-questionnaire-id',
sdcEndpoint: {
resourceType: 'Endpoint',
address: 'https://your-sdc-backend.example.com/fhir'
},
onSubmit: (response) => {
console.log('Form submitted:', response)
alert('Form submitted successfully!')
},
onChange: (response) => {
console.log('Form changed:', response)
},
onError: (error) => {
console.error('Form error:', error)
alert('Error: ' + error.message)
}
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
const container = document.getElementById('form-container')
const filler = new FormFiller(config)
filler.mount(container)
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
filler.unmount()
})
})
</script>
</body>
</html>
Open this file directly in your browser - no build step required!
Complete Example
Full-Featured Application
Complete example with all components and visualization:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Patient Forms - Tiro.health</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f5f5f5;
color: #333;
line-height: 1.5;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
h1 {
font-size: 2rem;
}
h2 {
font-size: 1.5rem;
margin-bottom: 1rem;
}
.layout {
display: grid;
grid-template-columns: 300px 1fr;
gap: 2rem;
}
.sidebar {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.main-content {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.card {
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.loading,
.error {
text-align: center;
padding: 3rem;
}
.error {
color: #d32f2f;
}
.error button {
margin-top: 1rem;
padding: 0.5rem 1rem;
background-color: #1976d2;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
}
.error button:hover {
background-color: #1565c0;
}
#content {
display: none;
}
#content.show {
display: block;
}
@media (max-width: 768px) {
.layout {
grid-template-columns: 1fr;
}
.container {
padding: 1rem;
}
}
</style>
</head>
<body>
<div class="container">
<!-- Loading State -->
<div id="loading" class="loading">
<h2>Loading...</h2>
</div>
<!-- Error State -->
<div id="error" class="error" style="display: none;">
<h2>Error</h2>
<p id="error-message"></p>
<button onclick="retryInitialization()">Retry</button>
</div>
<!-- Main Content -->
<div id="content">
<header>
<h1>Patient Forms</h1>
<div id="toggle-container"></div>
</header>
<div class="layout">
<aside class="sidebar">
<div class="card">
<h2>Context</h2>
<div id="context-container"></div>
</div>
<div class="card">
<h2>Validation</h2>
<div id="validation-container"></div>
</div>
</aside>
<main class="main-content">
<div class="card">
<h2>Questionnaire</h2>
<div id="form-container"></div>
</div>
<div class="card">
<h2>Clinical Narrative</h2>
<div id="narrative-container"></div>
</div>
</main>
</div>
</div>
</div>
<!-- Load Web SDK from CDN -->
<script src="https://unpkg.com/@tiro-health/web-sdk@latest/dist/index.umd.js"></script>
<script>
// Global app state
const app = {
components: {},
config: {
sdcEndpoint: {
resourceType: 'Endpoint',
address: 'https://your-sdc-backend.example.com/fhir'
},
dataEndpoint: {
resourceType: 'Endpoint',
address: 'https://your-fhir-server.example.com/fhir'
},
questionnaireId: 'your-questionnaire-id',
patientId: 'your-patient-id',
visualize: true // Set to false for production
}
}
// Initialize application
function initializeApp() {
try {
showLoading()
// Get SDK components from global
const {
FormFiller,
LaunchContextProvider,
Narrative,
ValidationFeedback,
VisualizationToggle
} = window.TiroWebSDK
// Create FormFiller
app.components.filler = new FormFiller({
questionnaire: app.config.questionnaireId,
sdcEndpoint: app.config.sdcEndpoint,
visualize: app.config.visualize,
onSubmit: handleSubmit,
onChange: handleChange,
onError: handleError
})
// Create LaunchContextProvider
app.components.contextProvider = new LaunchContextProvider({
dataEndpoint: app.config.dataEndpoint,
filler: app.components.filler,
patientId: app.config.patientId
})
// Create Narrative
app.components.narrative = new Narrative({
filler: app.components.filler,
visualize: app.config.visualize
})
// Create ValidationFeedback
app.components.validation = new ValidationFeedback({
filler: app.components.filler,
visualize: app.config.visualize
})
// Create VisualizationToggle
app.components.toggle = new VisualizationToggle()
// Mount all components
app.components.contextProvider.mount(
document.getElementById('context-container')
)
app.components.filler.mount(
document.getElementById('form-container')
)
app.components.narrative.mount(
document.getElementById('narrative-container')
)
app.components.validation.mount(
document.getElementById('validation-container')
)
app.components.toggle.mount(
document.getElementById('toggle-container')
)
hideLoading()
} catch (error) {
console.error('Failed to initialize app:', error)
showError(error)
}
}
// Event handlers
function handleSubmit(response) {
console.log('Form submitted:', response)
alert('Form submitted successfully!')
}
function handleChange(response) {
console.log('Form updated:', response)
}
function handleError(error) {
console.error('Form error:', error)
showError(error)
}
// UI state management
function showLoading() {
document.getElementById('loading').style.display = 'block'
document.getElementById('content').classList.remove('show')
document.getElementById('error').style.display = 'none'
}
function hideLoading() {
document.getElementById('loading').style.display = 'none'
document.getElementById('content').classList.add('show')
}
function showError(error) {
document.getElementById('loading').style.display = 'none'
document.getElementById('content').classList.remove('show')
document.getElementById('error').style.display = 'block'
document.getElementById('error-message').textContent = error.message
}
function retryInitialization() {
document.getElementById('error').style.display = 'none'
initializeApp()
}
// Cleanup function
function cleanupApp() {
Object.values(app.components).forEach(component => {
component?.unmount()
})
app.components = {}
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', initializeApp)
// Cleanup on page unload
window.addEventListener('beforeunload', cleanupApp)
// Expose app to window for debugging
window.formApp = app
</script>
</body>
</html>
Configuration
CDN Options
Latest version (recommended for prototyping):
<script src="https://unpkg.com/@tiro-health/web-sdk@latest/dist/index.umd.js"></script>
Specific version (recommended for stability):
<script src="https://unpkg.com/@tiro-health/web-sdk@1.2.3/dist/index.umd.js"></script>
Alternative CDN (jsDelivr):
<script src="https://cdn.jsdelivr.net/npm/@tiro-health/web-sdk@latest/dist/index.umd.js"></script>
Global Variable Access
The SDK is available via the window.TiroWebSDK global:
// Access all components
const {
FormFiller,
LaunchContextProvider,
Narrative,
ValidationFeedback,
VisualizationToggle
} = window.TiroWebSDK
// Or access individually
const FormFiller = window.TiroWebSDK.FormFiller
Environment-Specific Configuration
Use different configurations for different environments:
const isDevelopment = window.location.hostname === 'localhost'
const config = {
sdcEndpoint: {
resourceType: 'Endpoint',
address: isDevelopment
? 'https://dev-sdc.example.com/fhir'
: 'https://prod-sdc.example.com/fhir'
},
visualize: isDevelopment
}
Best Practices
1. Version Pinning
For production, always pin to a specific version:
<!-- ✅ Good - pinned version -->
<script src="https://unpkg.com/@tiro-health/web-sdk@1.2.3/dist/index.umd.js"></script>
<!-- ❌ Bad - unpredictable updates -->
<script src="https://unpkg.com/@tiro-health/web-sdk@latest/dist/index.umd.js"></script>
2. Defer Script Loading
Use defer to avoid blocking page render:
<script defer src="https://unpkg.com/@tiro-health/web-sdk@1.2.3/dist/index.umd.js"></script>
3. Check Global Availability
Ensure SDK is loaded before using:
function initializeApp() {
if (!window.TiroWebSDK) {
console.error('Web SDK not loaded')
return
}
const { FormFiller } = window.TiroWebSDK
// ... proceed with initialization
}
4. Separate Configuration
Keep configuration separate from logic:
// Configuration object
const APP_CONFIG = {
sdcEndpoint: {
resourceType: 'Endpoint',
address: 'https://your-sdc-backend.example.com/fhir'
},
questionnaireId: 'phq-9',
visualize: false
}
// Use configuration
const filler = new FormFiller({
questionnaire: APP_CONFIG.questionnaireId,
sdcEndpoint: APP_CONFIG.sdcEndpoint,
visualize: APP_CONFIG.visualize
})
5. Error Boundaries
Always handle initialization errors:
try {
const filler = new FormFiller(config)
filler.mount(container)
} catch (error) {
console.error('Initialization failed:', error)
showErrorMessage('Failed to load form. Please refresh the page.')
}
Limitations
Bundle Size
CDN builds include the entire library:
- Larger file size compared to tree-shaken NPM builds
- No code splitting or lazy loading
- All dependencies included
Impact: Slower initial page load, especially on slow connections.
No TypeScript Support
CDN approach provides no type safety:
- No autocompletion in editors
- No compile-time error checking
- Runtime errors only
Mitigation: Use JSDoc comments for basic type hints:
/**
* @param {Object} config
* @param {string} config.questionnaire
* @param {Object} config.sdcEndpoint
*/
function createForm(config) {
const filler = new FormFiller(config)
return filler
}
Dependency Management
No package.json for version control:
- Manual version updates required
- No dependency lock files
- Harder to track which version is in use
Mitigation: Document the version in comments:
<!-- Tiro.health Web SDK v1.2.3 - Updated 2024-01-15 -->
<script src="https://unpkg.com/@tiro-health/web-sdk@1.2.3/dist/index.umd.js"></script>
Security Updates
Manual updates required for security patches:
- No automated security alerts
- Must manually monitor for updates
- Higher risk of running outdated code
Mitigation: Set a reminder to check for updates monthly.
Production Considerations
When CDN is Acceptable
- Internal tools with controlled access
- Low-traffic applications
- Non-critical prototypes
- Educational demos
When to Migrate to NPM
Consider migrating when:
- Application becomes production-critical
- Performance optimization is needed
- Type safety becomes important
- Team size grows (collaboration benefits)
- Security requirements increase
Migration Path
When ready to migrate:
- Set up build tools: Install Node.js and a bundler (Vite, Webpack)
- Install via NPM:
npm install @tiro-health/web-sdk - Convert to modules: Use ES6 imports instead of global variable
- Add TypeScript: Optional but recommended for type safety
- Test thoroughly: Ensure all functionality works with new setup
See Vanilla JS Integration for a detailed migration guide.
Next Steps
- Explore the FormFiller API Reference for advanced configuration
- Learn about LaunchContextProvider for patient context
- Ready for production? Check out NPM-based integrations
- View live examples in the tutorial repository
For questions or support, please contact the Tiro.health team.