Using external JS Libraries in TW Plugins

What is the best way to use external js libs in TW plugins? Is there a way to compile them into a tiddler?

1 Like

This question may be more suited to the Developer area.

In my view the answer to your question is yes, but depends on some factors. This is my opinion that follows, I am no expert. I am answering from my summertime Timezone +11 UTC

Other’s can answer this question a lot better but one thing of note is a number of existing plugins already use Javascript libraries, such as for extended date and maths functions as an example. In many ways, anything that could be used in a browser is possible, as I understand it they can be loaded internally or externally, initially as you would in any HTML JavaScript solution.

  • I presume you are aware there is a way to load TiddlyWikis own core externally already, a version of which is or will be implemented on top of tiddlyhost?

Before you go ahead, consider telling us what you need and hope to achieve, it may already have being done in a plugin already, or a similar one at least. Or already possible nativly.

The issue that remains is accessing the functions within the library because they need to comply with TiddlyWiki’s model, typically through macros, widgets and action widgets. At first this may seem onerous, but keep in mind, apart from the fact you must integrate it appropriately to make it work, that once you do so, you gain the following benefits;

  • The features you expose become available within the TiddlyWiki platform and can thus make use of the UI, transclusions, and refresh model so compared to other implementations. You open that library to be used in many different ways, that would be hard to do if included in specific function software.
  • Others can use TiddlyWiki to illustrate the power of a library and give people the opportunity to experiment and identify gaps and feed back to the original project.
  • Libraries can be updated quite easily even if additional integrations and features still need to be made available.

I am spelling this out because there is a vast number of great open source libraries I believe would be “given Wings to fly” if someone puts the effort into integrating them via a plugin with TiddlyWiki. Others can tell you more as I said, but I have a deep commitment to TiddlyWiki as a platform. Here are some tips from my particular perspective that may help.

  • Complex User Interface related libraries may be harder to incorporate than others because TiddlyWiki already has a rich and powerful User Interface.
  • If you are integrating a library for particular features consider versions of the library that you use, are not full of features, you don’t plan to make accessible.
    • Either find smaller packages that address just what you need. To minimise the size of what is needed, because one of the benefits of tiddlywiki is its small size. Or expose many of the functions there in.
  • Try and expose other functions in your library that may be of use to someone, even if it is not the key functions you need, you never Know what people may find useful. The functions you use can also be exposed in a way that people of different skill levels can utilise.
  • The main trick is not to clash with existing tiddlywiki macro and functions.
    • I believe there are already solutions and libraries in use with functionality that has not yet being made accessible to TiddlyWiki, users, designers and developers.
  • If you spell out what you are looking for a lot of people are surprised to find out it is already possible in tiddlywiki, give people a chance to tell you how you may already be able to do it.
  • Consider trying to engage the library owner to discover an interest in tiddlywiki, they may discover the advantages and help you, now and in the future. I see a lot of great libraries that can only be used by developers writing programs that get no other interest, but if integrated with tiddlywiki could become available to a larger audience. TiddlyWiki could even present a platform on which people can “play with the library” interactively, even host a working example of their libraries functions.
  • Finally if you are trying to extend some fundamental features of TiddlyWiki it already has a long history guided well be @jeremyruston and most approaches have already being considered. Seek advice before you put in too much effort.
1 Like

Hi @GameDungeon – the preferred approach is indeed to package the library as a JS tiddler that can be accessed with require(). You can see a number of examples of that approach in the plugins folder of the TW5 repo.

The idea is that we always copy the full, versioned source of the library into the TW5 repo (ie we don’t use npm to dynamically pull them down from the internet). To simplify upgrades, we try not to modify the original files. In some cases we use the tiddlywiki.files file to wrap libraries with a custom prefix/suffix to adapt the calling convention.

The other approach is to include the external library as an ordinary script tag in a raw markup tiddler, but nowadays that seems to seldom be necessary.

