The State of Copy-Pasting in JavaScript
I recently did some work on a VS Code extension whose purpose is to handle rich pastes. It prompted me to survey different copy-pasting libraries and the state of the NPM ecosystem as a whole.
How do clipboards work?⌗
You've Got Mail!
No more than five emails per year, all densely packed with knowledge.
Clipboards across different operating systems work essentially the same. For this reason, we will stick to looking at a single one – Windows.
A common misconception about how clipboards work is that they contain a single piece of data, such as text or an image. In reality, clipboards hold all the ways target software can represent the data.
Consider if I copy the following webpage:
HTML headings as rendered by Firefox
When pasting it into Microsoft Word, it appears formatted as rich content:
Microsoft Word displays rich content exactly as Firefox does
When pasting it into Notepad++, it appears as plain text:
Notepad++ does not handle rich content
Pasting different content types depending on the software is possible because Firefox provides multiple representations of the rendered content to the clipboard.
Let’s see what types the clipboard is holding using Powershell:
$dataObj = [System.Windows.Forms.Clipboard]::GetDataObject()
foreach ($fmt in $dataObj.GetFormats()) {
[Console]::WriteLine($fmt)
}
## Output: ##
HTML Format
System.String
UnicodeText
Text
Chromium Web Custom MIME Data Format
Locale
OEMText
We can look inside some of the clipboard types using Powershell, mainly HTML Format
and Text
with the following commands:
# Get Text
[System.Windows.Forms.Clipboard]::GetText([System.Windows.Forms.TextDataFormat]::Text)
## Output: ##
This is heading 1
This is heading 2
This is heading 3
This is heading 4
This is heading 5
This is heading 6
# Get HTML Format
[System.Windows.Forms.Clipboard]::GetText([System.Windows.Forms.TextDataFormat]::Text)
## Output: ##
Version:0.9
StartHTML:00000174
EndHTML:00000410
StartFragment:00000208
EndFragment:00000374
SourceURL:https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_headers
<html><body>
<!--StartFragment--><h1>This is heading 1</h1>
<h2>This is heading 2</h2>
<h3>This is heading 3</h3>
<h4>This is heading 4</h4>
<h5>This is heading 5</h5>
<h6>This is heading 6</h6><!--EndFragment-->
</body>
</html>
The way to approach clipboards as a consumer is to consume the clipboard formats you know how to, prioritizing some over others. For example, Microsoft Word will prioritize pasting HTML Format
formats over Text
formats by default.
While Powershell has some built-in parsers, implementing the format specification for every type of content is a lot of work. Are there libraries that can help us with this?
Copy-Pasting in the NPM Ecosystem⌗
The simplest clipboard library would let us query the clipboard directly for binary data. This approach would require us to a parser per format per operating system. While such libraries deserve a place in this world, they are not helpful for most applications. Most applications want to handle copying and pasting only for text, rich text, images, and sometimes files. Let’s go through the different clipboard management libraries and see how they fare.
We are looking for a library that has the following features:
- Pure JS, with native bindings
- Cross-Platform Support
- Windows
- Linux
- MacOS
- Copy to clipboard
- Plain Text
- Rich Text
- Images
- Files
- Paste from clipboard
- Plain Text
- Rich Text
- Images
- Files
Here is a table I made from the collected NPM packages, featuring the most popular clipboard-related libraries on NPM:
Note: This table does not include browser-based copy-paste.
Features / Libraries | clipboardy | copy-paste | clipboard-cli | node-clipboard-wd | Electron (Not a library) |
---|---|---|---|---|---|
Pure JS | Uses system utilties | Uses system utilties | Uses system utilties | Chromium binary | Uses native bindings |
Cross-Platform Support | Yes | Yes | Yes | Yes | Yes |
Windows | Yes | Yes | Yes | Yes | Yes |
Linux | Yes | Yes | Yes | Yes | Yes |
MacOS | Yes | Yes | Yes | Yes | Yes |
Copy to Clipboard | Partial | Partial | Partial | No | Yes |
Plain Text | Yes | Yes | Yes | No | Yes |
Rich Text | No | No | No | No | Yes |
Images | No | No | No | No | Yes |
Files | No | No | No | No | Exposes binary API |
Paste from Clipboard | Partial | Partial | Partial | Yes | Yes |
Plain Text | Yes | Yes | Yes | Yes | Yes |
Rich Text | No | No | No | Yes | Yes |
Images | No | No | No | Yes | Yes |
Files | No | No | No | Yes | Exposes binary API |
Honorable mention: save-clipboard-image, uses AppleScript to save an image from clipboard to a file.
Summary⌗
Before writing this post, I was unaware that the result would be this. It appears that all of NPM’s clipboard libraries work the same way: they call built-in executables on their host operating system and return the data. Interestingly, none of them handle images, rich text, or files despite no reason they couldn’t deal with them the same way.
The only exceptions to this pattern are Electron and node-clipboard-wd
. The former is a framework unsuitable for usage as a library. The latter was written by myself last week as an experiment.
Where do we go from here?⌗
Software developers have crossed the clipboard bridge in other environments before. In fact, NodeJS developers have already crossed it in Electron, which uses native bindings under the hood. There is no reason we couldn’t do the same using a more lightweight C++ library.
I find clip particularly promising in this regard, and I’ve been looking into writing a NodeJS wrapper around it. For posterity, here is a table of possible native libraries that I have found so far:
Features / Libraries | clip | arboard | clipboard |
---|---|---|---|
Cross-Platform Support | Yes | Yes | Yes |
Windows | Yes | Yes | Yes |
Linux | Yes | Yes | Yes |
MacOS | Yes | Yes | Yes |
Copy to Clipboard | Yes | Partial | Partial |
Plain Text | Yes | Yes | Yes |
Rich Text | Yes | No | No |
Images | Yes | Yes | Yes |
Files | Exposes binary API | No | No |
Paste from Clipboard | Yes | Partial | Partial |
Plain Text | Yes | Yes | Yes |
Rich Text | Yes | No | No |
Images | Yes | Yes | Yes |
Files | Exposes binary API | No | No |