Cool scripting language feature: install your own dependencies

Babashka scripts are capable of downloading and starting pods at runtime. (Pods are programs that can be used as a Clojure library by babashka)

#!/usr/bin/env bb

(require '[babashka.pods :as pods])
(pods/load-pod 'org.babashka/fswatcher "0.0.5")

(require '[pod.babashka.fswatcher :as fw])

(fw/watch "." prn {:delay-ms 5000})

(println "Watching current directory for changes... Press Ctrl-C to quit.")

@(promise)

I’m not saying it’s something we should be amazed by, but not many languages provide this. It has a sort of “this opens up so many possibilities” feeling: You can just write your script, copy it into any environment, and it will bootstrap with its own dependencies. The same applies when you open your 5-year-old script; you don’t have to fiddle with manually installing all dependencies. As I said, it’s nothing crazy, just a small convenience. Obviously, this wouldn’t be that smart for production either.

So anyway, I was wondering what other languages can do this.

Elixir scripts can install packages and use them:

Mix.install([:jason])
IO.puts(Jason.encode!(%{hello: :world}))

(Too bad elixir’s startup time isn’t that great for scripting)

Deno can import packages from npm directly:

import chalk from "npm:chalk@5";
console.log(chalk.green("Hello from npm in Deno"));

or from any URL, even using data: schema

import { camelCase } from "jsr:@luca/cases@1.0.0";
import { pascalCase } from "https://deno.land/x/case/mod.ts";
import * as module from "data:application/javascript;base64,ZX..."

F# has #r directive

#r "nuget: Newtonsoft.Json"
open Newtonsoft.Json

let data = {| Name = "Don Syme"; Occupation = "F# Creator" |}
JsonConvert.SerializeObject(data)

Ruby has bundler/inline module:

#!/usr/bin/env ruby

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'httparty', '~> 0.20'
  gem 'json', '~> 2.6'
end

require 'httparty'
require 'json'

response = HTTParty.get('https://api.github.com/users/ruby')
puts JSON.pretty_generate(response.parsed_response)

R needs a little bit of helper code if you don’t want to download the package every time, even if it’s already available:

#!/usr/bin/env Rscript

ensure_package <- function(pkg) {
  if (!require(pkg)) {
    install.packages(pkg)
    library(pkg)
  }
}

ensure_package("dplyr")

You can possibly shell out and use the package manager in most scripting languages, but I wanted to highlight languages where this feature is a built-in first-class citizen.

That’s it. That’s the post. I think it’s a pretty cool feature.