The purpose of this guide is to provide you with resources to get started writing an Elm package. And to help you write an Elm package that is:
- Valuable
- Constrained to prevent impossible states
- Easy to learn and use
- Easy to tell if it's the right fit for the problem at hand
- Consistent with the README and documentation conventions in the Elm community
Check out this tweet that lays out some ideas from Elm's philosophy! It's got some great points regarding both community and API design.
You can read through TEMPLATE.md for inspiration. It's a good starting point for your README.md
if you want to copy-paste it.
The rest of this document provides guiding principles, tips, and resources.
Feedback through Slack, Github issues, or pull requests is always welcome! If it's not an obvious change, it might be best to hold off on the pull request until after some initial discussion around the idea to make sure we're on the same page.
dillonkearns/elm-publish-action
lets you automate publishing packages in your GitHub Actions (just runelm bump
and commit the updated elm.json andelm-publish-action
automates the rest). This helps you ensure that you only publish after a successful run of your build/test suite.stoeffel/elm-verify-examples
lets you write examples inline in your doc comments and then run them as a test suitedmy/elm-doc-preview
lets you view your package documentation locally with hot reloading (including any errors for generating your docs). You can also preview documentation using the online tool by going to a URL like https://elm-doc-preview.netlify.app/?repo=dillonkearns%2Felm-pages&version=serverless-latest (you'll need to runelm make docs=docs.json
and push the committeddocs.json
file for the tool to pick up your docs though).
- The Elm Package Documentation has basic information on publishing a package, and a few design guidelines
- Official guide for creating Elm docs
- There is an undocumented syntax for linking to Elm modules and definitions that you can use in your package's doc comments, described in this tweet
Here is a blog post about the process of publishing an Elm package:
The Basic Steps to Publish a Package in Elm 0.19 - Alex Korban
If your package isn't valuable, then the rest of these principles won't help you much! Here are some tips to maximize the value of your Elm package.
- Start simple. Introduce complexity sparingly.
- Valuable doesn't mean comprehensive. It can be a minimal, simple package but still be extremely valuable. Think of the single-purpose philosophy of unix tools like
ls
orgrep
. - Design a consumer-driven API (see 49:41-52:57)
- Check out Let's Publish Nice Packages - Brian Hicks at Elm Europe 2018 for some great tips like doing research on similar tools and avoiding direct ports from other libraries
When writing your Elm package, try to expose the minimal interface possible, AND drive that interface by concrete use cases. Test-driven development is a great way to do this on the micro level (see programming by intention). Take a look at the elm-test documentation for how to get started, and for best practices on writing a test suite.
On the macro level, try starting with an examples
folder (i.e. examples
-Driven Development). Start with a simple yet meaningful example. (For example, with elm-cli-options-parser
, I started with an end-to-end example of building up the elm-test
Command-Line Interface since it is a simple but meaningful and concrete interface). Then, add features to your package as needed to complete this simple but meaningful example and get it working fully end-to-end.
As you iterate on your design, review your examples
folder and ask yourself some questions:
- Are you proud to show these examples off?
- Is it obvious to someone who hasn't used your library what they are doing?
- Does the public API make sense?
- Could the examples be any simpler if the API were different?
- Is it discoverable (i.e. easy to find the functions you need to accomplish a task)?
- Can you express things that are invalid using your API?
You can make sure that your examples are valid by adding a Travis job so you get emails if any of your checks fail. Some ways you can check your examples:
- The
elm-verify-examples
tool lets you run the examples in your docs like unit tests! - Use
elm make
to make sure all of yourexamples
compile - Have a good-old-fashioned
elm-test
suite that shows some meaningful use cases (avoid toy examples!)
When publishing a package you will need a toolkit of advanced Elm type techniques to create a clear public API, and one that models the constraints of your library's domain. The Official Elm API Design Guidelines are a good starting point.
Make use of advanced Elm types to make a simpler API and to model the constraints of your domain better
These articles by Charlie Koster on advanced Elm types are very useful when writing Elm packages.
- Part I - Opaque Types
- Part II - Extensible Records
- Part III - The Never Type
- Part IV - Phantom Types
These videos are full of great tips for designing APIs.
- Make Data Structures - Richard Feldman @ Elm Europe 2018
- Make Impossible States Impossible
- Understanding Style - Matthew Griffith @ Elm Europe 2017 - Matt goes into details about his consumer-driven API
Use the "Inverted Pyramid"
You need a hook that let's potential users know whether it's worth their time to read more about your package or try it out. So give very clear summaries at the top that include key points. You can use this basic inverted pyarmid structure from journalism (i.e. bottom line up front... however far you read, you've read the most important points).
-
Key summary
- Headline (the github project description... this is the first thing users see)
- Why would I want to use this over other packages?
- What problem does it solve?
-
Important details
- How do I install and use this library?
-
Other background information
- Advanced usage tips
Use the same language everywhere, whether it's in a doc comment, the README, an example folder, an exposed function name or type, a type variable name (yes, even type variables, they show up in the docs, so they deserve great names, too!). And even your internal code! Bad naming in the internals of your code will inevitably leak out into confusing names that are public facing.
With that said, recognize that naming is a process. Don't expect to have the perfect names mapped out in your first commit. Iterate on the naming, just be sure to relentlessly do renaming refactors and module extracts as you see the opportunity for more clear names.
See the Learning Resources section in the README template.
Here are some examples of learning sections from Elm package READMEs:
Maintain a file called CHANGELOG.md
in the root folder of your project on Github. The purpose of this is to make it easy for users to
- Learn about new features (and how to use them)
- Learn how to upgrade to new major versions
- See when a bug is fixed
This is more granular than the commit log itself.
Copy-paste the template from Keep a Changelog to get started and follow the principles stated there. It's very well thought out and easy to follow once you get going with it.
Every time someone asks a question, consider answering it in your FAQ.
- Keep FAQs in an FAQ.md file or FAQ section in your README.
- Instead of directly answering a common question, send a link to the answer. That way you don't ever have to write that answer again, and it is there for future reference.
- If a question keeps coming up, it may also mean that it's an opportunity to make your API simpler or more intuitive.
You often see Github issues or pull requests like this one requesting that an Opaque Type be exposed in the API so that users of the API can annotate their functions that use values of that type. Avoid these types of requests by always following this rule of thumb:
If your public API allows you to create a value (even an intermediary one) of a given type, always expose that type in your public API so users can annotate their functions.
What differentiates your project from any other project out there? Since Elm packages are reliable due to the nature of the elm language and its guarantees, what makes Elm packages stick out more is not its reliability, but its design philosophy. So we put the design philosophy as the first section after the initial introduction.
Writing out explicit design goals is a great idea even before you write any code (of course you can always revise them). They serve as:
- A reminder to the package author of the core principles during design iteration
- A clear statement of goals to help users decide whether the library is a good fit for them
- A reference point for conversations about feature requests that helps ground the conversation in the basic goals of the library. This makes for a much more empathetic conversation (for example, someone could have a great idea that's not inline with the design goals of a library... in that case, perhaps a new library could be created, OR a different solution could be considered that honors the design goals of the library)
Here are some examples of design goals clearly stated in an Elm package README:
See the Design Goals section of the README template.
You can use this template as a starting point. See the How to Use This Guide section of this README!
Here are some examples of packages that you can use as inspiration:
If you click edit up at the top-right of your github page, you'll have
a place to enter a Description
and Website
. Put a link to your project's
Elm package page in the Website
.
In the Description
, put a catchy headline that summarizes what your library
does and what makes it unique in a short sentence.
For example, for mdgriffith/style-elements
it is:
Description
: Create styles that don't mysteriously break!
Website
: https://package.elm-lang.org/packages/mdgriffith/style-elements/latest
The recommended license for Elm projects is BSD3.
-
The Elm community encourages that package authors follow its Literal Naming Policy.
-
The Elm core team recommends that you "use the
elm-
prefix unlesselm
already appears in the repository name".
If you create a file called CONTRIBUTING
, Github will automatically link to it
when your users create issues or pull requests. It's a great idea to store it
in a separate file, but be sure to link to it!
Many package authors prefer to have discussions before they get pull requests.
If that's the case for you, give a note in the ## Contributing
section of
your readme to clarify that.
Note that no matter how clear your contributing guidelines are, many pull requests and issues are created without noticing them. That's alright! Kindly link to the relevant guidelines to frame the conversation and make it as constructive as possible! I often like to frame feature requests in terms of the design goals of the library as well. This makes it less of a "who's right?" zero-sum game, and more of a collaborative discussion about "how aligned is this direction with these design goals?"
Remember to keep a file or a section in your readme to list of contributors and recognize their work! I like to include both contributors of code (through pull requests) as well as those who share a lot of feedback or are very helpful reporting issues. Open source not just about one person's work, it's about community working together!