47

Adding continuous rendering to the PlantUML server

 5 years ago
source link: https://www.tuicool.com/articles/IJbm6fq
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

The problem with the PlantUML server

As of late, I’ve re-discovered PlantUML and how much easier it is to generate diagrams with text instead of clicking around with the mouse. I haven’t been following its development for quite some years now, and I’m amazed by how many more diagram types it supports. And also there is official support for all kinds of AWS icons which comes handy for communicating architecture.

But editing the diagrams is still painfully slow. The online demo server is a form with a submit button so that every time you make an edit you need to click on the button. Which brings the focus away from the editor, so you also need to click back to it:

qyUNrmz.gif

It’s just slow.

There is an online service at the bottom of the main page that does auto-regeneration, but that caps the number of images you can generate. And if you start using it, you’ll keep hitting the limit:

biMzQ3q.png!web

But hey, this is a client-side problem. And as it builds on HTML and Javascript, it can be modified without touching the server-side.

And the best thing is that the same techniques can be used for all sorts of frontend-augmentations. If you find yourself doing the same thing over and over again, there is a good chance that could be automated without touching any of the more complicated parts of the system. You usually don’t even need to check out the source code.

Getting notifications when there is a change on the page, hiding and showing elements, sending HTTP requests are all possible and while every frontend is different, these tools can be adapted to most of them.

So, my quest was to write a simple script that transforms the edit -> submit -> see workflow to an auto-regenerating one:

Uji6Rn6.gif

How to use

Since the online service is capped, you need to run your own PlantUML server for this:

docker run -it -p 3000:8080 plantuml/plantuml-server:jetty

After this, all you need is a browser:

  • open http://localhost:3000
  • open DevTools (F12)
  • open the Console
  • enter
  • close DevTools (F12)
  • enjoy!

Source code

(also on GitHub )

// https://codeburst.io/throttling-and-debouncing-in-javascript-b01cad5c8edf
const throttle=(t,e)=>{let n,o;return function(){const a=this,c=arguments;o?(clearTimeout(n),n=setTimeout(function(){Date.now()-o>=e&&(t.apply(a,c),o=Date.now())},e-(Date.now()-o))):(t.apply(a,c),o=Date.now())}};

const form = document.querySelector("form");
form.addEventListener("submit", (e) => e.preventDefault());
form.querySelector("input[type=submit]").style.display = "none";
[...document.querySelectorAll("form ~ *")].filter((e) => e.id !== "diagram").forEach((e) => e.remove());
let lastReq = undefined;

const observer = new MutationObserver(throttle(async () => {
	const currentTime = new Date().getTime();
	lastReq = currentTime;

	form.querySelector("input[type=submit]").click();
	const value = document.querySelector("textarea").value;

	const html = await (await fetch("/form", {
		method: "POST",
		headers: {
			"Content-Type": "application/x-www-form-urlencoded"
		},
		body: `text=${encodeURIComponent(value)}`
	})).text();

	if (lastReq === currentTime) {
		document.querySelector("#diagram img").src =
			new DOMParser()
				.parseFromString(html, 'text/html')
				.querySelector("#diagram img")
				.src;
	}
}
, 200));

observer.observe(form, {
	attributes: false,
	childList: true,
	subtree: true
});

How it works

The three main parts are:

  1. Getting the editor state
  2. Replacing the diagram image
  3. Detecting changes

Getting the editor state

This one is a bit tricky, as the editor is not a simple <textarea> but a CodeMirror instance. It offers an API but the variable that holds the editor instance is not available.

But it does support forms so it must use simple HTML elements. It works by capturing the form’s onsubmit event and then update a hidden <textarea>. The solution is straightforward then: programmatically submit the form then prevent it from sending after the CodeMirror code ran.

IJ7nimU.png!web

Translated to code:

const form = document.querySelector("form");

// prevent the form from submitting
form.addEventListener("submit", (e) => e.preventDefault());

// click on the submit button
form.querySelector("input[type=submit]").click();

// get the current value
const value = document.querySelector("textarea").value;

Replacing the diagram image

With the current diagram code, the next step is to emulate the form submit with a fetch then extract the new image URL from the result. Then we need to extract the URL from the response, and finally replace the <img> src.

Mj6JJ3m.png!web

The form is submitted as a POST request to /form with the body containing the diagram code in the form of text=... :

const html = await (await fetch("/form", {
	method: "POST",
	headers: {
		"Content-Type": "application/x-www-form-urlencoded"
	},
	body: `text=${encodeURIComponent(value)}`
})).text();

The html has a similar structure as the current page, so all we need is a DOMParser to extract the <img> element and its attribute:

new DOMParser()
	.parseFromString(html, 'text/html')
	.querySelector("#diagram img")
	.src;

And finally, update the attribute on the current page:

document.querySelector("#diagram img").src = ...;

