SwaggerEditor

repository·main·Indexed 27 days ago

https://github.com/swagger-api/swagger-editor

A React-based component providing a powerful editor for Swagger/OpenAPI and AsyncAPI specifications. It features Monaco Editor integration, Web Worker support, and two syntax highlighting modes: Simplified and ApiDOM. Supports OpenAPI (2.0, 3.0, 3.1, 3.2), AsyncAPI (2.x, 3.x), JSON, and YAML.

Tokens
24K
Snippets
53
Records
111
Agent score
94%

What's inside swagger-editor

  1. Overview of Web Workers in SwaggerEditor

    main

    SwaggerEditor utilizes three distinct Web Workers to offload heavy tasks from the main thread:

    • editor.worker (Label: editorWorkerService): Handles Monaco core text-model operations such as diffing, link detection, and bracket matching.
    • apidom.worker (Label: apidom): Powers the ApiDOM language server for validation, hover, completion, and semantic tokens.
    • asyncapi-parser.worker: Handles AsyncAPI spec parsing. Unlike the others, this is not managed by Monaco but is spawned directly by the editor-preview-asyncapi plugin using Comlink.
  2. Understand WASM Bundling for apidom.worker.js

    main

    The apidom.worker.js bundle requires three WASM binaries to be fully self-contained with no runtime HTTP fetches. This is necessary for the worker to function correctly within Webpack-bundled consumers (like swagger-editor-plus).

    The three required binaries are:

    1. tree-sitter.wasm: The Emscripten runtime binary from web-tree-sitter.
    2. tree-sitter-yaml.wasm: The YAML grammar from @swagger-api/apidom-parser-adapter-yaml-1-2.
    3. tree-sitter-json.wasm: The JSON grammar from @swagger-api/apidom-parser-adapter-json.

    These are managed by the inlineAllWasms Vite plugin to ensure they are inlined as Uint8Arrays rather than being converted into async loader functions or data URIs that break runtime fetching.

  3. Understand the SwaggerEditor@5 plugin architecture

    main

    SwaggerEditor@5 is built using a modular plugin architecture. Plugins are categorized into four distinct types based on their responsibility:

    1. Editor implementation plugins: Provide the actual text editing interface (e.g., editor-textarea, editor-monaco).
    2. Editor preview plugins: Responsible for rendering the editor's text content into UI components (e.g., editor-preview-swagger-ui, editor-preview-asyncapi).
    3. Editor implementation support plugins: Provide enhanced capabilities to editor implementation plugins in a generic way (e.g., editor-persistence, editor-read-only).
    4. Generic features plugins: Provide UI enhancements or features that augment the other three categories (e.g., dialogs, modals, layout).
  4. Understand the SwaggerEditor@5 Core Layout

    main

    The "core" layout plugin provides plug points analogous to "panes" or "bars" in an IDE, allowing developers to customize the UX with minimal changes to the core structure.

    Core Components:

    • EditorPane: The main editing area. It includes four surrounding bars (top, bottom, left, right) that can be customized.
    • EditorPreviewPane: The preview area.
    • Topbar: The top navigation bar.

    Standard Implementations:

    • EditorPanes: editor-monaco and editor-textarea.
    • EditorPreviewPanes: editor-preview-swagger-ui and editor-preview-asyncapi.
  5. Understand changes in Swagger Editor migration from v3/v4 to current

    main

    If you are migrating from legacy versions (v3 or v4) to the current version of Swagger Editor, note the following major architectural and feature shifts:

    Editor & UI Changes

    • Editor Engine: The Ace Editor has been replaced by the Monaco Editor.
    • Validation: Internal validation from v3/v4 has been removed and replaced by validation from the apidom-ls library. Results are displayed in a validation pane.
    • File Handling: Supports drag-and-drop for local files (via react-dropzone) and persists definitions in localStorage on browser refresh.
    • UI Components: Uses react-modal for consistent styled modals instead of native window.alert or window.confirm popups.
    • Visuals: Uses SwaggerUI v4 and React 17.

    Feature Changes

    • Menu Relocation: Clear Editor has moved from the File Menu to the Edit Menu.
    • Plugin Deprecation: Plugins that previously handled direct implementations like Convert to OAS3 and Import File are deprecated.
    • Removed Features: The topbar-insert plugin has been removed and has no planned replacement.

    Implementation Changes

    • Architecture: Logic has been reorganized to separate Actions from Components. Menu action methods and logic helpers have been extracted into Actions or utils/editor-converter.
    • HTTP Client: fetch has been replaced with axios helpers in utils/topbar-http.
    • Dependencies: The swagger-client library dependency has been removed.
  6. Implement WebWorkers for language services

    main
    WebWorkers are called via an Adapter to execute language service methods in a separate thread. This is the recommended pattern for Monaco to maintain editor performance. The worker calls a LanguageService (which can be any library, such as apidom, typescript, json, or css) to perform heavy computations outside the main thread.
  7. Implement the inlineAllWasms Vite plugin

    main

    To bundle WASM binaries correctly in ESM/UMD builds, use the inlineAllWasms plugin. This plugin must be added to the Vite plugins array, not the rollupOptions.plugins array, to ensure it runs with enforce: 'pre' and bypasses downstream WASM plugins (like @rollup/plugin-wasm) that might otherwise convert .wasm imports into incompatible loader functions.

    The plugin uses three hooks:

    • resolveId: Redirects .wasm imports to a virtual ID (\0wasm-inline:...:inline) to bypass downstream .wasm checks.
    • load: Converts the virtual ID back to a file path and exports the raw bytes as a Uint8Array.
    • renderChunk: Injects tree-sitter.wasm directly into the Emscripten Module['wasmBinary'] property to prevent Emscripten from attempting a fetch() call.
    const inlineAllWasms = () => {
      const treeSitterBase64 = readFileSync('node_modules/web-tree-sitter/tree-sitter.wasm').toString('base64');
      return {
        name: 'inline-all-wasms',
        enforce: 'pre',
    
        // Redirect every .wasm import to a virtual id that does NOT end with '.wasm'
        async resolveId(id, importer) {
          if (!id.endsWith('.wasm')) return null;
          const resolved = await this.resolve(id, importer, { skipSelf: true });
          if (!resolved) return null;
          return '\0wasm-inline:' + resolved.id + ':inline';
        },
    
        // Export the raw bytes as a Uint8Array.
        load(id) {
          if (!id.startsWith('\0wasm-inline:')) return null;
          const filePath = id.slice('\0wasm-inline:'.length, -':inline'.length);
          const base64 = readFileSync(filePath).toString('base64');
          return `const bytes=new Uint8Array(atob("${base64}").split("").map(function(c){return c.charCodeAt(0)}));export default bytes;`;
        },
    
        // Inject Module['wasmBinary'] into the final bundle
        renderChunk(code) {
          const updated = code.replace(
            /var Module\s*=\s*typeof Module\s*!=\s*["']undefined["']\s*\?\s*Module\s*:\s*\{\}/,
            (match) => `${match};Module['wasmBinary']=new Uint8Array(atob("${treeSitterBase64}").split("").map(function(c){return c.charCodeAt(0)}))`
          );
          return updated !== code ? { code: updated } : null;
        },
      };
    };
  8. Run SwaggerEditor tests

    main

    The project uses npm test for unit tests and playwright for End-to-End (E2E) tests.

    Unit Tests

    • Watch mode: npm test
    • Single run (CI): npm run test:run
    • With coverage report: npm run test:coverage

    E2E Tests (Playwright)

    In development (with UI/Debug options):

    • Headed mode: npx playwright test --headed
    • Playwright UI: npx playwright test --ui
    • Debug mode: npx playwright test --debug

    In CI (Headless):

    • npx playwright test

    To view the Playwright test report, use: npx playwright show-report test/playwright/report

    $ npm test              # watch mode
    $ npm run test:run      # single run (CI)
    $ npm run test:coverage # with coverage report
    
    $ npx playwright test --headed    # Run with browser visible
    $ npx playwright test --ui        # Run with Playwright UI
    $ npx playwright test --debug     # Run in debug mode
    
    $ npx playwright show-report test/playwright/report
  9. Set up SwaggerEditor for local development

    main

    To develop locally, ensure you have Node.js >=24.18.0 and npm >=11.16.0 installed. If you use nvm, you can run nvm use to automatically select the correct Node.js version. Follow these steps to clone and initialize the repository:

    1. Clone the repository.
    2. Navigate into the directory.
    3. Install dependencies.
    4. Start the development server.
     $ git clone https://github.com/swagger-api/swagger-editor.git
     $ cd swagger-editor
     $ npm i
     $ npm start
  10. Host and configure Web Workers for ESM/UMD builds

    main

    When using SwaggerEditor as a library, you are responsible for hosting the worker files and pointing MonacoEnvironment.getWorker (or getWorkerUrl) to their hosted URLs. The workers are not bundled into the library entry and must be accessible at runtime via a separate URL.

    Build Formats

    • ESM (dist/esm/): These are ES modules spawned with { type: 'module' }. Use these for Native-ESM and Vite-based consumers.
    • UMD (dist/umd/): These are IIFE bundles (classic scripts) for compatibility with Webpack-bundled consumers that may not support { type: 'module' } workers.

    Available Worker Bundles

    • apidom.worker.js
    • editor.worker.js
    • asyncapi-parser.worker.js
  11. Use older React versions (React 17) with swagger-editor@5

    main

    By default, swagger-editor@5 uses React 18. If your application requires React 17 (specifically React >=17 <18), you must use package manager overrides or resolutions to force the dependency version.

    Note: Since react-redux@9 requires React 18, you must also downgrade react-redux to version 8 when using React 17.

    ### npm
    
    ```json
    {
      "dependencies": {
        "react": "=17.0.2",
        "react-dom": "=17.0.2"
      },
      "overrides": {
        "swagger-editor": {
          "react": "$react",
          "react-dom": "$react-dom",
          "react-redux": "^8"
        }
      }
    }

    yarn

    {
      "dependencies": {
        "react": "17.0.2",
        "react-dom": "17.0.2"
      },
      "resolutions": {
        "swagger-editor/react": "17.0.2",
        "swagger-editor/react-dom": "17.0.2",
        "swagger-editor/react-redux": "^8"
      }
    }