Skip to content

feat: upgrade to Excalidraw 0.18.0 with legacy file compatibility#489

Closed
Technophobe01 wants to merge 9 commits intobric3:mainfrom
Technophobe01:feature/upgrade-excalidraw-0.18.0
Closed

feat: upgrade to Excalidraw 0.18.0 with legacy file compatibility#489
Technophobe01 wants to merge 9 commits intobric3:mainfrom
Technophobe01:feature/upgrade-excalidraw-0.18.0

Conversation

@Technophobe01
Copy link
Contributor

Summary

Closes #423

This PR upgrades the embedded Excalidraw library from 0.17.x to 0.18.0, bringing the latest features and improvements while maintaining full backward compatibility with existing .excalidraw files.

Motivation

Excalidraw 0.18.0 includes significant improvements including new fonts, better rendering, and various bug fixes. However, the upgrade introduced breaking changes in the element data format that caused issues with files created in older versions.

Changes

Build System Migration (addresses CRA incompatibility from #423)

  • Migrated from react-scripts to Vite for faster builds and better ES module support
  • Updated TypeScript configuration for Vite compatibility (moduleResolution: "bundler")
  • Moved index.html to project root (Vite convention)
  • Replaced config-overrides.js with vite.config.ts
  • Set "type": "module" in package.json for ESM support

Excalidraw 0.18.0 Compatibility

  • Updated @excalidraw/excalidraw to 0.18.0
  • Added required CSS import (@excalidraw/excalidraw/index.css)
  • Set window.EXCALIDRAW_ASSET_PATH = "/" for proper font loading (self-hosted fonts)
  • Added font refresh callback to ensure text renders correctly after async font loading
  • Fonts are copied to build output and served via the existing SchemeHandlerFactory

Legacy File Support

Implemented preprocessing for files created with older Excalidraw versions to fix:

Issue Fix
Text elements missing lineHeight Calculate from font family using Excalidraw's font metadata
Text elements missing autoResize Default to true
Elements with boundElements: null Convert to empty array []
Elements missing frameId Add with value null
Arrow bindings using old format (focus/gap) Convert to new format (mode/fixedPoint)

Loading Strategy

  • Changed element loading to use Excalidraw's loadFromBlob API instead of direct updateScene calls
  • This ensures Excalidraw's internal format normalization handles any edge cases not covered by preprocessing

Other Changes

  • Added MIME type registration for .woff2 font files in SchemeHandlerFactory
  • Updated SVG font path patching regex for new Excalidraw 0.18.0 font paths

Checklist from #423

  • ESM modules support (migrated to Vite, set "type": "module")
  • TypeScript moduleResolution updated to "bundler"
  • Deprecated excalidraw-assets folder handling (now using fonts/ path)
  • Fonts self-hosting with EXCALIDRAW_ASSET_PATH
  • updateScene API changes handled

Testing

  • Verified on macOS (IntelliJ IDEA, PyCharm)
  • Verified on Windows (IntelliJ IDEA)
  • Tested with legacy files created in Excalidraw 0.15.x and 0.17.x
  • Tested with files from excalidraw.com (latest format)
  • All file types work correctly: .excalidraw, .excalidraw.svg, .excalidraw.png

Breaking Changes

None for end users. Existing files continue to work without modification.

- Migrate from react-scripts to Vite for faster builds
- Add preprocessLegacyElements() to fix rendering of old .excalidraw files
- Fix missing lineHeight, autoResize, frameId, and boundElements fields
- Update TypeScript and dependency configurations
- Update font path regex for 0.18.0 asset structure
- Use loadFromBlob API in update handler to let Excalidraw handle
  format normalization internally (fixes menu disappearing issue)
- Add arrow binding format conversion for legacy files
- Remove unused restoreElements import
- Clean up debug logging
- Disable debug mode
MockK 1.14.x requires Kotlin 2.0+ and was pulling in kotlin-stdlib 2.1.20,
which has metadata version 2.1.0. The project compiles with Kotlin 1.9.22,
which expects metadata version 1.9.0, causing test compilation to fail.

Changes:
- Downgrade MockK from 1.14.4 to 1.13.16 (compatible with Kotlin 1.9.x)
- Remove deprecated instrumentationTools() call (no longer necessary in
  IntelliJ Platform Gradle Plugin 2.x as instrumentation is now handled
  automatically when using testFramework())
Copy link
Owner

@bric3 bric3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome !
So many thanks for doing it !

I noticed a few issue though, possibly due to how Excalidraw 0.18 works.

  • For example, the plugin used the current IDE theme when opening an Excalidraw file. With this change it doesn't seem to be the case

  • Embedded excalidraw in SVG file don't seem to load (hello-export-with-scene.svg), there's a tab on the bottom of the editor to switch to excalidraw.

    java.lang.Throwable: hello-export-with-scene.svg: [LOGSEVERITY_ERROR][https://excalidraw-jetbrains-plugin/assets/index-B5dKs5In.js:40679]:
    Error loading file: SyntaxError: Unexpected token '<', "<svg versi"... is not valid JSON
      at com.intellij.openapi.diagnostic.Logger.error(Logger.java:376)
      at com.github.bric3.excalidraw.editor.ExcalidrawWebViewController$initJcefPanel$5.onConsoleMessage(ExcalidrawWebViewController.kt:219)
      at com.intellij.ui.jcef.JBCefClient$3.lambda$onConsoleMessage$4(JBCefClient.java:301)
      at com.intellij.ui.jcef.JBCefClient$HandlerSupport.lambda$handle$0(JBCefClient.java:733)
      at java.base/java.lang.Iterable.forEach(Iterable.java:75)
      at java.base/java.util.Collections$SynchronizedCollection.forEach(Collections.java:2131)
      at com.intellij.ui.jcef.JBCefClient$HandlerSupport.handle(JBCefClient.java:733)
      at com.intellij.ui.jcef.JBCefClient$HandlerSupport.handleBoolean(JBCefClient.java:738)
      at com.intellij.ui.jcef.JBCefClient$3.onConsoleMessage(JBCefClient.java:300)
      at jcef/org.cef.CefClient.onConsoleMessage(CefClient.java:354)
    
  • Or PNG for that matter (random.png)

    java.lang.Throwable: random.png: [LOGSEVERITY_ERROR][https://excalidraw-jetbrains-plugin/assets/index-B5dKs5In.js:40679]:
    Error loading file: SyntaxError: Unexpected token '�', "�PNG
    �
    ��"... is not valid JSON
      at com.intellij.openapi.diagnostic.Logger.error(Logger.java:376)
      at com.github.bric3.excalidraw.editor.ExcalidrawWebViewController$initJcefPanel$5.onConsoleMessage(ExcalidrawWebViewController.kt:219)
      at com.intellij.ui.jcef.JBCefClient$3.lambda$onConsoleMessage$4(JBCefClient.java:301)
      at com.intellij.ui.jcef.JBCefClient$HandlerSupport.lambda$handle$0(JBCefClient.java:733)
      at java.base/java.lang.Iterable.forEach(Iterable.java:75)
      at java.base/java.util.Collections$SynchronizedCollection.forEach(Collections.java:2131)
      at com.intellij.ui.jcef.JBCefClient$HandlerSupport.handle(JBCefClient.java:733)
      at com.intellij.ui.jcef.JBCefClient$HandlerSupport.handleBoolean(JBCefClient.java:738)
      at com.intellij.ui.jcef.JBCefClient$3.onConsoleMessage(JBCefClient.java:300)
      at jcef/org.cef.CefClient.onConsoleMessage(CefClient.java:354)
    
  • Got this error :

    java.lang.Throwable: invalid.excalidraw: [LOGSEVERITY_ERROR][https://excalidraw-jetbrains-plugin/assets/index-B5dKs5In.js:40647]:
    Error in update handler: TypeError: Cannot read properties of undefined (reading 'length')
      at com.intellij.openapi.diagnostic.Logger.error(Logger.java:376)
      at com.github.bric3.excalidraw.editor.ExcalidrawWebViewController$initJcefPanel$5.onConsoleMessage(ExcalidrawWebViewController.kt:219)
      at com.intellij.ui.jcef.JBCefClient$3.lambda$onConsoleMessage$4(JBCefClient.java:301)
      at com.intellij.ui.jcef.JBCefClient$HandlerSupport.lambda$handle$0(JBCefClient.java:733)
      at java.base/java.lang.Iterable.forEach(Iterable.java:75)
      at java.base/java.util.Collections$SynchronizedCollection.forEach(Collections.java:2131)
      at com.intellij.ui.jcef.JBCefClient$HandlerSupport.handle(JBCefClient.java:733)
      at com.intellij.ui.jcef.JBCefClient$HandlerSupport.handleBoolean(JBCefClient.java:738)
      at com.intellij.ui.jcef.JBCefClient$3.onConsoleMessage(JBCefClient.java:300)
      at jcef/org.cef.CefClient.onConsoleMessage(CefClient.java:354)
    

Out of scope note:

  • Embedded images in Excalidraw do not work yet so there's likely something else to do on this front. See #200

Comment on lines 39 to +54
uri.path.endsWith(".html") -> response.mimeType = "text/html"
uri.path.endsWith(".js") -> response.mimeType = "application/javascript"
uri.path.endsWith(".css") -> response.mimeType = "text/css"
uri.path.endsWith(".svg") -> response.mimeType = "image/svg+xml"
uri.path.endsWith(".png") -> response.mimeType = "image/png"
uri.path.endsWith(".jpg") || uri.path.endsWith(".jpeg") -> response.mimeType = "image/jpeg"
uri.path.endsWith(".gif") -> response.mimeType = "image/gif"
uri.path.endsWith(".webp") -> response.mimeType = "image/webp"
// Font MIME types for Excalidraw 0.18.0 self-hosted fonts
uri.path.endsWith(".woff") -> response.mimeType = "font/woff"
uri.path.endsWith(".woff2") -> response.mimeType = "font/woff2"
uri.path.endsWith(".ttf") -> response.mimeType = "font/ttf"
uri.path.endsWith(".otf") -> response.mimeType = "font/otf"
uri.path.endsWith(".eot") -> response.mimeType = "application/vnd.ms-fontobject"
// JSON for any config files
uri.path.endsWith(".json") -> response.mimeType = "application/json"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: In a future work this might be a good idea to extract the mapping to some configuration file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted for future work.

val debugMode = ProcessHandle.current().info().arguments().map {
it.any { arg -> arg.contains("-agentlib:jdwp") }
}.orElse(false)!!
val debugMode = false
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chore: To revert, ideally I'd like to keep this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 582c08c

* https://github.com/excalidraw/excalidraw/issues/7543
*
* Updated for Excalidraw 0.18.0 where font paths changed from
* dist/excalidraw-assets/ to fonts/
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick:

Suggested change
* dist/excalidraw-assets/ to fonts/
* `dist/excalidraw-assets/` to `fonts/`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 582c08c

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chore: Rename test.excalidrow to zero-bytes.excalidraw.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way opening that file generates the following error:

This is unrelated to this work, but I'd like to mention it, so at some point it can be picked up.

java.lang.Throwable: test.excalidraw: [LOGSEVERITY_ERROR][https://excalidraw-jetbrains-plugin/index.html:1]:
Uncaught SyntaxError: Unexpected end of JSON input
	at com.intellij.openapi.diagnostic.Logger.error(Logger.java:376)
	at com.github.bric3.excalidraw.editor.ExcalidrawWebViewController$initJcefPanel$5.onConsoleMessage(ExcalidrawWebViewController.kt:219)
	at com.intellij.ui.jcef.JBCefClient$3.lambda$onConsoleMessage$4(JBCefClient.java:301)
	at com.intellij.ui.jcef.JBCefClient$HandlerSupport.lambda$handle$0(JBCefClient.java:733)
	at java.base/java.lang.Iterable.forEach(Iterable.java:75)
	at java.base/java.util.Collections$SynchronizedCollection.forEach(Collections.java:2131)
	at com.intellij.ui.jcef.JBCefClient$HandlerSupport.handle(JBCefClient.java:733)
	at com.intellij.ui.jcef.JBCefClient$HandlerSupport.handleBoolean(JBCefClient.java:738)
	at com.intellij.ui.jcef.JBCefClient$3.onConsoleMessage(JBCefClient.java:300)
	at jcef/org.cef.CefClient.onConsoleMessage(CefClient.java:354)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 582c08c - renamed to zero-bytes.excalidraw

.gitignore Outdated
./project-fixture/.idea/workspace.xml

# Claude Code
CLAUDE.md No newline at end of file
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: If claude helped there, maybe that's worth havng that file here. However I'd rather have a standard AGENTS.md file and tell claude to point to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in c884923. Will consider AGENTS.md for future.

.first() ?: defaultPort
}
// PORT is configured in vite.config.ts, defaulting to 3006
3006
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: For future work maybe this can be extracted from the vite.config.ts file, or some other file that both vite and gradle can read.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted for future work.

Comment on lines -5 to -24
module.exports = function override(config, env) {
// see https://webpack.js.org/configuration/
console.log("config-overrides.js: Disabling build minimizer");
config.mode = "development";
config.optimization.minimize = false;
config.optimization.minimizer = [];

// https://webpack.js.org/configuration/devtool/
console.log("config-overrides.js: Set devtool inline SourceMaps");
config.devtool = "inline-source-map"

// There seem to be a bug in CRA 5 that causes warnings about source maps
// Disable the warning for now
// Discussion of the bug: https://github.com/facebook/create-react-app/discussions/11767
// PR supposed to fix the issue: https://github.com/facebook/create-react-app/pull/11752
console.log("config-overrides.js: Ignoring source map warning https://github.com/facebook/create-react-app/discussions/11767");
config.ignoreWarnings = [/Failed to parse source map/];

return config;
};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: How does that work in practice without ? the vite config appear simpler and working?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed - project migrated from webpack (CRA) to Vite. The vite.config.ts handles build configuration now.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Maybe rename after what should be looked at in this file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left as-is - the name already describes its content (scan sequence diagram).

@bric3 bric3 added the enhancement New feature or request label Dec 18, 2025
@bric3
Copy link
Owner

bric3 commented Dec 18, 2025

By the way you can prepare an entry in the Changelog.md file as well, you can mention yourself there :)

@Technophobe01
Copy link
Contributor Author

Technophobe01 commented Dec 21, 2025 via email

- Restore ProcessHandle-based debug mode detection
- Add backticks around font paths in KDoc comment

Signed-off-by: Technophobe01 <pkjarvis01@gmail.com>
Signed-off-by: Technophobe01 <pkjarvis01@gmail.com>
- Add tryReadSvgExcalidrawScene() to extract base64 payload from SVG
- Update pngHasEmbeddedScene() to cache extracted scene data
- Scheme handler serves decoded JSON instead of raw file content

Signed-off-by: Technophobe01 <pkjarvis01@gmail.com>
Add dynamic theme--light/theme--dark class to wrapper element.
Required for Excalidraw 0.18.0 CSS theming to work correctly.

Signed-off-by: Technophobe01 <pkjarvis01@gmail.com>
Update load-from-file handler to detect and handle both:
- Regular Excalidraw JSON files
- Encoded/compressed payloads from embedded SVG/PNG scenes

Signed-off-by: Technophobe01 <pkjarvis01@gmail.com>
Signed-off-by: Technophobe01 <pkjarvis01@gmail.com>
@Technophobe01 Technophobe01 requested a review from bric3 December 22, 2025 00:41
@Technophobe01 Technophobe01 deleted the feature/upgrade-excalidraw-0.18.0 branch December 22, 2025 02:21
@Technophobe01
Copy link
Contributor Author

Technophobe01 commented Dec 22, 2025 via email

@Technophobe01
Copy link
Contributor Author

Technophobe01 commented Dec 22, 2025 via email

@bric3
Copy link
Owner

bric3 commented Dec 22, 2025

Happy to take a look at this one: #488 I'm not sure if a solution exists, but in 18 there may be a way.

Yeah go ahead!

That said I cannot review seriously until next week. So don't expect much feedback until then.

Team care.

Also, you should avoid reply by email it seems to leak quite a few like your phone number.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Upgrade to Excalidraw 0.18.0

2 participants