Skip to main content

Markdown syntax highliting glitch

Overview:

The changes refactor the integration of Prism.js for syntax highlighting, moving from a dynamic, lazy-loading approach to static imports. This enhances Server-Side Rendering (SSR) compatibility, simplifies the highlighting logic, and introduces a new, highly customized theme based on prism-okaidia.css to improve the visual presentation of code blocks.

Explanation:

1. Global Styling & Theme Overhaul (angular.json, src/styles.scss, message-bubble.component.ts)

  • Theme Switch: The angular.json file now includes node_modules/prismjs/themes/prism-okaidia.css in the global styles array. This means the application will now use the Okaidia theme as its base for code highlighting. This change is mirrored in src/styles.scss, where the previous prism.css import is replaced by prism-okaidia.css.
  • Centralized & Customizable Styling: The extensive custom CSS rules in src/styles.scss are introduced to override and enhance the Okaidia theme. This includes new CSS variables for colors (e.g., --prism-background, --prism-keyword), font family adjustments (like adding 'Fira Code' for ligatures), and fine-tuning spacing and borders. This approach centralizes the styling for Prism.js, making it easier to manage and modify.
  • Component Styling Removal: Correspondingly, the inline <style> block in message-bubble.component.ts has had its pre and code specific styling removed. This is because the global prism-okaidia.css and the custom overrides in styles.scss now handle the base styling for code blocks, reducing redundancy and improving modularity.

2. Simplified Highlighting Logic (message-bubble.component.ts)

  • Synchronous Highlighting: The highlightCode method in MessageBubbleComponent is no longer async and directly calls this.prismService.highlightElement. The loop iterating over codeBlocks no longer uses await. This change is a direct result of the PrismService no longer performing asynchronous loading of Prism.js (see next point).
  • Immediate Highlighting on Markdown Ready: The onMarkdownReady lifecycle hook now calls this.highlightCode() directly, removing the setTimeout(..., 0) wrapper. This suggests that ngx-markdown's event now reliably fires after the markdown content has been rendered into the DOM, making immediate synchronous highlighting possible without deferring to the next tick.

3. Prism.js Service Refactor & SSR Compatibility (prism.service.ts)

  • Static Imports: The PrismService has been fundamentally changed. The previous dynamic loadPrism() and loadLanguageComponents() methods, which used await import() to lazy-load Prism.js and its language components at runtime, have been removed. Instead, Prism core (import 'prismjs';) and a specific set of commonly used language components (e.g., prism-typescript, prism-javascript, prism-json) are now directly imported at the top of the prism.service.ts file. This means Prism.js and its languages are bundled with the application at compile time.
    • Implication: This simplifies the service and ensures Prism is immediately available, but it increases the initial bundle size slightly as Prism and all imported languages are always included.
  • SSR Compatibility: The service now injects PLATFORM_ID and uses isPlatformBrowser from @angular/common. All calls to Prism.highlightElement and Prism.highlightAll are guarded by if (this.isBrowser). This is a critical improvement for Server-Side Rendering (SSR) support, as Prism.js is a client-side library that relies on browser DOM APIs. By performing these checks, the application prevents errors when attempting to render on the server, ensuring a smoother universal rendering experience.
  • Synchronous Methods: The highlightElement and highlightAll methods are now synchronous, reflecting the fact that Prism is already loaded and available due to the static imports.

4. Dependency Downgrades (package.json, package-lock.json)

  • Minor version downgrades for marked, ngx-markdown, prismjs, and @types/prismjs are observed. For example, prismjs was downgraded from ^1.30.0 to ^1.29.0. While generally minor, downgrades are less common than upgrades and usually indicate a specific compatibility issue or a deliberate choice to align with a known stable version of these libraries within the project's ecosystem.

Best Practices:

  • SSR Awareness: Using PLATFORM_ID and isPlatformBrowser is a standard and crucial best practice for ensuring client-side libraries function correctly and avoid errors in an Angular Universal (SSR) environment.
  • Modular Styling: Centralizing third-party library styles in global stylesheets and using CSS variables for theming promotes maintainability and easier theme management.
  • Readiness for External Libraries: The change in onMarkdownReady from setTimeout to direct call indicates improved understanding or guarantees of the timing of DOM readiness from ngx-markdown, leading to more robust and immediate code highlighting.

Suggestions:

  • Document Dependency Downgrades: For the minor version downgrades of marked, ngx-markdown, prismjs, and @types/prismjs, it would be beneficial to add a comment in the commit message or project documentation explaining the reason. This prevents future developers from re-introducing potential issues by upgrading back to those specific versions.
  • Consider Granularity of Prism Imports: While static imports simplify the service, if this application were to grow significantly and only a few specific code languages were consistently highlighted, a more granular import approach (e.g., loading only prism-typescript if only TS code is expected) could further optimize bundle size. However, for a chat application that might display various code types, the current broad import list is practical.