Do feel free to ask any specific questions – GitHub might be more appropriate for some of these discussions because of the improved support for embedding code within posts.

3 Likes
  1. Use GitHub - tiddly-gittly/Modern.TiddlyDev: Modern TiddlyWiki Developing Environment or GitHub - tiddly-gittly/TiddlyWiki-TS-Plugin-Template: Typescript plugin template for TiddlyWiki5 to pack your JS library as .tid file, and get auto-reload during development (There may be other template repos, search for posts in the forum like Best TW Dev Framework! Easy WYSIWYG plugin developing, support TypeScript to find them)
  2. You can refer to some of my plugins

Pack the npm package with your code that uses this package, as a single tid

for example, I pack @fullcalendar/core along with it initialization code into a tid here tiddlywiki-calendar/widget.ts at 34d8f23bea4eb44bc916d49449023a69f96c07b3 · tiddly-gittly/tiddlywiki-calendar · GitHub , and pack @tldraw/core in tiddlywiki-whiteboard/Tldraw.tsx at a62abf4c432b9aa452ffc1982921741b129026d9 · tiddly-gittly/tiddlywiki-whiteboard · GitHub

There also need to be a .meta file with them tiddlywiki-calendar/widget.js.meta at 34d8f23bea4eb44bc916d49449023a69f96c07b3 · tiddly-gittly/tiddlywiki-calendar · GitHub

In this way, library will only be accessible in your code, can’t be reused by other plugin authors.

Put dependency into a single tid file, and set type as library, use it later

See tw-react/react-dom.js.meta at 711edd33df92d0288d75ec98d2532e4f983e760f · tiddly-gittly/tw-react · GitHub which has module-type: library

And I will run a script in github action during release process, to download js file from unpkg, place then along with the meta file, rename them to be same as meta file tw-react/download-react.mjs at 711edd33df92d0288d75ec98d2532e4f983e760f · tiddly-gittly/tw-react · GitHub

So my other plugins can load this js file using require tiddlywiki-whiteboard/widget.ts at a62abf4c432b9aa452ffc1982921741b129026d9 · tiddly-gittly/tiddlywiki-whiteboard · GitHub

Using library in start script, and inject code to window

I have a library code that will immediately inject an instance to window.ipc, which will be used later in other plugins. So I import it directly itonnote-plugin/electron-ipc-cat.ts at 8ff66c0156c1d3de03ec7c91ecdd5af41528b8ac · tiddly-gittly/itonnote-plugin · GitHub

import 'electron-ipc-cat/fixContextIsolation';
console.log('electron-ipc-cat/fixContextIsolation');

with meta file

creator: LinOnetwo
title: $:/plugins/linonetwo/itonnote/Startup/electron-ipc-cat.js
type: application/javascript
module-type: library

then load it in a startup script, so this injection only happened on the browser side, and happened after startup. (I can’t do import in the if statement (ES6 limitation), but I can do require in the if statement, so I wrap the code as a library first, then require it in the if statement conditionally)


/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
exports.name = 'install-electron-ipc-cat';
exports.platforms = ['browser'];
exports.after = ['startup'];
exports.synchronous = true;
exports.startup = function () {
  if ('service' in window && 'descriptors' in window.service && window.service.descriptors !== undefined) {
    require('$:/plugins/linonetwo/itonnote/Startup/electron-ipc-cat.js');
  }
};

with meta file

creator: LinOnetwo
title: $:/plugins/linonetwo/itonnote/Startup/install-electron-ipc-cat.js
type: application/javascript
module-type: startup
2 Likes

For example:

3 Likes

I know it’s been a while, but I finally started the project that I made this post for. I ended up using modern. When using the external libraries TW now errors and I can’t seem to fix it. TypeError: Cannot read properties of undefined (reading 'navigator') I can’t tell if it’s being imported wrong or if something else is the issue. (I do know that adding the import is what causes the error.)

