JavaScript Build Examples
Example Builds for Real Projects
Mill comes bundled with example builds for real-world open-source projects, demonstrating how Mill can be used to build code outside of tiny example codebases:
Ollama-Js
ollam-js
library provides the easiest way to integrate
your JavaScript project with Ollama.
In this example we show how to integrate ollama-js
with Mill,
showing off Mill’s scalability with custom tasks and traits.
Using custom tasks we build our library and then prepare the bundled code to be used as an unmanaged dependcy (file-based package installation) for the mini example projects.
Project home: https://github.com/ollama/ollama-js
Project Layout:
<project-root>
examples/
src/
test/
package.json
tsconfig.json
package build
import mill.*, javascriptlib.*
import os.*
import ujson.*
trait OllamaModule extends TypeScriptModule {
def moduleName = "ollama"
def npmDeps = Seq("whatwg-fetch@3.6.20")
def npmDevDeps = Seq(
"@swc/core@1.10.12",
"@types/whatwg-fetch@0.0.33",
"unbuild@2.0.0"
)
def packageJson = PackageJson(
name = "ollama",
version = "0.0.0",
description = "Ollama Javascript library",
main = "dist/index.cjs",
module = "dist/index.mjs",
types = "dist/index.d.ts",
exports = ujson.Obj(
"." -> ujson.Obj(
"require" -> "./dist/index.cjs",
"import" -> "./dist/index.mjs",
"types" -> "./dist/index.d.ts"
),
"./browser" -> ujson.Obj(
"require" -> "./dist/browser.cjs",
"import" -> "./dist/browser.mjs",
"types" -> "./dist/browser.d.ts"
),
"./*" -> "./*"
)
)
def options =
super.options() + (
"ts-node" -> ujson.Obj(
"swc" -> ujson.True,
"esm" -> ujson.True
)
)
def compilerOptions =
super.compilerOptions() + (
"noImplicitAny" -> ujson.True,
"noImplicitThis" -> ujson.True,
"strictNullChecks" -> ujson.True,
"declarationMap" -> ujson.True,
"skipLibCheck" -> ujson.True,
"strict" -> ujson.True,
"forceConsistentCasingInFileNames" -> ujson.True,
"module" -> ujson.Str("ES2022"),
"target" -> ujson.Str("es6"),
"lib" -> ujson.Arr.from(Seq("es6", "es2018.asyncgenerator", "dom"))
)
// disable declarations output in compile stage
def runTypeCheck = false
// bundle ollama with `unbuild`
def unbuild = Task {
createNodeModulesSymlink()
val out = compile().path
os.walk(
out,
skip = p =>
p.last == "node_modules" ||
p.last == "package-lock.json"
)
.foreach(p => os.copy.over(p, Task.dest / p.relativeTo(out), createFolders = true))
os.call("node_modules/.bin/unbuild")
// prepare bundled ouptut to be used as `unmanagedDeps` in examples section.
os.makeDir.all(Task.dest / "ollama")
os.copy(Task.dest / "package.json", Task.dest / "ollama" / "package.json")
os.copy(Task.dest / "dist", Task.dest / "ollama" / "dist")
PathRef(Task.dest / "ollama")
}
}
We take advantage of Mill to reduce boiler plate code, providing clean,
reusable traits specific to our many mini example usage projects found in ollama-js
source.
unbuild
:
The unbuild
task defined in OllamaModule
uses the unbuild
npm package to bundle the ollama-js project.
It then prepares the output to be used as an unmanaged dependency unmanagedDeps
for our example projects.
See: https://github.com/unjs/unbuild
trait Examples extends TypeScriptModule {
trait ExampleModule extends TypeScriptModule {
def sources = Task.Sources(moduleDir)
def mainFilePath = Task { compile().path / mainFileName() }
def unmanagedDeps = Examples.this.unmanagedDeps()
}
trait EsmModule extends ExampleModule {
def enableEsm = true
def runTypeCheck = false
}
trait Es6Module extends ExampleModule {
def compilerOptions =
super.compilerOptions() + (
"module" -> ujson.Str("nodenext"),
"moduleResolution" -> ujson.Str("nodenext"),
"target" -> ujson.Str("es6")
)
}
}
object `package` extends OllamaModule, TsLintModule {
def npmLintDeps = Seq(
"prettier@3.2.4",
"eslint@8.29.0",
"@typescript-eslint/eslint-plugin@5.42.1",
"@typescript-eslint/parser@5.42.1"
)
object test extends TypeScriptTests, TestModule.Vitest
object examples extends Examples {
def unmanagedDeps = Seq(unbuild())
object abort extends ExampleModule {
def mainFileName = "abort-all-requests.ts"
}
object `fill-in-middle` extends EsmModule {
def npmDeps = Seq("ollama@0.5.12")
def mainFileName = "fill.ts"
}
object multimodal extends EsmModule {
def mainFileName = "multimodal.ts"
}
object `pull-progress` extends EsmModule {
def mainFileName = "pull.ts"
}
object structured_outputs extends Es6Module {
def npmDeps = Seq("zod@3.24.1", "zod-to-json-schema@3.24.1")
def mainFileName = "structured-outputs.ts"
}
object tools extends Es6Module {
def mainFileName = "calculator.ts"
}
}
}
Example projects:
All example projects featured in examples/
do not have a src
directory,
they contain stand alone file(s) intended to be run independently with a command like
npx tsx <folder-name>/<file-name>.ts
.
The ExampleModule
trait configures the sources
and mainFilePath
task,
accounting for the absence of src
directory in the example projects.
EsmModule
enables esm, defining compilerOptions
for select project with esm features,
while the Es6Module
defines compilerOptions for projects with es6
features.
Ordinarily the compile stages mill compile
would build our declarations
and or dist
files (depending on configurations),
but since we intend to use unbuild
task to handle bundling we don’t need this output
from running the tsc
command. We can turn of its generation by settig the task runTypeCheck
to false.
User Defined JSON:
package.json
{
"name": "ollama",
"version": "0.0.0",
"description": "Ollama Javascript library",
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"exports": {
".": {
"require": "./dist/index.cjs",
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
},
"./browser": {
"require": "./dist/browser.cjs",
"import": "./dist/browser.mjs",
"types": "./dist/browser.d.ts"
},
"./*": "./*"
},
"scripts": {
"format": "prettier --write .",
"test": "vitest --run",
"build": "unbuild",
"lint": "eslint ./src/*",
"prepublishOnly": "npm run build"
},
"homepage": "https://github.com/ollama/ollama-js",
"repository": {
...
},
"author": "Saul Boyd",
"license": "MIT",
"devDependencies": {
...
},
"dependencies": {
...
}
}
tsconfig.json
{
"compilerOptions": {
"noImplicitAny": false,
"noImplicitThis": true,
"strictNullChecks": true,
"esModuleInterop": true,
"declaration": true,
"declarationMap": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"module": "ES2022",
"outDir": "./dist",
"target": "ES6",
"lib": [
"es6",
"es2018.asyncgenerator",
"dom"
]
},
"ts-node": {
"swc": true,
"esm": true,
},
"include": ["./src/**/*.ts"],
"exclude": ["node_modules"]
}
> ./mill checkFormatEslint # expected no failures, equivalent of running `npm run lint`
> ./mill checkFormatPrettier # expected failures, equivalent of running `prettier --check .`
...
Checking formatting...
[warn] examples/abort/abort-all-requests.ts
[warn] examples/abort/abort-single-request.ts
[warn] examples/structured_outputs/structured-outputs-image.ts
[warn] examples/structured_outputs/structured-outputs.ts
[warn] examples/tools/calculator.ts
[warn] examples/tools/flight-tracker.ts
[warn] src/browser.ts
[warn] src/constant.ts
[warn] src/index.ts
[warn] src/interfaces.ts
[warn] src/utils.ts
[warn] test/utils.test.ts
...
> ./mill reformatPrettier # fixes code style issues with prettier, equivalent of running `npm run format`
...
examples/abort/abort-all-requests.ts...
examples/abort/abort-single-request.ts...
examples/fill-in-middle/fill.ts...
examples/multimodal/multimodal.ts...
examples/pull-progress/pull.ts...
examples/structured_outputs/structured-outputs-image.ts...
examples/structured_outputs/structured-outputs.ts...
examples/tools/calculator.ts...
examples/tools/flight-tracker.ts...
src/browser.ts...
src/constant.ts...
src/index.ts...
src/interfaces.ts...
src/utils.ts...
src/version.ts...
test/index.test.ts...
test/utils.test.ts...
All matched files have been reformatted!
> ./mill checkFormatPrettier # no more failures after formatting, equivalent of running `prettier --check .`
...
Checking formatting...
All matched files use Prettier code style!
> ./mill test # Run vitest, this would be the equivalent of the `npm run test` command
...
.../index.test.ts (15 tests) ...
.../utils.test.ts (5 tests) ...
...
...Test Files 2 passed (2)
... Tests 20 passed (20)
...
> ./mill examples.abort.run
...Aborting all requests...
> ./mill examples.fill-in-middle.run
...syscall: 'connect',
...address: '127.0.0.1',
...port: 11434
...
> ./mill examples.multimodal.run
...syscall: 'connect',
...address: '127.0.0.1',
...port: 11434
...
> ./mill examples.pull-progress.run
...syscall: 'connect',
...address: '127.0.0.1',
...port: 11434
...
> ./mill examples.structured_outputs.run
...syscall: 'connect',
...address: '127.0.0.1',
...port: 11434
...
> ./mill examples.tools.run
...syscall: 'connect',
...address: '127.0.0.1',
...port: 11434
...