sveltejs / svelte

web development for the rest of us
https://svelte.dev
MIT License
79.71k stars 4.23k forks source link

Support for plugins #7100

Closed brandon942 closed 7 months ago

brandon942 commented 2 years ago

Describe the problem

I was considering testing svelte on a project. But it has no support for plugins (other than compilation preprocessors) which I could use to, for example, patch in runtime dependency handling.

Describe the proposed solution

A plugin would require an API for registering hooks globally like synchronous beforeCreate, afterCreate, afterDestroy for doing initializations and cleanup. With that one could integrate runtime state management libraries like mobx and have functions like watch(handler) that unregister the handlers when the component is destroyed. Without such global hooks one could maybe resort to using an existing instance to add on_destroy handlers like watch(closure, currentInstance) but it seems the official svelte build refuses to provide an api for that (#5517).

In addition a way to integrate observable arrays with template features like #each could be helpful. And did I mention JSX For when templates don't cut it?

Btw, I saw it mentioned somewhere that svelte is just a compiler that can be combined with more serious frameworks like react, angular, ... but I'd like to see how that could be accomplished considering all serious frameworks themselves have components and compilation steps.

Alternatives considered

-

Importance

i cannot use svelte without it

dummdidumm commented 2 years ago

Please elaborate more on what you want to achieve with these global hooks/plugins. You can use the existing lifecycle functions outside of Svelte components just fine, you just need to make sure they are called synchronously during component initialization.

// somejavascripefile.js
import { onDestroy } from 'svelte';

function watch(stuff) {
  const unwatch = ...;
  onDestroy(() => unwatch());
}

// svelte component
<script>
  import { watch } from '..';
  watch(() => ..);
</script>
}
brandon942 commented 2 years ago

@dummdidumm A simple example plugin could look like

// Plugin
import {beforeCreate, afterCreate, afterDestroy} from "svelte"
var toCleanup = new Map // instance => handlers[]
var currentInstance
beforeCreate(instance => currentInstance = instance) // the component initialization runs between beforeCreate and afterCreate
afterCreate(instance => currentInstance = null)
afterDestroy(instance => {
    var handlers = toCleanup.get(instance)
    if (handlers) {
        unwatch(...handlers)
        toCleanup.delete(instance)
    }
})
export function watch(handler) {
    watchEffect(handler);
    toCleanup.get(currentInstance)?.push(handler) ?? toCleanup.set(currentInstance, [handler])
}

Your suggestion can also work in this case but you can't combine the handlers so it can create a lot of closures. Of course these hooks can be used for other meta things that do not involve or are limited to the component calling a function during initialization. A plugin could support asynchronous registration, like how createEventDispatcher works. Your suggested example will not support that.

samclaus commented 2 years ago

@brandon942

Man, your issue is so incredibly vague. I tried to make sense of it and I really couldn't. I'm going to give you the benefit of the doubt and assume you're not a troll because your example code felt somewhat legitimate.

I come from working on a gigantic Angular frontend that is basically like a native app level SPA with tons of state and pages and forms and widgets galore. Angular locks you in to their stupid CLI project setup and Angular modules are basically just a waste of time on top of regular TypeScript imports. I'm not even sure I can get access to the WebPack config the Angular CLI uses behind the scenes so I can incrementally port components to Svelte.

Now, the beauty of Svelte is that every .svelte file will literally compile to a single JavaScript class. Svelte is basically just a file loader for WebPack or Rollup so that when it sees a file like UserContactCard.svelte, it runs the Svelte compiler to turn it into UserContactCard.js, with content akin to:

// NOTE: this is pseudo-code, look at the Svelte docs to see what the actual method names are

export default class UserContactCard {

    // Svelte-generated code for DOM manipulation..

    function hookupToDOM(parent, sibling) {
        // ...
    }

    // Svelte can even add getters/setters for props (inputs and outputs) that will
    // contain generated code to schedule DOM updates when you set the values

}

The elegance of this approach is that you can import that Svelte component as a regular JS class literally anywhere you want. You can mix and match vanilla JS (with your own custom DOM manipulation code) and Svelte components however you like. .svelte files are nothing more than a class boundary.

This means adding Svelte to a React or Vue project is as easy as adding the Svelte plugin for WebPack or Rollup (whichever you are using), write some Svelte component(s), and then import them into your TSX/JSX workflow. Use teardown hooks in your React component to then tell the Svelte child component to tear itself down. If you have a vanilla JS/TS module for UI like modals or popup notifications ("toasts"), you can add a function to that file that takes a Svelte component for a custom toast and then calls its hookupToDOM(...) (or whatever the real method name is) with your toast container DOM element reference that your code creates at the root of the DOM on page load or whatever.

Regarding the thing you said about interop with {#each}, I am not sure, but we may have the same opinions there. Like I said, I work on a big app which essentially talks to a bunch of CRUD (create-update-delete) APIs with GET requests to refresh the whole list of items from the database. My TypeScript code makes heavy use of a custom SubscribableList interface which is basically like any pub-sub observable (Svelte stores and others), except it can either emit a brand new array of objects (when information is refreshed from the backend) or it can emit more fine-grained events like "an item was modified" or "an item was deleted". I compose these lists to do simple operations like filtering (whenever the filtered list gets an "item was added" event, it only has to run the predicate on that ONE item instead of the whole array again), and also to do aggregate for one-to-many and other relations between collections. The frontend is sort of like a reactive SQL engine, and every list and table in the UI is sort of like a top-level query that needs to get granularly updated whenever one of the data sources further down in the pipeline gets updated. It would be cool if I could custom hook into Angular's *ngFor or Svelte's {#each} to explicitly tell it "hey this ONE item was added, you don't need to a diff of the whole list". AFAIK, none of the frameworks support that, and if you want it, you have to manually write the DOM manipulation code yourself--which you can easily do with Svelte by binding an element in the markup to a variable and then passing that DOM element variable along to your own list/table rendering code.

As far as "plugins" go, Svelte seems like it hit a sweet spot for simplicitly and elegance. Like I said, you can also mix and match it with vanilla JS when you need to write custom code for performance (like rendering big data tables intelligently). It does not try to add too many fancy abstractions over the native DOM elements and it does not try to hide stuff from you and take over your whole life with an enterprise workflow (lookin' at you, Angular). Probably best not to try and make Svelte yet another "do it ALL" highly opinionated framework, and keep it at a lower-level so you can mix and match it just like you build up trees of functions in any programming language.

samclaus commented 2 years ago

Also, what do you mean by "serious frameworks like Angular and React"? Angular is heavily opinionated, but I would argue it gets a number of things wrong, and that quickly becomes a problem when you're locked into the workflow with a gigantic application and you want to shop for alternatives. Svelte may not have an equivalent for Angular Material, but I literally skipped all the other "1st party" parts of Angular like the HttpClient (I just use a thin layer on top of fetch API) because I despise RxJS and I think its a way for people to keep busy without actually doing much--maybe I'm just not smart enough to understand RxJS though..