--- title: "Best Practices - BYO" slug: "best-practices-byo" updated: 2026-10-07T18:50:00Z published: 2026-10-07T18:50:00Z canonical: "docs.unqork.io/best-practices-byo" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.unqork.io/llms.txt > Use this file to discover all available pages before exploring further. # Best Practices - BYO Follow the best practices below to build secure, high-performance, and maintainable BYO components. ## Performance ### Bundle Size Optimization **Minimize Bundle Size** Every BYO asset adds to the page load, so keep bundles small: ```typescript // ❌ Bad - imports entire library import * as _ from 'lodash' // ✅ Good - imports what's needed import debounce from 'lodash/debounce' import throttle from 'lodash/throttle' ``` **Lazy Load Heavy Dependencies** Load large libraries when needed: ```typescript export class MyChart extends HTMLElement {  private chartLib: any = null    async loadChartLibrary() {    if (!this.chartLib) {      // load when actually rendering a chart      this.chartLib = await import('d3')    }    return this.chartLib  }    async renderChart() {    const d3 = await this.loadChartLibrary()    // use d3...  } } ``` **Use Code Splitting** Split components into separate bundles: ```typescript // src/entry.ts export const myButton = {  view: async () => (await import('./MyButton')).MyButton,  model: async () => (await import('./MyButton.model')).MyButtonModel, } export const myChart = {  view: async () => (await import('./MyChart')).MyChart,  model: async () => (await import('./MyChart.model')).MyChartModel, } ``` The page loads the components it uses. **Monitor Bundle Size** Check the production bundle size: ```bash npm run build ls -lh dist/*.js # Target: < 100KB per component (minified + gzipped) ``` ### Limit the Number of BYO Assets Every BYO asset adds overhead to module loading. Follow the guidelines below: - Use BYO when necessary. - Combine related components into asset. - Avoid creating many single-purpose components. - Reuse components across applications. **Example**: ```plaintext ❌ Bad - separate assets - text-input.tar.gz - number-input.tar.gz - email-input.tar.gz ✅ Good - combined asset - form-inputs.tar.gz (contains all three) ``` ### Limit How Often Operations Run **Rate-limit submission updates (debouncing)**: ```typescript import debounce from 'lodash/debounce' export class MyInput extends HTMLElement {  private updateSubmission = debounce((value: string) => {    this.onSubmissionUpdate?.(value)  }, 300)    handleInput(e: Event) {    const value = (e.target as HTMLInputElement).value    this.updateSubmission(value)  } } ``` **Limit how often events fire (throttling)**: ```typescript import throttle from 'lodash/throttle' export class MySlider extends HTMLElement {  private handleSlide = throttle((value: number) => {    this.onValueChanged?.(value)  }, 100) } ``` ### Batch DOM Reads and Writes Batch DOM reads before writes to avoid forcing the browser to recalculate layout on every change: ```typescript // ❌ Bad - interleaved reads and writes (causes reflow) element.style.width = '100px' const height = element.offsetHeight  // forces reflow element.style.height = `${height}px` // ✅ Good - batch reads, then writes const height = element.offsetHeight requestAnimationFrame(() => {  element.style.width = '100px'  element.style.height = `${height}px` }) ``` ## Security ### Prevent Cross-Site Scripting (XSS) Attacks **Never use** `innerHTML` **with user input**: ```typescript // ❌ Bad - vulnerable to XSS set label(value: string) {  this.querySelector('.label')!.innerHTML = value } // ✅ Good - escapes HTML set label(value: string) {  this.querySelector('.label')!.textContent = value } ``` **Sanitize HTML if you must render it**: ```typescript import DOMPurify from 'dompurify' set richText(value: string) {  const clean = DOMPurify.sanitize(value)  this.querySelector('.content')!.innerHTML = clean } ``` ### Validate Input **Validate all prop values**: ```typescript set count(value: unknown) {  // Validate type  if (typeof value !== 'number') {    console.warn('Invalid count value:', value)    return  }    // Validate range  if (value < 0 || value > 1000) {    console.warn('Count out of range:', value)    return  }    this._count = value  this.render() } ``` ### Secure Third-Party Libraries **Audit dependencies**: ```bash # Check for known vulnerabilities npm audit # Fix vulnerabilities npm audit fix ``` **Keep libraries updated**: ```bash # Check for outdated packages npm outdated # Update dependencies npm update ``` **Review library code**: - Use well-maintained libraries with active communities. - Check GitHub issues for security concerns. - Review license compatibility. - Avoid deprecated libraries. ### Don't Expose Sensitive Data **Never log sensitive data**: ```typescript // ❌ Bad console.log('User data:', submissionData) // ✅ Good console.log('Submission updated') ``` **Don't store** secrets **in code**: ```typescript // ❌ Bad - hardcoded secret in bundle const API_KEY = 'sk_live_1234567890' ``` Never store credentials, API keys, or tokens directly in component code. The compiled bundle is downloadable by anyone with access to the page. ## Maintainability ### Validate Use Cases First Before creating a BYO component, complete the following steps: 1. Check if built-in components meet your needs. 2. Explore logic components for business logic. 3. Consider if CSS styling is sufficient. 4. [Validate that BYO is necessary](/unqorkai/docs/bring-your-own). Validating first avoids duplicating platform functionality and reduces maintenance overhead. ### Document Your Components **Add JSDoc comments**: ```typescript /** * Custom task list component. * * Displays a list of tasks with checkboxes and delete buttons. * * @example * ```typescript * element.items = [ *   { id: '1', title: 'Task 1', completed: false } * ] * ``` */ export class TaskList extends HTMLElement {  /**   * Array of task items to display.   */  set items(value: Task[]) {    this._items = value    this.render()  } } ``` **Create a README**: ```markdown # Task List Component Custom component for displaying and managing tasks. ## Props - `label` (string) - List heading - `items` (Task[]) - Array of tasks - `allowAdd` (boolean) - Show add button - `allowDelete` (boolean) - Show delete buttons ## Events - `onTaskToggled` - Fires when task is checked/unchecked - `onTaskAdded` - Fires when add button is clicked - `onTaskDeleted` - Fires when delete button is clicked ## Usage See examples in [Module Builder Documentation](#). ``` ### Version Your Components **Use semantic versioning**: ```json {  "version": "1.2.3" } ``` - **Major (1.x.x)**: Breaking changes. - **Minor (x.2.x)**: New backward-compatible features. - **Patch (x.x.3)**: Bug fixes. **Document breaking changes**: ```markdown # CHANGELOG.md ## [2.0.0] - 2026-05-01 ### Breaking Changes - Renamed `onItemClick` to `onItemSelected` - Changed `items` prop structure (now requires `id` field) ### Migration Guide Update event handlers from `onItemClick` to `onItemSelected`. Add `id` field to all items: `{ id: '1', ...rest }`. ``` ### Centralize Updates **One asset, many uses**: When you update a BYO asset, changes reflect everywhere it's used. Plan updates carefully: 1. Test changes thoroughly in staging. 2. Communicate updates to Creators. 3. Provide migration guides for breaking changes. 4. Consider backward compatibility. ### Plan for Library Updates **Track third-party library versions**: ```json {  "dependencies": {    "d3": "7.8.5",    "lodash": "4.17.21"  } } ``` **Set up dependency monitoring**: - Enable [Dependabot](https://docs.github.com/en/code-security/dependabot) or [Renovate](https://docs.renovatebot.com/). - Subscribe to library release notes. - Test updates in staging before production. ## Accessibility ### Semantic HTML **Use proper HTML elements**: ```typescript // ❌ Bad - div button this.innerHTML = `  
Click me
` // ✅ Good - semantic button this.innerHTML = `   ` ``` ### ARIA Labels **Add labels for screen readers**: ```typescript this.innerHTML = `   ` ``` ### Keyboard Navigation **Support keyboard interactions**: ```typescript connectedCallback() {  this.addEventListener('keydown', (e) => {    if (e.key === 'Enter' || e.key === ' ') {      e.preventDefault()      this.handleClick()    }  }) } ``` **Make elements focusable**: ```typescript this.innerHTML = `  
   ...  
