Plain Vanilla

repository·main·Indexed 20 days ago

https://github.com/jsebrech/plainvanilla

A demonstration project and website built exclusively with vanilla web technologies (HTML, CSS, and JavaScript) without build tools, frameworks, or bundlers. The project includes implementations of a declarative router using the <view-route> custom element, a view transition system with startTransition() and the <view-transition> custom element, and utility functions for HTML encoding and navigation interception.

Tokens
7.5K
Snippets
34
Records
36
Agent score
69%

What's inside plainvanilla

  1. Overview of Plain Vanilla

    main
    Plain Vanilla is a website and demonstration project that showcases web development using only vanilla techniques. It avoids all external tools, build steps, and frameworks, relying exclusively on the browser and standard web code (HTML, CSS, and JavaScript).
  2. Run the Plain Vanilla project locally

    main

    The project is designed to be served as a static website from the public/ directory. You can use any of the following commands to host the site locally depending on your installed environment:

    # Using Node.js (http-server)
    npx http-server public -c-1
    
    # Using PHP
    php -S localhost:8000 -t public
    
    # Using Python 3
    python3 -m http.server 8000 --directory public
  3. Configure ESLint for Plain Vanilla

    main

    The project uses the ESLint flat config format (eslint.config.cjs). The configuration extends the recommended JavaScript rules and sets up specific environment globals and ignore patterns.

    Key settings include:

    • Globals: Includes browser and mocha environments.
    • ECMAScript Version: Set to 2022.
    • Source Type: Set to module.

    Note that certain directories are explicitly ignored by the linting process to prevent errors in specific content or library paths.

    /* eslint-disable no-undef */
    const globals = require("globals");
    const js = require("@eslint/js");
    
    module.exports = [
        js.configs.recommended, 
        {
            languageOptions: {
                globals: {
                    ...globals.browser,
                    ...globals.mocha
                },
                ecmaVersion: 2022,
                sourceType: "module",
            }
        },
        {
            ignores: [
                "public/blog/articles/",
                "**/lib/",
                "**/react/",
            ]
        }
    ];
  4. Implement route navigation with startTransition

    main

    To implement a view-transition-aware router, use startTransition whenever you modify the visibility or content of route elements. This ensures that the browser's View Transition API can correctly interpolate between the old and new states of the DOM.

    import { startTransition } from './view-transition.js';
    
    let currentRoute = '';
    
    export function navigate() {
        currentRoute = currentRoute === 'route2' ? 'route1' : 'route2';
        updateRoute1();
        updateRoute2();
    }
    
    function updateRoute1() {
        startTransition(() => {
            if (currentRoute === 'route1') {
                document.getElementById('route1').classList.add('active');
            } else {
                document.getElementById('route1').classList.remove('active');
            }
        });
    }
    
    function updateRoute2() {
        startTransition(() => {
            const route2 = document.getElementById('route2');
            if (currentRoute === 'route2') {
                route2.classList.add('active', 'loading');
                route2.textContent = '...';
                load().then((data) => startTransition(() => {
                    route2.textContent = data;
                    route2.classList.remove('loading');
                }));
            } else {
                document.getElementById('route2').classList.remove('active');
            }
        });
    }
  5. Use `htmlRaw` to bypass HTML encoding

    main

    If you have a string that is already safe or contains intentional HTML markup that should not be encoded, wrap it in htmlRaw(). This marks the string as an instance of the Html class, which tells the html template tag to skip encoding for that specific value.

    import { html, htmlRaw } from './lib/html.js';
    
    const safeMarkup = htmlRaw'<strong>Important</strong>';
    const template = html`<div>${safeMarkup}</div>`;
    // Result: "<div><strong>Important</strong></div>"
  6. Tokenize code with tokenize()

    main

    The tokenize function is the low-level engine that traverses a source string and identifies tokens based on a language definition. It executes a callback for every identified token.

    Parameters:

    • src (string): The source code.
    • lang (ShjLanguage | Array): A language identifier string (which triggers an async import) or an array of ShjLanguageComponent objects.
    • token (function): A callback function called for each token. Signature: (text: string, type: ShjToken) => void.

    Token Types (ShjToken): 'deleted' | 'err' | 'var' | 'section' | 'kwd' | 'class' | 'cmnt' | 'insert' | 'type' | 'func' | 'bool' | 'num' | 'oper' | 'str' | 'esc'

    await tokenize('const x = 1;', 'js', (text, type) => {
      console.log(`Token: ${text}, Type: ${type}`);
    });
  7. Navigate programmatically with pushState()

    main

    The pushState function allows you to manually trigger navigation. It updates the browser's history stack and dispatches a popstate event to the routerEvents target, which in turn triggers all active <view-route> elements to update their matching state.

    Signature: pushState(state, unused, url)

    import { pushState } from './view-route.js';
    
    // Navigate to a new URL
    pushState(null, null, '/new-page');
  8. Highlight a DOM element with highlightElement()

    main

    Use highlightElement to transform an existing DOM element (like a <code> or <pre> tag) into a highlighted version. It automatically detects the language from the element's class name if not provided and determines the display mode based on the tag name and content length.

    Parameters:

    • elm (Element): The DOM element to highlight.
    • lang (ShjLanguage, optional): The language identifier. If omitted, it searches for a class matching shj-lang-{language} on the element.
    • mode (ShjDisplayMode, optional): The display mode. If omitted, it defaults to:
      • inline if the tag is <code>.
      • oneline if the text contains fewer than 2 lines.
      • multiline otherwise.
    • opt (ShjOptions, optional): Customization options.

    Display Modes (ShjDisplayMode):

    • inline: Inside a <code> element.
    • oneline: Inside a <div> element, containing only one line.
    • multiline: Inside a <div> element.

    Note: This function modifies the element's innerHTML, className, and dataset.lang.

    // Assuming <pre class="shj-lang-js">const x = 1;</pre> exists in DOM
    const element = document.querySelector('pre');
    await highlightElement(element);
  9. Highlight text with highlightText()

    main

    Use highlightText to convert a raw code string into an HTML string with syntax highlighting applied. It supports multiline wrapping and customization via options.

    Parameters:

    • src (string): The raw code content.
    • lang (ShjLanguage): The language identifier (e.g., 'js', 'css', 'python').
    • multiline (boolean, default: true): If true, wraps the output in <div> elements suitable for line numbering.
    • opt (ShjOptions, default: {}): Configuration object.

    Options (ShjOptions):

    • hideLineNumbers (boolean): If true, prevents the generation of line number placeholders.

    Returns: A Promise<string> containing the HTML content.

    const code = 'const x = 10;';
    const highlighted = await highlightText(code, 'js');
    // Or with options
    const highlightedWithNoLines = await highlightText(code, 'js', true, { hideLineNumbers: true });
  10. Use the `html` tagged template literal for auto-encoding

    main

    The html function acts as a tagged template literal that automatically encodes HTML entities in interpolated values. This prevents XSS by ensuring that characters like <, >, and & are converted to their safe HTML entity equivalents (e.g., &lt;, &gt;, &amp;).

    import { html } from './lib/html.js';
    
    const userInput = '<img src=x onerror=alert(1)>';
    const template = html`<div>${userInput}</div>`;
    
    // template will be: "<div>&lt;img src=x onerror=alert(1)&gt;</div>"
  11. Manage task state with TasksProvider and hooks

    main

    The TasksContext module provides a React context-based state management system for tasks. To use it, wrap your component tree with TasksProvider. This enables child components to access the current list of tasks via useTasks() and trigger state updates via useTasksDispatch().

    import { TasksProvider, useTasks, useTasksDispatch } from './TasksContext';
    
    function App() {
      return (
        <TasksProvider>
          <TaskList />
        </TasksProvider>
      );
    }
    
    function TaskList() {
      const tasks = useTasks();
      const dispatch = useTasksDispatch();
    
      // Example: adding a task
      const addTask = () => dispatch({ type: 'added', id: 3, text: 'New Task' });
    
      return (
        <div>
          {tasks.map(task => <div key={task.id}>{task.text}</div>)}
          <button onClick={addTask}>Add Task</button>
        </div>
      );
    }