Note : This approach parses the HTML returned by the form instead of constructing the new image src locally as the online demo server does. That approach works also but would require some libraries to include which I didn’t want to do. Luckily, the local server is fast enough so it does not do any noticeable slowdown.

Detecting changes

The last piece is to detect when the code in the editor is changed and only update the image when it did. It requires three components:

MutationObserver
throttle

IbQRZrv.png!web

A MutationObserver is a neat and versatile tool that is especially suited for hacks like this. It needs a DOM element, some configuration, and a callback function, and it will call the function whenever the element is changed. It can detect attribute as well as element changes for the whole subtree.

const observer = new MutationObserver(() => {
	...
});

observer.observe(form, {
	attributes: false, // don't notify for attribute changes
	childList: true,
	subtree: true
});

But it will call the function for all changes, which can flood the listener. To prevent this, the function should be throttled , meaning it will be called once every X ms. When you write the code it won’t send a POST request for every character, but it will be fast that you still see the latest result.

There are many implementations of the throttle method. You can find ready-made implementations in different libraries , but it’s not that long to copy-paste either. I’ve found a simple one here .

With the function in place, the only thing left is to rate-limit the MutationObserver callback:

const throttle = ...;

const observer = new MutationObserver(throttle(() => {
	...
}));

The last problem to solve is the asynchronicity of the fetch call. There are no guarantees that network responses come back in the order they are dispatched, and that can result in overwriting with an earlier response.

6RBnUbe.png!web

To handle this, the easiest solution is to introduce a requestID and check if the latest dispatched ID matches. If the last requestID is different, simply discard the result.

VNVVB3r.png!web

In code, the easiest requestID is the current time.

let lastReq = undefined;
const observer = new MutationObserver(throttle(async () => {
	// requestID
	const currentTime = new Date().getTime();
	lastReq = currentTime;

	await fetch(...);

	if (lastReq === currentTime) {
		// handle the result
	}
}));

Conclusion

Speed makes or breaks a tool. If I need to manually do something to see the results and it takes a considerable amount of time then I’m unlikely to use it. PlantUML is just one example that I recently looked into, but many others rely on a web interface. And sometimes all it needs is an hour of hacking to convert something that is nice-but-slow into something that is a pleasure to use. The techniques described in this post are some of the building blocks.


Recommend

  • 144

    Vim PlantUML Syntax/Plugin/FTDetect This is a vim syntax file for PlantUML. The filetype will be set to plantuml for *.pu, *.uml...

  • 76

    plantuml-mode - A major mode for editing PlantUML sources in Emacs

  • 88

    README.md Oh shit! Lock up your daughters it's ... ███████╗██╗ ██╗ ██╗███╗ ███╗██╗ ██████╗ ██████╗ ██████╗ ██╔════╝██║ ██║ ██║████╗ ████║██║ ██╔═══██╗██╔══██╗██╔══...

  • 53
    • xuanye.github.io 5 years ago
    • Cache

    自定义PlantUML和C4 Model样式

    什么是PlantUml PlantUml 是一个支持快速绘制的开源项目.其定义了一套完整的语言用于实现UML关系图的描述.并基于强大的graphviz图形渲染库进行UML图的生成.绘制的UML图还可以导出为图...

  • 36
    • blog.djy.io 4 years ago
    • Cache

    PlantUML turns text into diagrams

    A coworker of mine introduced us to PlantUML a year or two ago and we used it to create an architecture diagram of our distributed systems. I revisited that old architecture diagram re...

  • 38

    类库大魔王的挖井日记挖一口属于自己的井404 Not Found没有这个页面诶,返回首页看看吧~

  • 7

    前段时间设计后端表结构的时候接触了 PlantUML,它能够直观表现出类的属性和方法,反应出类与类之间的关系。之前有同事就是用 PlantUML 来做后端数据表设计工作的,所以有必要学习一下。 流程图 Flowchart 是用...

  • 2
    • yuanfentiank789.github.io 3 years ago
    • Cache

    PlantUML 高效地画图

    程序员难免要经常画流程图,状态图,时序图等。以前经常用 visio 画,经常为矩形画多大,摆放在哪等问题费脑筋。有时候修改文字后,为了较好的显示效果不得不再去修改图形。今天介绍的工具是如何使用 Sublime + PlantUML 的插件画流程图,状态图,...

  • 7
    • www.codeproject.com 3 years ago
    • Cache

    UML Made Easy with PlantUML & VS Code

    Introduction UML stands for Unified Modeling Language. It’s a general-purpose modeling language to standardize a way to visualize the architecture of software systems. It was developed by

  • 5
    • paul.pub 3 years ago
    • Cache

    使用PlantUML绘图

    本文介绍一个适合于软件工程师所使用的绘图工具:PlantUML。 软件工程师常常需要绘制UML图来描述软件结构。市面上有非常多...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK