How (And When) to Publish a Package

We cover the mechanics of publishing your first Elm package. Best practices for making a great package, and how to learn the API design skills to build great tools.
May 11, 2020

What is an Elm Package?

  • Elm package repository
  • Elm packages enforce SemVer for the public package API
  • The SemVer (Semantic Versioning) Spec
  • Interesting note: the SemVer spec says breaking changes only refers to the public API. But a core contributor clarifies that breaking changes can come from changes to the public contract that you intend users to depend on. See this GitHub thread.
  • list-extra package
  • dict-extra package
  • Minimize dependencies in your package to make it easier for users to manage their dependencies
  • "Vendoring" elm packages (using a local copy instead of doing elm install author/package-name) can be useful in some cases to remove a dependency from your package

Should you publish a package?

  • The Elm package ecosystem has a high signal to noise ratio
  • Elm packages always start at version 1.0.0
  • SemVer has different semantics before 1.0.0 (patch releases can be breaking before 1) - see SemVer spec item 4

Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.

  • Elm package philosophy is to do an alpha phase before publishing package

Keep yourself honest about solving meaningful problems

  • Start by asking "What problem are you solving? Who are you solving it for?"

  • Scratch your own itch

  • elm-graphql

  • servant-elm (for Haskell servant)

  • Keep yourself honest about solving problems by starting with an examples/ folder

  • Early elm-graphql commits started with examples before anything else

  • Write meaningful test cases with elm-test

  • elm-verify-examples

  • Have a clear vision

  • Ask people for feedback

  • Let ease of explanation guide you to refine core concepts

Make it easy for people to understand your package goals and philosophy

Porting libraries vs. Coming Up With an Idiomatic Solution for Elm

  • Instead of moment js ported to Elm, have an API built for a typed context
  • Ryan's date-format package

How to design an Elm package API

Pay attention to how other packages solve problems

Pick your constraints instead of trying to solve every problem

  • Helps you choose between tradeoffs
  • Having clear project goals explicitly in your Readme makes it easier to discuss tradeoffs with users and set expectations
  • Idiomatic elm package guide has lots of info on basic mechanics and best practices for publishing Elm packages

The mechanics of publishing an elm package

  • elm make --docs docs.json will tell you if you're missing docs or if there are documentation validation errors
  • elm-doc-preview
  • Can use elm-doc-preview site to share documentation of branches, or packages that haven't been published yet
  • Set up a CI
  • Dillon's CI script for dillonkearns/elm-markdown package
  • Dillon's elm-publish-action GitHub Action will publish package versions when you increment the version in your elm.json - nice because it runs CI checks before finalizing a new package release
  • elm publish will walk you through the steps you need before publishing the first version of your Elm package
  • #packages channel on the Elm slack shows a feed of packages as they're published
  • #api-design channel on the Elm slack is a good place to ask for feedback on your API design and package idea

Continue the Conversation

Share your package ideas with us @elmradiopodcast on Twitter!


