sveltejs / svelte

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

RFC: Convention for packaged components to export uncompiled templates #604

Closed Rich-Harris closed 3 years ago

Rich-Harris commented 7 years ago

586 and #603 highlight an interesting question. If a component nests another component that was compiled with a previous version of Svelte...

<Widget/>

<script>
  // node_modules/some-component-library/[pkg.main].js is precompiled
  import { Widget } from 'some-component-library';

  export default {
    components: { Widget }
  };
</script>

...it's more likely that changed internal implementation details (that are not considered, rightly or wrongly, part of the public API) will result in breakage.

At the same time, using precompiled components prevents us from sharing code effectively, resulting in larger bundles with functions that take longer to warm up.

So while it's certainly useful for some-component-library to ship precompiled components so that people can do this in non-Svelte apps...

import { Widget } from 'some-component-library';

const widget = new Widget({
  target: document.querySelector('.widget-container')
});

...it would be great if bundler plugins could avoid precompiled code where possible.

Proposal: pkg.svelte

Suppose the author of some-component-library included the following in her package.json:

{
  "main": "dist/some-component-library.umd.js",
  "module": "dist/some-component-library.esm.js",
  "svelte": "src/index.js",
  ...
}

A module-aware bundler (e.g. Rollup or Webpack) would import the pkg.module, a bundler like Browserify would import pkg.main.

But an app that was already using Svelte could import pkg.svelte, and any components re-exported from index.js would be compiled by the app's build process. This would be trivial to implement in rollup-plugin-svelte (just need to add a resolveId hook), and I assume it's possible in svelte-loader.

We could also resolve imports like this...

// resolves to node_modules/some-component-library/src/Widget.html
import Widget from 'some-component-library/Widget.html';

...by adding a "svelte.root": "src" property, analogous to "modules.root" in this document. (If the app author imported some-component-library/src/Widget.html directly, it would still work, assuming that the resolution logic saw that src/src/Widget.html didn't exist and fell back to not using pkg['svelte.root'].

Breaking changes

This wouldn't eliminate the possibility of breaking changes causing havoc — if a Svelte 3 app imported a Svelte 1 component which contained some long-since-deprecated syntax, the compilation would fail. But it gives us the opportunity to anticipate that breakage (the bundler plugin could compare the versions of Svelte that the app and the imported component depended on) and handle it at compile time with a helpful error message, rather than at runtime with a cryptic one.


Does this sound like a good idea? Is there anything obvious that I'm overlooking?

PaulBGD commented 7 years ago

I think it'd make more sense to just make the entire mounting/destroying API stable between versions. I'm sure there's some downsides to that though.

Rich-Harris commented 7 years ago

Yep, we should certainly strive to minimise breaking changes. #592 is a case where the current behaviour is buggy though, and we're offered a choice between fixing that bug or preventing breakage in edge cases involving precompiled components. And there's more surface area to consider than just unmount/destroy — #586 was about something unrelated.

So I definitely agree, but I think we have to have a strategy that allows for some flexibility in implementation details, and allowing packages to expose uncompiled components has that plus the additional size/performance benefits.

TehShrike commented 7 years ago

I like this idea.

I love Svelte for allowing me to publish a vanilla-JS component without charging a high framework-byte-size tax to anyone who wants to pull it into their Angular/React/whatever app.

On the other hand, I do feel slightly bad to be charging the few extra bytes for the folks who are bundling a Svelte app and want to minimize duplicating shared functions.

So, this is pretty great.

My only real contribution besides happiness is: you may want to respect pkg.engines.svelte (see pkg.engines) to allow folks to specify "svelte": "^3.0.0" or whatever when they know their component isn't compatible with older versions.

nsaunders commented 7 years ago

Initial reactions:

  1. NPM documentation seems to encourage us to do as much preprocessing as possible before publishing a package, basically to reduce the burden on the client. I believe this is why distributing, say, TypeScript source via NPM is often discouraged in favor of compiled JavaScript plus type definitions. So with regard to our component framework, I prefer to keep Svelte completely as an implementation detail and not bother the client with it at all if possible.

  2. In some cases, our components require additional preprocessing (LESS, for example) before we hand the source off to Svelte to compile. This additional preprocessing is achieved through Webpack loaders. Most of the time, the client probably will be using Svelte, but usually its application components will not require all of the preprocessing that the framework components do. So it would be kind of unfortunate for the client to have to configure multiple Webpack loaders to compile our .html source. Hopefully this makes sense.

But aside from these points, I actually would consider the interaction between Svelte components as more than an implementation detail. It is OK if the interaction changes, but to me they are public API and somehow must be detected so that we can properly version our components. But even at that, we still need the component package to be able to tell the client what versions of Svelte will emit code compatible with the component's API...almost like a peer dependency, except optional.

I'll have my colleague review this issue when he returns from vacation next week and see if he has any ideas to offer, and of course I'll also keep noodling on it myself. Otherwise, I'd suggest maybe we keep this issue open but wait on implementing a solution until we have more input...

Thanks guys and @Rich-Harris you da man!

PaulBGD commented 7 years ago

Now that I think of it, one really big reason to distribute the original .svelte files is so that you can use other renderers such as SSR.

nsaunders commented 7 years ago

Indeed I don't think we have to go out of the way to exclude the source or anything like that, but I think the NPM docs linked above are just trying to encourage us to reduce client burden.

Rich-Harris commented 7 years ago

As of version 2, rollup-plugin-svelte respects pkg.svelte and pkg['svelte.root'] (README). Version mismatch warning and pkg.engines support etc is on the TODO list.

nilchaos87 commented 7 years ago

A note regarding Webpack: I don't think svelte-loader requires any modification; rather, the Webpack configuration will need to specify "svelte" in the resolve.mainFields setting and avoid excluding node_modules in the rule that applies svelte-loader.

qm3ster commented 6 years ago

Will webpack 4+ module types affect this? (svelte module type all the way up)

madeleineostoja commented 3 years ago

Sorry if this ancient issue isn't the place for this, but I couldn't find anything else open that discussed it — am I correct in thinking that there's currently no way of distributing compiled Svelte components? I think this is a dangerous step backwards in DX for distributed components/component libraries. As a basic example, I've authored a few Svelte components using typescript (both in the component and utils in .ts files), which would now require every consumer of the component to setup both Svelte's typescript support and an ability to import typescript files.

No other large ecosystem that I know of on the web forces the bundling and tooling support onto the consumer. Seems like a big gotcha for authoring reusable Svelte components.

dummdidumm commented 3 years ago

Pointers to issues related: https://github.com/sveltejs/component-template/issues/8 https://github.com/sveltejs/component-template/pull/31

pngwn commented 3 years ago

We hope to address this with svelte/kit.

madeleineostoja commented 3 years ago

Excellent to hear svelte/kit will address this!

That would hopefully pave the way for more idiomatic usage of compiled svelte components in the wider ecosystem, assuming that whatever mechanism svelte/kit uses is adaptable in other contexts?

As an aside, would svelte/kit allow the use of webpack as a prod bundler for snowpack, since webpack/rollup are just plugins for snowpack? Because I've had nothing but trouble with rollup as an app bundler (as opposed to library bundler, which it excels at), and being forced to use it could be a dealbreaker for a lot of people. Something as simple as handling font files in CSS was a massive pain in rollup, and a one liner in Webpack.

benmccann commented 3 years ago

I'm going to close this as implemented: https://kit.svelte.dev/docs#packaging

pkg.svelte could possibly be referenced in the SvelteKit docs. Right now I only see it in https://github.com/sveltejs/rollup-plugin-svelte#pkgsvelte