import { IWidgetEvent, Widget } from 'tiddlywiki';
import { widget } from '$:/core/modules/widgets/widget.js';
import * as pdfMake from 'pdfmake/build/pdfmake';
import htmlToPdfmake from 'html-to-pdfmake';
import 'pdfmake/build/vfs_fonts';

class ExportAsPDF extends widget {
    render(parent: Element, nextSibling: Element) {
        this.computeAttributes();
        this.execute();
    }

    invokeAction(triggeringWidget: Widget, event: IWidgetEvent) {
        var tiddlers = this.wiki.filterTiddlers("[all[]]", this);
        var html = htmlToPdfmake(`
        <div>
            <h1>My title</h1>
            <p>
            This is a sentence with a <strong>bold word</strong>, <em>one in italic</em>,
            and <u>one with underline</u>. And finally <a href="https://www.somewhere.com">a link</a>.
            </p>
        </div>
        `)

        pdfMake.createPdf(<any>html).download();

        console.log(tiddlers);

        return true; // Action was invoked
    };
}

This is a common problem when developing JS plugin, when plugin run in nodejs, it can’t access to browser API like navigator. And usually the access to navigator is done by some npm dependency, so you will be even more confused.

Solution is to check if you are in a browser environment, then load the real plugin code:

3 Likes

Something I find interesting, I see in a lot of these the requires are written in tiddlywiki format instead of a file path. I could be wrong but both seem to work?

It would be great to have a solution where big libraries like vis,js and or in the case of the whiteboard the react-library are only downloaded if they are used by a dedicated layout.
Is this technically possible?

It would be technically possible to dynamically load third party JS libraries. I think the difficulties are practical and philosophical: where should the files be downloaded from? If TiddlyWiki is being used without a server, then the only option would be to download the files from a shared library location, which has privacy implications.

1 Like

Hi @jeremyruston, it would be nice to have the option to replace a version of the plugin that contains the files with a slim one.
Of course, it is good to have the security that everything will work offline, but when I used TW as a schoolbook I offer for download, I would like to be it as small as possible and only fetch big libraries if necessary.
It would be an idea to furnish both: a slim version and an emergency.json for standalone.

@linonetwo 's whiteboard-plugin would be a great use case: At the moment, the feature of collaboration built-in in excalidraw is chopped off, to keep the plugin small - which seems completely reasonable, because it is intended for standalone use. Here for example it would be great to have a slim collaboration version making use of a server and a standalone version containing all the necessary files.

Hi @JanJo

To be clear, there’s nothing to stop any plugin making custom arrangements to dynamically load JavaScript code. My comment is from the perspective of the support in the core.

2 Likes

I already extract react reactjs library into tw-react plugin, and set it as dependency of whiteboard plugin. So it only be downloaded if you try to download whiteboard plugin from a plugin library that includes two of them.

used by a dedicated layout

You mean download at background? That maybe possible currently (try use js to auto add plugin (or even just add js tiddler) from every installed plugin library), but for security concern, that will still require a refresh of page to take effect.

1 Like

Hi @linonetwo, I think the plugin you made is important because it allows to keep the results of the whiteboard also functioning if you are offline (resp. if the server to download a js from is not available.
It would be great to have a version where the code is downloaded only in case you use the whiteboard.
And in case of the whiteboard-plugin this server-based approach would have the advantage that it also could be used for collaboration.

Seems you are not using tw as a personal wiki? If so, if someone don’t use white board plugin, he won’t download this from the plugin library.

But i also found scenario for personal wiki.

For a lazy loading wiki, maybe you want whiteboard widget (including JS tiddler of this widget) to also lazy loading. Until tiddler containing the widget call wikify.

This will require a lot of work to do in the lazy loading mechanism.

1 Like

I would like to use the whiteboard-plugin for two reasons:

  • for a as a collaborative board in a classroom-wiki for this a small size and the possibility to cooperate would be great.
  • to sort ideas like in this usage of TLDraw within LogSeq for this the unbreakability and the possibility to transclude are of vital importance.

(Alas I know these goals might exclude one another.)