` ``` ### Color Contrast **Ensure sufficient contrast**: ```css /* ❌ Bad - low contrast (2.5:1) */ .label {  color: #999;  background: #fff; } /* ✅ Good - high contrast (7:1) */ .label {  color: #333;  background: #fff; } ``` **Test with tools**: - [Chrome DevTools Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/). - [axe DevTools](https://www.deque.com/axe/devtools/). - [WAVE browser extension](https://wave.webaim.org/extension/). ### Focus Management **Visible focus indicators**: ```css button:focus-visible {  outline: 2px solid #0066cc;  outline-offset: 2px; } ``` **Don't remove focus styles**: ```css /* ❌ Bad */ *:focus {  outline: none; } /* ✅ Good - custom but visible */ *:focus-visible {  outline: 2px solid currentColor; } ``` ## Error Handling ### Graceful Degradation **Handle missing callbacks**: ```typescript handleClick() {  // Always use optional chaining  this.onButtonClicked?.({ label: this._label }) } ``` **Validate properties**: ```typescript set items(value: unknown) {  if (!Array.isArray(value)) {    console.warn('items must be an array')    this._items = []    return  }    this._items = value  this.render() } ``` ### Error Boundaries **Wrap error-prone code**: ```typescript async loadData() {  try {    const data = await import('./heavy-library.js')    return data  } catch (error) {    console.error('Failed to load library:', error)    this.showError('Failed to load component')    return null  } } ``` ### User-Friendly Messages **Show helpful errors**: ```typescript // ❌ Bad throw new Error('Invalid input') // ✅ Good console.error('Invalid items prop: expected array, got', typeof value) this.showError('Unable to display items. Please contact support.') ``` ## Testing ### Test Across Browsers **Minimum browser support**: - Chrome, Firefox, Safari, and Edge: two most recent versions of each. **Use cross-browser testing**: - [BrowserStack](https://www.browserstack.com). - [Sauce Labs](https://saucelabs.com). - Local testing with different browsers. ### Test Edge Cases **Empty states**: ```typescript it('handles empty items array', () => {  element.items = []  expect(element.querySelector('.task')).toBeNull() }) ``` **Invalid input**: ```typescript it('handles invalid items gracefully', () => {  element.items = null as any  expect(() => element.items = null).not.toThrow() }) ``` **Large datasets**: ```typescript it('handles 1000 items', () => {  const items = Array.from({ length: 1000 }, (_, i) => ({    id: String(i),    title: `Task ${i}`,    completed: false,  }))    element.items = items  expect(element.querySelectorAll('.task')).toHaveLength(1000) }) ``` ### Automated Testing **Set up automated testing**: ```yaml # .github/workflows/test.yml name: Test on: [push, pull_request] jobs:  test:    runs-on: ubuntu-latest    steps:      - uses: actions/checkout@v4      - run: npm ci      - run: npm test      - run: npm run lint ``` ## Deployment ### Pre-Deployment Checklist Before uploading to Unqork, verify the following: - [ ] All tests pass (`npm test`). - [ ] Linting passes (`npm run lint`). - [ ] Build succeeds (`npm run build:prod`). - [ ] Bundle size is acceptable (< 100KB gzipped). - [ ] Tested in dev harness (`npm run preview`). - [ ] Documented usage and properties. - [ ] Version updated — `npm run build:prod` bumps the patch version automatically; bump major or minor manually in `package.json` for breaking or feature changes. - [ ] CHANGELOG updated. ### Staging First **Deploy to staging before production**: 1. Upload to the staging environment. 2. Test in real modules. 3. Verify integration with other components. 4. Get stakeholder approval. 5. Deploy to production. ### Monitor After Deployment **Check for issues**: - Browser console errors. - Performance regressions. - User feedback. - Error tracking tools like [Sentry](https://sentry.io). ## Common Pitfalls ### Avoid DOM Manipulation Outside the Component **Don't modify the outer DOM**: ```typescript // ❌ Bad - manipulates platform DOM document.querySelector('.unqork-module').classList.add('custom') // ✅ Good - modifies own DOM this.querySelector('.my-component').classList.add('active') ``` ### Don't Block the Main Thread **Avoid heavy operations**: ```typescript // ❌ Bad - blocks UI function processLargeDataset(items: Item[]) {  return items.map(complexCalculation) } // ✅ Good - async with batching async function processLargeDataset(items: Item[]) {  const batchSize = 100  const results = []    for (let i = 0; i < items.length; i += batchSize) {    const batch = items.slice(i, i + batchSize)    results.push(...batch.map(complexCalculation))        // Yield to browser    await new Promise(resolve => setTimeout(resolve, 0))  }    return results } ``` ### Clean Up Resources **Always remove event listeners in** `disconnectedCallback()`: Without cleanup, event listeners keep references to the component instance, causing memory leaks as the browser cannot reclaim unused memory. ```typescript private handleIncrement = () => {  this._count++  this.render() } connectedCallback() {  // Commands are namespaced as :  this.addEventListener('myComponent:increment', this.handleIncrement) } disconnectedCallback() {  // Always clean up event listeners  this.removeEventListener('myComponent:increment', this.handleIncrement) } ``` **Framework-specific cleanup**: **React** ```typescript useEffect(() => {  const host = containerRef.current?.getRootNode()?.host  if (!host) return  const handleIncrement = () => {    setCount(c => c + 1)  }  host.addEventListener('myComponent:increment', handleIncrement)    // Cleanup function automatically called on unmount  return () => {    host.removeEventListener('myComponent:increment', handleIncrement)  } }, []) ``` **Vue** ```typescript ``` **Angular** ```typescript export class MyComponent implements {  private hostElement: HTMLElement | null = null    ngAfterViewInit() {    this.hostElement = this.elementRef.nativeElement.getRootNode()?.host    if (!this.hostElement) return    this.hostElement.addEventListener('myComponent:increment', this.handleIncrement)  }  ngOnDestroy() {    if (this.hostElement) {      this.hostElement.removeEventListener('myComponent:increment', this.handleIncrement)    }  }  private handleIncrement = () => {    this.count++  } } ``` **Lit** ```typescript export class MyComponent extends LitElement {  private handleIncrement = () => {    this._count++  }  override connectedCallback() {    super.connectedCallback()    this.addEventListener('myComponent:increment', this.handleIncrement)  }  override disconnectedCallback() {    super.disconnectedCallback()    this.removeEventListener('myComponent:increment', this.handleIncrement)  } } ``` **Cancel async operations**: ```typescript private abortController: AbortController | null = null async fetchData() {  this.abortController = new AbortController()    try {    const response = await fetch(url, {      signal: this.abortController.signal    })    return response.json()  } catch (error) {    if (error.name === 'AbortError') {      console.log('Fetch cancelled')    }  } } disconnectedCallback() {  this.abortController?.abort() } ``` ## Summary **Key Takeaways**: - Keep bundles small by splitting your bundle and loading code on demand. - Validate use cases before creating BYO components. - Prevent cross-site scripting (XSS) attacks and validate all inputs. - Make components accessible through ARIA labels, keyboard navigation, and color contrast. - Test thoroughly across browsers and edge cases. - Document usage and maintain version history. - Monitor performance and errors after deployment. **Next Steps**: - Review [Styling Guide](/unqorkai/docs/styling-guide-byo) for CSS best practices. - Review [When to Use BYO](/unqorkai/docs/bring-your-own) guidelines. - Check [FAQ](/unqorkai/docs/faq-byo) for common questions. - See [Troubleshooting](/unqorkai/docs/troubleshooting-byo) for issues. --- **Changelog** | Date | Change | | --- | --- | | 2026-09-30 | Replaced `npx byo preview` with `npm run preview`; removed hardcoded API key example; updated command event names to namespaced format across all framework examples; fixed em dash in browser support list; added blank lines before lists; fixed broken when-to-use links to point to Overview (EN-8151). | | 2026-05-01 | Initial publication | The Document Object Model (DOM) is the data representation of objects that form the structure and content of a document on the web. The DOM represents the page. Authentication secrets are a value of confidential information. For example, a password, security token, or cryptographic key, that is used to verify a user's identity during the authentication process to grant access to a system or application. APIs (application programming interfaces) are a set of protocols and definitions developers use to build and integrate application software. APIs act as the connective tissue between products and services. A key-value pair is a fundamental data structure where a unique "key" is associated with a specific "value," used for organizing and retrieving data efficiently, like in dictionaries or databases. Cascading Style Sheets (CSS) is a style sheet language used for presenting how a HTML or XML document looks to end-users. Also known as Unqork Users, or Designer Users; is anyone who is inside the Unqork platform. A functionality of a software. For example, Copy and Paste is a feature of a computer's operating system. AngularJS is a JavaScript framework added to an HTML page with a