dillonkearns/elm-pages-v3-beta/8.0.0

NOTE: the latest version is 21.0.0

Rerouted (regenerated, verified). Same repository and tag, but GitHub now builds the zip with different bytes, so it no longer matches the SHA-1 package.elm-lang.org recorded. The Lamdera package server serves a copy recovered from https://github.com/dillonkearns/elm-pages-v3-beta.

What we checked: Tag 8.0.0 is still on github.com/dillonkearns/elm-pages-v3-beta; its commit b7735572454e is from 2023-01-17 05:49 UTC, before the version was published (2023-01-17 05:52 UTC); elm.json is byte-identical to package.elm-lang.org's; the exposed modules match docs.json; the zip's SHA-1 is not the published one (GitHub's zip no longer comes out byte for byte the same), so the contents were checked instead.

The Lamdera compiler uses this copy and prints a one-line notice saying so. elm itself fails to download this version.

Details · package.elm-lang.org's zip: https://github.com/dillonkearns/elm-pages-v3-beta/zipball/8.0.0/

BackendTask.Port

In a vanilla Elm application, ports let you either send or receive JSON data between your Elm application and the JavaScript context in the user's browser at runtime.

With BackendTask.Port, you send and receive JSON to JavaScript running in NodeJS. As with any BackendTask, Port BackendTask's are either run at build-time (for pre-rendered routes) or at request-time (for server-rendered routes). See BackendTask for more about the lifecycle of BackendTask's.

This means that you can call shell scripts, run NPM packages that are installed, or anything else you could do with NodeJS to perform custom side-effects, get some data, or both.

A BackendTask.Port will call an async JavaScript function with the given name from the definition in a file called port-data-source.js in your project's root directory. The function receives the input JSON value, and the Decoder is used to decode the return value of the async function.

get :
String
-> Value
-> Decoder b
-> BackendTask
{ fatal : FatalError
, recoverable : Error
}
b

Here is the Elm code and corresponding JavaScript definition for getting an environment variable (or an FatalError BackendTask.Port.Error if it isn't found). In this example, we're using BackendTask.allowFatal to let the framework treat that as an unexpected exception, but we could also handle the possible failures of the FatalError (see FatalError).

import BackendTask exposing (BackendTask)
import BackendTask.Port
import Json.Encode
import OptimizedDecoder as Decode


data : BackendTask FatalError String
data =
    BackendTask.Port.get "environmentVariable"
        (Json.Encode.string "EDITOR")
        Decode.string
        |> BackendTask.allowFatal


-- will resolve to "VIM" if you run `EDITOR=vim elm-pages dev`
// port-data-source.js

module.exports =
  /**
   * @param { unknown } fromElm
   * @returns { Promise<unknown> }
   */
  {
    environmentVariable: async function (name) {
      const result = process.env[name];
      if (result) {
        return result;
      } else {
        throw `No environment variable called ${name}

Available:

${Object.keys(process.env).join("\n")}
`;
      }
    },
  }

Performance

As with any JavaScript or NodeJS code, avoid doing blocking IO operations. For example, avoid using fs.readFileSync, because blocking IO can slow down your elm-pages builds and dev server. elm-pages performances all BackendTask's in parallel whenever possible. So if you do BackendTask.map2 Tuple.pair myHttpBackendTask myPortBackendTask, it will resolve those two in parallel. NodeJS performs best when you take advantage of its ability to do non-blocking I/O (file reads, HTTP requests, etc.). If you use BackendTask.andThen, it will need to resolve them in sequence rather than in parallel, but it's still best to avoid blocking IO operations in your BackendTask Port definitions.

Error Handling

There are a few different things that can go wrong when running a port-data-source. These possible errors are captured in the BackendTask.Port.Error type.

type Error
= Error
| ErrorInPortsFile
| MissingPortsFile
| PortNotDefined { name : String }
| PortCallException Value
| ExportIsNotFunction

Any time you throw a JavaScript exception from a BackendTask.Port definition, it will give you a PortCallException. It's usually easier to add a try/catch in your JavaScript code in port-data-source.js to handle possible errors, but you can throw a JSON value and handle it in Elm in the PortCallException call error.