Hello, Jeroen.
Hello, Dillon.
How are you doing today?
I'm doing pretty well. How about you?
Not too bad. Can't complain.
We do have an exciting topic today.
So it's going to be how and when to publish an Elm package.
Yes, another tough one to squeeze everything into one episode, but here we go. Let's do it.
Yeah. So let's talk about what an Elm package is.
So if you want to share your code, your library in Elm, you want to do it through an Elm package.
So it's a repository that you publish on the Elm package registry,
and that people can then install through Elm install your package name.
So that's the basis.
Yeah. And so there are some unique things for people who are used to other package ecosystems
like NPM for JavaScript or Ruby gems, things like that.
There are some things that are unique because of Elm and because of some decisions of the Elm package workflow.
So maybe we should talk a little bit about what makes Elm packages unique.
So first of all, it enforces semantic versioning.
You've probably seen the versions of Elm packages or NPM packages that are of form X.Y.Z, like 1.0.0.
Well, those are enforced in Elm. So all packages started with version 1.0.0.
And then every time you make a breaking change, the Elm compiler enforces that you publish a major version.
So that would be 2.00.
Right. Where a breaking change is as far as the API is concerned.
So if you remove an existing API function,
if you change an existing type in a way that breaks users code in a way that users can depend on,
that would be a breaking change.
Yeah. But if the implementation changes in a way that is noticeable by the user,
that will not be considered as a breaking change.
Right. Which is interesting because it's a little bit different than semantic versioning in a sense, right?
Because you could technically have a breaking change where the API doesn't have a breaking change.
Yeah. Maybe it's more like API versioning.
Exactly. It's kind of API semantic versioning. Yes.
And so that allows you to depend on a package range.
So you can say, I depend on the list extract package,
and I can have version between version four and before version five.
But I can't support version five because by definition,
it's going to have a breaking API change which may possibly break my code.
So I think that's kind of the main reasoning is you can say,
I depend on this code and allow it to upgrade.
But of course, it still is possible for the code to break,
even if it's a non breaking change, quote unquote, as far as the API is concerned.
But in practice, that happens a lot less than JavaScript.
I'm a lot less afraid to do a minor or patch upgrade in an Elm package than I am in an NPM package.
Yeah, me too.
Another thing that's kind of unique about Elm packages,
I don't think any other package ecosystem has this sort of semantic versioning design,
which is one of the really unique things about Elm.
Not that I know of.
It's a pretty impressive feature.
I mean, as we said, semantic versioning only applies to the API itself,
not to the actual code.
There's not really a way to automate semantic versioning and say that your code won't break.
But it's quite nice.
But another thing that's different about the Elm ecosystem compared to Ruby or JavaScript
is that you can't publish a package that just does global side effects because of the nature of Elm.
And you also can't publish things like ports.
So you can't publish any JavaScript in there.
So that makes it a lot easier to depend on packages and to focus on just looking at the API
and saying, does this code do what I want it to do?
And you can pull it in with more confidence.
So anyway, that's sort of like a basic overview of some of the things that are interesting about the Elm package ecosystem.
Also, with code splitting at the function level that we get from Elm,
you don't have to split off packages into these tiny pieces like things like...
Load dash.
Load dash having a package for like every single set of functions grouped into these like ultra granular sizes.
There are about 200 functions in there.
So that's about 200 packages.
That seems so unmaintainable to me as a user.
But you do get into an interesting thing where sometimes you'll depend on a package that,
for example, something like Elm community packages like list extra, dict extra, these kinds of packages.
A lot of different packages depend on these.
And sometimes you'll have a major version update.
So it's a breaking change for some reason.
Maybe some function is removed.
For Elm community list extra, do you mean?
Yeah, exactly.
Let's say that something in Elm community list extra gets removed.
Some function is removed for whatever reason, hey, this is a better name for this function.
Maybe a function gets renamed.
And now it's a major version bump.
But if you had a package as a package author and you depend on that package,
even though it's a major version upgrade, you could safely upgrade.
Maybe your users could safely upgrade if they're also depending on Elm community list extra.
But because as far as Elm is concerned, it's a major version change.
It's a breaking API change.
Even if you and your users were not using that function that was renamed, everybody has to upgrade.
And so that can become kind of a nuisance.
So it can be kind of nice as a package author to reduce the number of dependencies that you have.
And so sometimes, to be honest, I find myself either quote unquote vendoring,
like copying in the list extra package, or just copying the function that I need into a little helper module.
Because for users, it can be hard to kind of manage these different versions of dependencies
because they might depend on another package that hasn't updated its list extra dependency yet.
And so that becomes this weird sort of chicken and egg problem.
Because you can't have two different major versions for a package in your application or your project.
Yeah, that's something that I vendor code a lot of too in Elm review,
because a lot of packages will depend on it and do want them to all be able to coexist.
So I encourage to vendor code, which is not great,
but that's the limitation that we have with Elm compiler at the moment.
Few pain points with the Elm compiler at the moment.
Right. I mean, the experience overall is quite nice.
And it's not, I wouldn't expect it to solve this problem.
But that said, I think that there is a way to come up with really creative solutions to this problem.
And it could be an interesting space to explore at some point.
But our title sort of hints at this big question, which I think is a really important one.
Should you publish an Elm package?
What do you think, Jeroen?
Should the listener publish an Elm package?
Don't do it.
There you go.
All right. See you next time.
See you next time.
In the NPM ecosystem, there are a lot of tutorials about how to publish your own package.
And it just shows you, hey, do this, create this project, run NPM publish, and you're done.
And a lot of people do it.
A lot of people do publish their package, even just to try it out.
I feel like in the Elm community, we try not to do that.
And we try to have a much higher bar for packages.
And I think in the long run, that will serve us very well.
Because we don't want to end up with millions of packages like the NPM registry has.
I don't know, hundreds of thousands of millions, maybe now?
It's probably millions. We should look that up.
But yeah, it's a good point. And there is very little noise in the Elm package registry,
which is quite refreshing to look through and be able to see a large number of high quality packages
to have a very high signal to noise ratio.
That's quite nice.
And the fact that the Elm semantic versioning starts at 1.0, I think is sort of a signal that this is the philosophy
to try to publish high quality packages and to try to vet packages a little bit to do even like an alpha or beta testing phase
before publishing a package to really do your homework and think about, is there a clear problem that I'm solving
with this package before you just go and put it out there?
And by the way, when I say 1.0 as the starting point for the Elm package semantic versioning,
1.0 is significant in the semantic versioning spec because it means that the semantics totally change.
Before 1.0 in SemVer, you can have a breaking change with a patch release.
So if you go from 0.0.1 to 0.0.2, you can have a breaking change according to the semantic versioning spec.
And so the Elm package repository just says, hey, if you're going to have a 0.0.1 release, that's great,
put it on the package repository once you've gotten past that phase.
Yeah, I don't really trust packages whose version starts with 0.0.1 because I feel like...
Like Elm?
No, Elm is better. It starts with 0.1.
It's a minor release.
Yeah, you see them on the NPM registry and you know it's going to be very different with the next version.
Yeah, it's something that I've kind of gotten used to also to just have...
I personally do pretty frequent releases and there are different schools of thought on this,
but I like doing frequent releases.
I don't shy away from getting a lot of different major versions up there.
It's interesting because in one sense, having to do a major version for a breaking change encourages you to batch together breaking changes.
Yeah, definitely.
And that's nice in one sense, but in another sense, it's not so nice for a user to have a bunch of breaking changes all at once.
So I think these are all sort of things to keep in mind and you really need to use your own judgment and just be aware of the trade offs.
But we're kind of...
Okay, so we teased this topic of should you publish an Elm package?
So what are the considerations for whether you should publish an Elm package?
Like where do you even start with this question?
Let's say I've got an idea.
There's some API that I think would be really cool to expose.
What question would you ask me if I say, hey, Jeroen, I've got this idea.
I think it would be really cool.
I'm going to publish a package.
Where would you...
What would you ask me?
I would ask you, what does it solve?
What problem does it solve?
What problem does it solve?
Who does it solve it for is probably good.
Does it solve it for you?
Does it solve it for other people also?
Because it's tempting to say, oh, it solves this problem.
Who has that problem?
Oh, well, I bet a lot of people have this problem.
If you have that problem, then at least you know that one person not maybe has that problem.
One person actually definitely concretely has that problem.
You know that person very well.
You can talk to them anytime.
Too often.
Especially if you're in self isolation.
Just go to the bathroom, go to the mirror and say, what are your problems?
What are your needs?
What are your problems?
Tell me about your problem.
But that is such a great place to start to scratch your own itch.
Really, that's always where I try to start.
To be honest, with Elm GraphQL, I didn't actually start there, if I'm being honest.
How dare you?
Because I wasn't a GraphQL user at the time.
But I was thinking there needs to be something out there that bridges the gap between information about what types exist on the server and information about what types exist on the client.
And there were some things like Haskell Servant and projects like that.
But I just thought that GraphQL was a really great way to solve that problem.
And I wanted that in Elm.
I wanted to be able to do that.
And so I started learning about GraphQL and building this project.
But actually, this is probably a good thing to talk about because even though I didn't scratch my own itch per se,
there was this thing that I thought would be really exciting and I wanted to have.
I was very deliberate about solving real problems in the way that I approach it.
So we can talk a little bit about maybe that story because I think that might be good to walk through a concrete example of going and asking these questions.
What problem are you solving?
And how are you going to design it?
What's the vision?
So I think the first thing to point out in this particular story when I built Elm GraphQL, like I said, it wasn't directly a concrete problem that I was solving for myself.
But you knew the problem existed in other environments.
So you knew that the problem existed somewhere.
Yeah, I mean, it seemed pretty clear to me that it would be valuable.
I knew that there were packages out there for making GraphQL queries in Elm and building up a GraphQL query.
And I knew that it wasn't fully type safe, at least some of the ways that packages were approaching that problem.
And I wanted to have a type safe way to do that.
So I knew that was a real problem.
So I started with a problem that I knew that was going to provide value in some way.
And I kept myself honest about providing a valuable solution.
And one of the things that I did that helps keep me honest doing that also was I started with examples.
So you can go back in the commit logs and you can see in Elm GraphQL, which is a code generation library.
It uses code generation to give you a typed API for your GraphQL schema.
And the very first commits didn't have any sort of code generation.
I was creating that generated code by hand.
But what it did have is an examples folder.
That's the very first thing it had.
It had an examples folder.
And shortly after that, it had some unit tests.
And those things kept me honest about solving real world problems.
Because what it allowed me to do is have an examples folder and say, OK, how does this design look for solving this real problem?
What does the API look like?
And is this something I'd be proud to show off to the world and say, hey, here's this library.
Here's what it looks like.
Here's how you build a query.
And here are what the error messages look like.
If you build something that's not a valid GraphQL query.
So starting with the examples folder, I think is extremely important for keeping yourself honest.
You want to start with a problem.
You want to start with a real problem.
If you have that problem, great.
If you don't have that problem, be very careful.
Be very careful and really think about is this valuable?
Are you keeping yourself honest?
Are you keeping yourself accountable about solving a real problem?
But then once you've done that, start with an examples folder.
Like, seriously, build the examples folder before you build anything else.
Another thing that I do, I will actually change things in the examples folder before I change anything else.
So like with Elm GraphQL, I'll go into the examples folder.
Again, I'll change this code that's being generated by the code gen.
I'll change it by hand.
But first, I'll start with the examples folder and I'll say, what would I like this example to look like?
If there's a new feature I'm thinking about adding, I'll start by seeing if I can do it with the existing APIs.
I'll create an example in the examples folder that illustrates this functionality.
See if I can do it with existing APIs.
If I can't, then what would I like it to look like?
I'll just write it how I want the example to work.
And then I'll hand generate the code, see how it actually feels to do an end to end example.
And only then do I do the actual code generation part.
But you don't have to do code generation to publish packages.
That's right.
Plenty of other problems to solve.
The only reason I mentioned that I do the code generation part last is because it's an illustration of how important I think it is to start with examples.
Put examples before anything else, because you want that feedback of seeing how does it feel to use this API?
So another thing that helps you experience that feedback of how does it feel using this API is writing a test case.
And if you write a unit test with elm test, it's extremely valuable to write meaningful test cases, not just toy examples.
That helps keep you honest that you're solving real problems because it changes the way that your brain is processing what you're doing.
Because you're thinking in terms of concrete problems and value rather than just in the abstract thinking about this API.
Oh, yeah. And one more thing on that topic.
So there is a really great package called Elm verify examples by Stoffel.
And I highly recommend checking that out for building a package.
It's great because you can write your documentation comments for your Elm package docs and you can do a syntax where you have an Elm code snippet.
And then you show the return value and it will actually generate an Elm test for you from that code snippet and from the expected output.
And so it makes sure that your documentation examples are up to date.
Yeah, that's very, very useful. I haven't actually tried it out yet because it was a bit harder in my case for Elm review.
Yes. The same with Elm GraphQL.
It was tough for that. But if there's a package where you can clearly say, here's the simple setup, here's the output it gives you, you should definitely use it.
It's a no brainer.
Definitely agree. So we talked about finding a problem for you and for others.
We talked about dog fooding, dog feeding.
To get feedback. So dog feedback.
Don't know if people see that.
I like it.
I like it too.
Let's go with that.
I would also say just go ask other people for feedback.
So just what you said before, hey, I have an idea.
And that's what people think about it.
And they will ask you, what is the problem that you're trying to solve?
And I think that very often people will tell you things that you did not expect.
And people will tell you this is not a problem that needs solving sometimes.
Especially when it's about solving boilerplate, for instance.
If you come talk to me about solving boilerplate, I will tell you that's not a problem.
It depends on the use case of the sleeper.
You know, another thing that happens when you go to a human being and you say, hey, can I talk to you about this API idea I have?
What do you think about it? Get feedback.
Ideally get feedback from someone who it might potentially be relevant to.
But really, it could just be anybody.
Because when you go through that exercise of putting the concept and the goals of the project into words,
that is a forcing function that will check to keep you honest about whether you actually have a clear concept and a clear goal in mind and a clear problem that you're solving.
If you can't describe that concisely to somebody, you need to go back to the drawing board and think about what problem are you solving
and what's unique about the way you're solving it?
What's the philosophy and the goal and the vision?
If you don't have a clear vision, it's okay.
It's not going to happen overnight.
But that's a good test.
If you're trying to explain to somebody what it does, why it's meaningful, what types of things it could be used to do that would be interesting.
If you can't do that concisely, if you can't have a good elevator pitch, then work on your elevator pitch because you're not going to end up with a really useful package.
You're going to be not using your time very effectively if you're doing that without having a clear vision first.
Yeah, the same thing happens with documentation.
If you start writing your documentation and you have trouble doing it, then maybe it's not the problem of you trying to write something,
but it's a problem of you not knowing what the problem is and what you're trying to solve.
Same happens with the API.
Not too long ago with Elm Review, I had an API that I needed to document and it was really hard.
It took me like two months to finish it.
But before I finished it, I found a new API that was much better and it was much easier to explain and to document.
So then you knew you were onto something.
Before I was like, yeah, it makes sense in my head, but it's just really hard to explain it.
And yeah, I was not onto something good.
Yeah, that process of just communicating your work, communicating the goal, communicating how you use it and why that's nice.
That's just part of the process of improving your design, improving your API.
Another thing I want to mention here is we talked about the importance of having an examples folder really right from the start.
People think in terms of examples, thinking in terms of examples keeps you honest about doing meaningful things.
Another thing I wanted to say on this topic is that you've only got a few seconds to get people's interest when they're looking at your package,
when they've taken the time out of their day to click to your package.
You've only got a few seconds to let them know that it's meaningful and relevant to them.
So use that time wisely.
And the very first thing people want to see is a code example.
So put code examples early and often in your documentation.
Put it right up front, front and center in your readme.
Maybe not the very first paragraph, but the second paragraph and sprinkle it throughout your documentation examples.
Even if it seems like overkill to include an example, just do it.
Just get in the habit of doing it. Use Elm Verify examples all over the place.
If you can just do it.
Trust me, people are not going to read actual words.
People will read words once they have been using something for a while and they need to understand something more thoroughly.
Once they're getting to a corner case and they need to understand something conceptually to get there.
Like, for example, Yaron, we did a live stream last week where we went through Elm Review, which was super fun by the way.
And really cool to go watch it. Take that for a spin.
Go watch it for sure. Yeah, we'll link to that.
And I was going through and setting up Elm Review for the first time. You were guiding me through that.
Are you going to criticize my work?
No, no, not quite the opposite.
Okay, go ahead then.
But I was able to figure it out before you guided me through a lot of the steps, which is great.
And a big part of the reason why is because there were examples.
Yeah, a lot of examples.
There are a lot of examples. I was clicking through to find something and there were a lot of words.
I appreciate that there were a lot of words, but I noticed as I was going through it, I didn't read a single one.
But I looked at examples and I know you put so much love into those words.
But when I'm going back to it and I'm trying to figure out, wait a minute, why can't I write this rule?
I'm trying to figure out like, I want to write this Elm Review rule and I'm getting stuck.
Then I'm going to go through and read every word in the package docs.
But when I'm first getting started or trying to figure out, do I want to use this package or trying to get set up, just trying it out, I'm not going to look at words.
I'm only going to look at examples. So put good examples in. It's the first thing people look at.
Yeah, if people know what they were looking for, then they will read more.
If they're just looking at the package, not knowing that you're solving a problem that they may have, then they're just going to skim through it and look at the examples.
Look at how it kind of looks, look at the big words and titles and that's it.
Yes. And if you have an example, it's in the right place and it's a concise example and it shows them what they need to know.
But it's, oh, this example and we're checking for Foo and Bar and Baz, not really going to help them.
You've got to make the examples meaningful because when they look at an example, they're going to look at an example, they're going to scan for what is this example doing and is this similar to what I want to do?
Oh, this is writing a rule to check if this is a valid regular expression.
That seems kind of similar to what I want to do. OK, so this is how you would solve that problem.
Don't just write examples, write examples of meaningful concrete problems.
Yeah, it takes time, but it's worth it. It's really useful to do that.
An image could also work instead of an example, like if you're designing a UI components or if you in the case of Elm Review, I display an image like that is the example output that you get from Elm Review that just communicates everything all at once.
So an example or an image that is very clear to the user, both will work.
Yes, you're right. That's in the same category of examples that people will scan through and understand what what it's going to do.
Yeah, I agree. OK, so also on this topic of should you write a package?
Richard Feldman gave this talk, this keynote at Oslo Elm Day.
The talk was a tour of the Elm Spa example.
Never, never, you'll never change my pronunciation.
Sorry, Arun. It's just so relaxing in Elm Spa.
Not to me.
But Richard goes through in that talk, he talks about a lot of really interesting things.
But one of the things he hits on is, OK, we can build these cool packages.
He talks about Luke Westby's HTTP builder package, which is a really cool package.
And Richard is saying, like, OK, this is a great package.
Pulling this package in nice, we can make HTTP requests in this way.
And that's nice and clean. But then he's saying, well, OK, but let's look at the source code.
There's not that much code here. And then let's think about what is this really buying me?
Well, it's giving me this nice syntax for building up HTTP requests.
But what if I had my own version of this custom tailored library that actually knew about my domain,
that actually knew about the endpoints that I can make in my HTTP?
That could be even simpler than what Luke's package does.
Exactly. It has an advantage over Luke's package because Luke doesn't know and Luke's package can't know what your code base can do.
It doesn't know the constraints of your code base. It doesn't know the type of how you send an auth token there.
So it just needs to expose this generalized way to add headers.
Whereas if you you could even if you wanted to copy paste that code into your own code base, make a nice module,
use the same nice API design techniques that Luke used.
But you have the advantage of building it for your domain and solving your specific problem. It's not that hard with Elm code.
A lot of the time I find with Elm it's it's actually not that hard to write something that I don't know in other languages.
I feel like I would be reaching for a package. Yeah.
But in Elm, just with this like nice functional style, it's so easy to just build up a nice, simple API.
So let's build this HTTP API for ourselves.
Let's give it this specific knowledge that, oh, every HTTP request is going to need this auth token or these particular routes don't need an auth token.
And so these are the routes you can make requests to without an auth token. Let's build that in.
It's also quite nice that all Elm packages are open source and they only contain Elm.
So you don't need to like an NPM packages. Sometimes you need to build C code or Rust code maybe.
Since it's all Elm code in this case, you can just copy paste it and make your project think it's its own code.
Yes. And then, yeah, you do. You adapt it to your own needs if you need to.
If you need to fork it, well, you don't need to fork it. You can copy paste it. That's easier.
Yeah, exactly. So this is something to think about if you're considering publishing a package.
Really think about should this be a package or maybe could this be a blog post where I share some pattern with people,
where I share a repository that shows an example of this, like the Elm Spa example.
Elm what?
You heard me.
Just keep that in mind. We're not saying don't publish your package, but this is the process.
These are the questions to go through.
Yeah, there's also one thing that I think Evan advises against is porting libraries.
So for instance, you have a Moment.js in JavaScript, which is great, which is very big, though, but in Elm that would not be a problem.
But it's great in JavaScript.
Well, since it's JavaScript, it has a lot of problems. Like it can misuse things.
But in Elm, you can define a different API that prevents you from misusing the package.
You can give your users more guarantees than you can with the JavaScript package.
So that's one advice I would give and I think Evan does too, if I understood him correctly,
is try to make the API look Elm like in order to give all those guarantees and nice things that you get with Elm.
Also, there are obviously a few things that you can't do in Elm that you can do in JavaScript packages,
like things that can mutate it.
Right, right.
So a very well designed package for doing the types of things that Moment.js does in Elm is going to look very different than in JavaScript,
even though Moment.js is great to use in JavaScript.
Like in many cases, it's stringly typed, meaning you pass in strings and it has different behavior because you passed in a string with this format versus this format.
So Ryan Haskell Glotz has a nice package where he basically does this.
And actually, if you're trying to understand better what it means to, instead of just thinking of porting a library from JavaScript or another ecosystem into Elm,
rather rethinking what does that package look like in the context of Elm, I think this is a really good example to look at.
Look at the API that Ryan built.
It's really nice and simple and type safe and allows you to sort of build up nicely formatted dates with a type safe API with an API that leverages the strengths of Elm.
Yeah, when I see packages that say, this is a port of this library in that language, I skim through it way more than for other packages.
Because I feel like it won't have the same niceties as other good Elm packages.
I might be wrong, but it could also just be a communication problem from their parts.
Don't say it's a port of something.
Say it's a good package for dealing with dates or times.
I mean, in terms of the mindset, I think of Elm as a superpower, as an API designer, as a package author.
So take advantage of that superpower.
Like if you're Superman and you go buy a flight to Mexico, then it doesn't really make sense.
Buying a flight to Mexico might be a great idea for your spring break.
But if you're Superman, just fly there.
Use your superpowers.
It's cheaper too.
It's cheaper, more fun.
So when you start designing your API, what should you look out for?
So as I said just before, when you're working in Elm, you want to give guarantees to the user.
So that means applying constraints to your functions, having specific types as inputs,
specific types as outputs, and types that can only have certain amounts of values.
Certain sets of values.
So you want all those guarantees.
Basically what you're trying to do is when you see a user, a way that a user can shut themselves in the foot, prevent that.
Make impossible states impossible for your package users.
Yeah, I couldn't agree more.
I think as an API designer, even more than an Elm application author, it's really beneficial to understand these API design techniques and these advanced techniques.
And so Charlie Koster has a really nice blog post series where he talks about these advanced type techniques,
opaque types, extensible records, the never type phantom types.
I would recommend checking that out and making sure that you have a good grasp of these advanced API design techniques
if you're publishing a package because they come in handy for providing these guarantees and constraints for your users
that make your library more intuitive, more easy to use, less error prone.
And actually, another thing that allows you to do is it allows you to protect users from breaking API changes.
Because you don't want to expose internals to users, not only because exposing internals can make it harder to verify certain constraints and give them guarantees,
but it also, so if you have a type alias for a record that you expose to users that has some internal data structure, maybe it's, I don't know,
let's say it's some data structure like a dictionary type thing, something like that.
You've got a package that has this nice data structure and you want to expose an API for users and you have a non opaque type.
You have a type alias for a record that has the internals or maybe it's just a custom type, but you expose the constructor for that custom type.
Oh no.
Don't do it. Don't do it.
It's going to cause a lot more breaking changes if you ever go back and change the implementation details,
if you change the name of a field in your internals or slightly change a type or have some, you know,
refactoring you're doing that the users shouldn't care about.
Basically anything you're going to have to do a breaking change.
So that's not to say that there could never be a reason to expose some detail like that, but really think carefully before you expose the internals like that.
Yeah, definitely.
And as you said, you remove constraints and guarantees like that.
So people can shit themselves in the foot again.
So those four advanced topics are, or techniques are very useful.
There are plenty of others.
They're not necessarily all that advanced like Opic types who already talked about it.
The never type is not that complicated.
And the other ones are not that complicated to either.
So calling them advances, making them look more complex than they are.
So definitely, definitely look at them.
Right. And it's a very creative process.
So another thing that's useful is just to pay attention to how other package authors solve problems.
You know, watch their talks, listen to how they talk about them.
In fact, I can link to some talks like Richard had a nice talk about the sort of evolution of Elm CSS and some design changes along the way.
But just listen to how people talk about their way of using types to solve problems, because that's one of the core design sensibilities that you bring to the Elm package authoring experience.
So pay attention to how other people do it, because it's really good to learn from other people.
Brian Hicks has also a nice talk where it's called Let's Publish Nice Packages.
And he goes through some of these techniques on kind of thinking about a package and what problem it solves and some of the things that we've talked about here.
So I definitely recommend checking that out.
And one of the things he talks about there, too, that I think is also super valuable is looking at prior art.
You know, we kind of talked about how you don't want to port packages directly and just verbatim do the exact same API as another ecosystem.
But you do want to look at how did they solve these problems?
How do different competing libraries in these other ecosystems approach this problem differently?
And what can you learn from that?
What do you want to do the same? What do you want to do differently?
Look at GitHub issues. What are people having problems with?
What design iteration?
Sometimes you can even get a peek into the design process in these other ecosystems.
I'm very often going in and looking at GitHub issues in Gatsby, JS and 11D and GridSum and these other frameworks for inspiration with Elm pages,
because it's a great shortcut to not having to go through some of the same problems, pain points and challenges that they did.
Also look at what other package authors care about.
So do they care about the boilerplate? Do they care about how verbose it will be?
How much errors you can avoid with it?
And go through the issues that are opened on their repositories, because you will see people asking for some features.
You will often see them say no, because this defeats that problem.
You will learn about a lot of tradeoffs that you will have to think about at some point.
Or you will not think about it and then you will regret it maybe.
Right, because it is kind of all about tradeoffs, isn't it?
And that's what makes it really challenging but really rewarding is you're kind of navigating through all these different tradeoffs and it's a balancing act.
And you've got to go with these decisions.
And so speaking of tradeoffs, that's why I think it's really valuable to have a clear vision.
Keep in mind, if you're building an Elm package or any package, you're not going to have the perfect solution to everything because it doesn't exist.
I remember one time I was in like a ski rental shop and somebody came in and he said, I want to get a really great snowboard.
And the person working at the store said, oh great, what kind of snowboard are you looking for?
Do you want an alpine board? Do you want a board that's good for tricks?
And the guy said, oh everything, I want the best at everything.
And they said, sir, that's not how it works.
You've got to pick the specialization because it's about tradeoffs.
If you have a board that's really good at alpine going as fast as possible, it's going to make it worse at doing tricks.
So as you said, they're tradeoffs.
Which means if you don't have a clear vision for which problems you're prioritizing, for which use cases are the core focus of the library,
and for which approach you're going to take, you're going to try to make every tradeoff and you're not going to do any of them successfully.
And you're not going to have fun.
And you're not going to have a good time.
So I find that the packages that I love the most are the ones that just have a clear vision and they say, yes, there are tradeoffs here,
but we have this value that we prioritize above other things.
And so this is the one making the decision.
And then you get this cohesive experience where, okay, above everything else, this thing needs to be type safe and this thing needs to be impossible.
And so maybe that has this other tradeoff that it's a little bit more verbose.
But we decided that you can't make invalid things with the type system.
And so we're going to make that tradeoff and be a little more verbose.
Yeah, just like the Elm language, there are a lot of things that it won't allow you to do, but you gain a lot from it.
So we talked a lot about documentation, so giving examples, explaining the problem, your solution.
Sometimes there are already a few packages out there that do the same thing as you, especially in the case of Elm GraphQL.
And also Elm Review with Elm Analyze.
And what is useful for users if they already know those packages is that you explain what the differences are between your solution, your package, and the other solutions.
And maybe that will tell them exactly what they want to know.
Like this does this this way, and it gives us this guarantee, this nice thing.
But it won't do this that the other package solves better or something like that.
So just like what you said before is every package has a vision.
Our package has this vision. Explain it.
Exactly. If one package author is making the equivalent of the downhill snowboard, then they're going to make it really good at going downhill.
And the other competing one is going to make it really good at doing tricks, and they're going to be making different choices and different trade offs and evolving in a different way.
And sure, there are some things that maybe they can both learn from each other and that's, you know, a win without a trade off.
But a lot of the time, the really big wins are wins that you have because you set a certain goal and set of values and you're willing to make trade offs in favor of that value to create a cohesive set of values.
A cohesive set of benefits that work well together. Yeah.
It also helps with the conversation because so really, this is a whole nother topic, but maintaining open source projects is challenging.
And one of the things that's most challenging is when you have feedback that's not a clear cut decision.
It happens often and it helps to be prepared for that by having a clear vision for your package and that clear vision allows you to sort of discuss the goals of the package that you've outlined rather than, I don't like that.
Yeah, your users won't like that.
It's not a trick. That's what's honest because a package has to sort of set a course and some things don't fit on that course.
And of course, the author who's sort of leading that vision has to make a certain set of trade offs that, you know, there's no objective this fits into that course or this doesn't.
But at least that helps set some sort of guide rails of what that course is going to look like.
And expectations too.
It helps to set expectations of what's in scope.
I outlined this and a lot of other things we've talked about here in this little GitHub repo that's just a read me sort of thing.
It's the idiomatic Elm package guide.
So we'll link to that in the show notes.
I used it for Elm review.
Oh, nice. How was it?
Along with Brian Nix's talk.
And yeah, it was very useful.
Okay, good.
Well, feedback is welcome if there's anything that.
I don't remember it now because it's been too long, but it was useful.
Okay, good. Yeah. Yeah.
I mean, it kind of talks about some of these things and like, I don't know, some things that I've tried to do from experience with this kind of thing, like getting feedback from people and finding it really useful to have a vision that's stated at the very top.
And like Jeroen said, having a comparison to other packages and this package solves this problem in this way. It doesn't solve this problem. It doesn't take this philosophy.
If you're looking for that, maybe look at this package.
It's really nice to have that right up front.
Also, because if somebody is considering using your package, that's often the first thing they want to know.
And if you're the first one to solve this problem, you won't have to do that.
So be the first one.
Okay, so if somebody wants to get started building an Elm package, let's say they've gone through this process.
They've kind of talked to a friend or somebody in the community about their idea and they've been able to concisely explain it.
They've got kind of a vision. They've got a clear problem they're solving. They've gone through all these things and done their homework.
How do they actually put the pedal to the metal and publish a package?
Or they need to write it.
Well, yeah, there you go. Write the package, implement your idea.
You're going to have to iterate on it. You're going to have to go through all these things.
So along the way, and I kind of outlined some of the tools and kind of basic mechanics of it in that idiomatic Elm package guide.
I try to make that just a resource that hopefully makes it a little easier to discover those things because they're not very clearly documented.
Yeah, you need to know about those tools somehow.
You need to know some of those basic tools. Like if you do elm make dash dash docs, the compiler is going to actually look at your documentation
because you have these doc comments for Elm functions and Elm modules and Elm types.
And you have to have a doc comment for every exposed type and module and function.
So you will get the same errors if you try to do Elm publish.
But if you do Elm make dash dash docs before you try to publish, it will not be as frustrated as when you try to publish it.
Exactly. And it's good to think of docs as a first class citizen.
So Remy has this tool at DMY on the interwebs.
He has this cool tool called Elm doc preview. And it's really, really useful for package authors.
It allows you to you just run that on the command line and you can see your documentation and it live reloads as you update it, which is great.
Yeah, there's also an online version where you can see the docs of a package that has not been released yet or a version that has not been released yet.
So you can preview someone else's repo documentation.
Exactly. Which is a great thing to get feedback on.
And if you're trying to ask people for feedback, which I always try to do before a big release, it's nice to have sort of a clearly packaged set of documentation and examples for people to look at.
Because it takes a lot of time for people to sort of get context on something.
So you want them to be able to look at what's the API going to look like to give you feedback.
You don't just want to send them a link to a GitHub repo and say, try out these examples and look at this code.
Do we have to specify that you should have tests in your package?
Please write tests. Don't publish them. Please write. Don't publish them.
Don't publish your tests. Just test like in a regular Elm application.
You've got your test folder, you run those, set up a CI.
I can link to some examples of some GitHub actions that I use for my continuous integration tests in some Elm packages.
I also built this GitHub action that I published that helps you publish your Elm package because, I don't know, there's a little bit of a dance with doing the publish process for an Elm package where you say, publish this package.
And then it checks everything out. It sees if the documentation is all in order and if everything's ready to go.
And then if it is, it says, OK, great. But this isn't tagged on GitHub yet with the latest version.
So run these commands, tag it in GitHub, push everything up and then run Elm publish again.
And what I kind of want to do is I just want to bump my Elm package version.
So the Elm.JSON file will have a bumped version. You use just the Elm command line tool, Elm bump to do that.
Elm space bump. And then I just want to push that and I want it to run all of my unit tests, all of my end to end tests, all of my Elm verify examples.
Make sure the docs check out all of that. And then and only then do I want it to push up the git tag for the current release and publish the package.
And so the GitHub action that I created does exactly that.
It's not a process that is that tedious. So if it's your first time or if you're beginning, just try Elm publish and the Elm compiler will tell you what to do and it's just fine.
You're absolutely right. It's not it's not so much that it's tedious. It's just that I want my CI server to run all of my tests and checks before I go through the trouble of creating a git tag.
But you're absolutely right. When you're starting, just do it by hand. You'll you'll get the hang of it and then you can streamline it later.
All right. Well, hopefully that gives people a pretty good overview of how to get started or whether they should even get started publishing package.
And hopefully that gives you some things to look into some inspiration to maybe watch some talks and learn about the experiences of developing some APIs and how they've evolved for other package authors.
And we hope you have fun with it. Yeah. Publish a package. Let the community know about it.
There's a packages channel on Slack, by the way.
So any package that gets published new version or first version is sent over there. If you want spoilers, go there.
Yes, that's a cool channel. And there's also an API design channel on the Elm Slack.
And that's a great place to just get feedback about your package ideas.
And, you know, there are a lot of people who will chime in and sort of give their thoughts on on package design ideas there.
Yeah. API design or a lot of other channels. Just get feedback.
Yes. I think you will learn most by getting feedback, asking for feedback.
Dog feedback. Absolutely. Dog feedback. Dog feedback. Hashtag dog feedback.
That's a thing now. Please make it a thing. Make it a thing.
Tweet us at ElmLangRadio. Hashtag dog feedback.
OK. Well, have a nice day. You too. See you next time. See you next time.