-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Meta] Plugin system for 2.0 #2635
Comments
Related PR: #2671 I made a crate to pass AST nodes as a special form to the plugin and this is the result I got. Due to limitation of testing system, I also profiled the performance of passing data to plugins using JSON . Note that in the real-world, DeletedI forgot to configure the memory allocator. So I updated the image above. |
Excited to see progress here! I think there's still one potential issue with this approach. Adding/updating/removing fields from AST nodes is potentially a breaking change when types are nested without boxes. For example: struct Foo {
foo: u32,
bar: Bar,
baz: u32
}
struct Bar {
test: u32
} Now say you add another field to the To avoid this you'd have to box every single nested struct (which might not be great for perf), and also make sure that you only ever add fields at the end. Another way could be to make accessor functions and have plugins only use those instead of accessing fields directly. Eg Or maybe you just decide that AST changes can only be done in major versions. Not sure how often they occur. |
@devongovett Thank you for the feedback! Actually And It also supports making I'll make all fields of Problem is that the current design makes 'unknown' variants and fields dropped by the plugin. Thoughts:
How do you think about these ideas? |
Ah interesting, I missed that. Looking at the docs it seems all field accesses end up becoming methods (returning
What are the downsides of this? It seems like this would avoid some extra AST conversions as well, so maybe better perf too? I guess the difference would be that the API is slightly different for plugins vs in SWC core, but I think this is ok (and maybe expected?). I would think you would want to expose a more limited API to plugins anyway to avoid maintenance problems. Babel had this problem - they exposed everything in core to plugins, and were stuck not being able to make changes because plugins started depending on too many internals. I would recommend being quite intentional about what is exposed to plugins. Limit it at first and expand over time as things get figured out and requested by plugin authors.
I could imagine plugins wanting to run before stripping TS as well, e.g. a documentation generator, or code mod system.
Yeah that sounds hard. |
Oh, I see. Yeah, something similar to
Exactly. It will avoid lots of conversions. I was simply trying to expose identical API as core transforms, but I think you are right. The problem of babel is quite interesting. Thank you so much for sharing it. I concluded that exposing |
I found that |
One downside of using |
Which libraries would use the |
|
If the two ASTs are similar enough, perhaps you could make a single swc-ast crate and use |
@dwoznicki Sadly it's not the case, and as feature flag is global, it will break lots of code. |
@kdy1 Will Plugin 2.0 support the lifecycle of Babel's |
I did some more investigation. I switched wasm runtime to Time for loading + 100 invocation:
plugin system based on a dynamic library is faster, but it's really a headache to publish the plugin to all os, arch and abis. Additional notes
But this will not be officially supported, at least for now.
This is just possible, and not going to be implemented in the initial version of v2.
|
TL:DR, it is a major shift but we'd like to propose plugin interface based on wasm instead of native dylib binaries. There are notable wins as shared above if we can accept some of costs of cold startup time. Still SWC will need to care about breaking changes around AST itself but abi becomes lot stable, also it doesn't require to build several native binaries per each platform. As same as current codesbases, this plugin api changes will live under |
A huge downside of WASM is that there is no memory sharing so you have to serialize and copy everything into and out of WASM's memory. That will be the biggest performance bottleneck of this approach I think. Probably not much better than writing plugins in JS. In your comparison you are doing this in both wasm and dylib versions, but if your dylib didn't serialize/deserialize and instead used shared memory it would likely be much faster. |
Actually, dylib version also serializes and deserializes. But you are right, most of the time is used by serde, both for wasm and dylib-based plugin. |
Yeah that's what I meant. |
Unfortunate thing in here is we really don't have a satisfactory solution to solve all the problems we have. We either have to suffer with building native binaries per platform or bite (some) of the costs by allowing portable formats like wasm. @kdy1 did some quick measurement with current serialization approaches and numbers are indeed not something we can easily accept. However, so far it looks like the major cost comes from serialization itself, not for the part of copying bytes across host-to-guest memory spaces. If it's true, and if somehow acceptably faster serialization approach can be adapted we may be able to pursue wasm based plugin approach. I am aware these are based on a lot of if and I do not think we can conclude at this point though. The reason I'm in favor of wasm-based approach is it definitely lowers barrier to the ecosystem by reducing some of pain points. Writing non-js-plugins is already a barrier from the beginning, but logistics around building several native binaries & publishing add a lot more complex problems on top of that. Wasm at least consolidates build targets as well as allowing several languages other than rust. |
Sure. I think it would be pretty simple to allow both though. The API would be identical between native and WASM for plugin authors, and it seems like it would be pretty easy to support loading both dylibs and WASM within SWC. That gives plugin authors the choice. |
I would like to avoid to open up SWC attempts to support both due to we don't have a single solution satisfies all. I mean we may eventually land there, but that means SWC should support both ways to work gracefully for the authors and consumers of the plugin or otherwise it'll be hard ecosystem fragments for the users. Opening up both interfaces gives flexibility to the author but users cannot choose which type of plugin to use unless there are identical plugins written in both ways, and in the worst cases, it can cause both problems like some plugins written in wasm degrades perf and some other native plugins exposes issues around native binaries like not having proper prebuilt binaries for the platform. For now I'd like to push forward one approach first and see what kind of blockers we'll see. Decisions can be made based on the results. |
I think we can support wasm plugin without regressing performance. I used
This does not solve the AST compatibility issue and the compatibility issue requires more investigation. |
I just saw the integration of Wasmer into SWC... it's awesome! PS: one of the advantages of using Wasmer is that also works fully on the browser via wasm-bindgen, so it can run properly on the SWC playground. Not sure if you have already tried it, but we can help on make it work! We are building some services that will help on automatically cross-compiling the wasm modules on the cloud. Would that be interesting for SWC? |
Yes, and I want it now actually :( Seriously, we were considering something similar, although it is a simple subcommand and uses the CI system the author choose. If this process can be easier, I'd love to see some integrations. |
@syrusakbary One more thing. Plugin authors will typically be users, but not customers. Is it fine? |
Yeah, that is completely fine. Do you think that would work for your use case @kdy1 ? |
Not kdy1 (😅 ) but it sounds reasonably workflow. I have one question regarding on those - How does it affects for the local / CI dev for the consumer point of view? Originally, our assumption is simply let each plugin is being published into npm registry and it can be listed under npm's package management for the dependency management. Peeking wapm bit, it looks like it needs separate pkg mgr for wapm registry (or either install wasmer)? |
I'm not sure about wapm at the moment, but it sounds reasonably simple so I think it will work. |
This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you. |
I will modify this as required.
Javascript plugins
If it's possible to make it fast, I'll support js plugins, too.
Rust-based plugins
Tasks
Task: Proxy
swc_common
(#2646)swc_common
: Create a trait for thread-local APIs. (cargo featureplugin-base
)swc_common
: Implement the trait. (cargo featureplugin-rt
)swc_common
: Replace APIs in plugin mode. (cargo featureplugin-mode
)swc_common
APIs.Task:
rplugin
: System to pass AST directly (#2671)This is important for performance.
Task: Share more input, like
SourceMap
orSourceFile
.Task: Actaully use
rplugin
.Task: Improve plugin runner
Task: Add
plugin
feature toswc
Task: Expose node apis
Design
I experimented lots of ways for plugin system, and decided to go with abi_stable. But there's a problem.
Problem: Identical API
I want to provide identical API as core transforms. And it requires sharing data stored in thread-local variables.
Triage: Dynamic library
I initially thought I need to make
swc_common
a dynamic library so plugins can invoke it. But dynamic linking does not fit for swc plugins. Asswc_common
depends onstd
, the version ofrustc
should be all same among plugins, and chaning version ofrustc
becomes a huge breaking change. I don't want to break all plugins by upgradingrustc
. So wee need another way than a dynamic library.I posted some questions on the rust community while trying this option, in case you are curious about what I tried.
I also experimented with
extern "C"
. See https://github.com/kdy1/rust-dylib-testSolution: Pass function to plugin with stable abi
Instead of making
swc_common
dynamic library, we can pass a big trait that contains functions related to thread-local variables inswc_common
.The plugin runner will pass this trait while invoking plugins. abi_stable can be used to do this.
For plugins, operations of
swc_common
will be replaced by the trait. In other words, functions inswc_common
will work by calling a method in the passed trait object.The text was updated successfully, but these errors were encountered: