Imports and Package Management (upcoming)

Available in version 1.1.72 (not yet live).

Use standard JavaScript imports like import dayjs from "[email protected]" or zw.import() to install (if needed) and load packages during a run. ZeroWork installs missing packages and makes them available to your code.

Manage packages with zw.packages.list(), zw.packages.uninstall(), and zw.packages.uninstallAll().

API zw.import(), zw.packages.*

// @zw-run-locally

import dayjs from "[email protected]"; // auto-installs if needed
await zw.log("now", dayjs().toISOString());

// Example with options — 'isolate' scopes the package to this TaskBot only
const lodash = await zw.import({ isolate: true }, "[email protected]");
await zw.log("chunked result", lodash.chunk([1, 2, 3, 4], 2));

1. Imports

Standard Imports

You can use standard ESM or CommonJS syntax. Any such import installs the package if needed, then loads it.

// @zw-run-locally

// ESM default import
import dayjs from "[email protected]";
await zw.log("now", dayjs().toISOString());

// ESM subpath import
import chunk from "lodash/chunk";
await zw.log("chunked result", chunk([1, 2, 3, 4], 2));

// CommonJS require
const _ = require("[email protected]");
await zw.log("uniq result", _.uniq([1, 1, 2, 3]));

// Import from Git (HTTPS only)
import lodash from "git+https://github.com/lodash/lodash.git"

Notes

  • Installed packages are available to any TaskBot by default. To scope per TaskBot, use zw.import() with { isolate: true } (see details further below).

  • If a package isn’t used for one week, it's removed automatically. To change this window or prevent automatic uninstall, use zw.import() with uninstallIfUnusedFor (see details further below).

  • Subpaths like "lodash/chunk" and Git repositories are supported.


Imports with zw.import()

Use zw.import() when you need options or want to import several packages at once.

At a Glance

  • await zw.import(options: ImportOptions, pkg: string | string[] | { [key: string]: string }) → installs if needed and returns the loaded module(s)

// Reference only — not executable in Write JS
type ImportOptions = {
  uninstallIfUnusedFor: number | null; // hours; default 168; null = never uninstall
  isolate: boolean;                    // scope to this TaskBot only; default false
  preferDefault: boolean;              // advanced; default false
};

Examples

// @zw-run-locally

// Single package with options
const dayjs = await zw.import(
  { isolate: true, uninstallIfUnusedFor: 300 },
  "[email protected]"
);

// Multiple packages as an array
const [dayjs2, lodash] = await zw.import(
  { isolate: true },
  ["[email protected]", "[email protected]"]
);

// Named mapping as an object
const { time, dash } = await zw.import(
  { uninstallIfUnusedFor: null },
  { time: "[email protected]", dash: "[email protected]" }
);

// Installing from Git (HTTPS only)
const gitLodash = await zw.import(
  { uninstallIfUnusedFor: null },
  "git+https://github.com/lodash/lodash.git"
);

// Use default options by passing an empty object {}.
const dash2 = await zw.import({}, "[email protected]");

Import Option Details

  • uninstallIfUnusedFor Default 168 hours (one week). Set to null to keep the package on the device indefinitely.

  • isolate Default false. When true, the package is scoped to this TaskBot only and isn’t visible in other TaskBots. Useful when different TaskBots need different versions of the same package. Note: If the TaskBot is deleted, the package isn’t removed automatically; you can manage it later with zw.packages.* (see details further below).

  • preferDefault (advanced) Default false. If set to true, it returns module.default when present (otherwise the module object). This only changes the return shape; it doesn’t affect how the module is resolved. In most cases, leave it false.

    // @zw-run-locally
    
    // In most cases, leave preferDefault as is (false).
    const mysql = await zw.import({}, "mysql2@latest/promise"); // works
    const { chalk } = await zw.import({}, { chalk: "chalk@4" }); // works

Supported Package Inputs

You can pass any of the following to zw.import() as the second argument.

Invalid inputs that are rejected

  • Tarball URLs, local file paths, and directories

  • Non-HTTPS Git URLs


Built-in Packages

These packages are pre-bundled and resolve without installation.

  • Node core modules such as fs, os, etc.

  • axios pinned to ^1.6.6

  • playwright pinned to ^1.45.0

They don’t appear in zw.packages.list(), and you can’t change their versions or uninstall them.

// @zw-run-locally

import * as fs from "fs";  // pre-bundled; no installation occurs
import axios from "axios"; // resolves to axios@^1.6.6

// Version spec is ignored; resolves to pinned playwright@^1.45.0
import playwright from "[email protected]";

Pure ESM Packages

ZeroWork isn’t pure ESM yet. Pure ESM packages aren’t supported and will throw an error similar to:

This package appears to be pure ESM. Pure ESM packages are currently not supported. To confirm, check the package documentation.

Workarounds for pure ESM packages

  • Use a maintained non-ESM fork if available. For example, cacheable-lookup is a pure ESM module but a maintained fork is available: @esm2cjs/cacheable-lookup.

  • Pin a non-ESM version if the project provides one. For example, the latest chalk version is pure ESM but its documentation recommends pinning to v4 chalk@4 for a CommonJS build.

  • If the package is pure ESM with no CommonJS fork or version, you will have to find an alternative.


Reusing References

Import once and reuse the reference, as is generally best practice. The package won’t reinstall if it’s already installed, but package parsing and resolution still add overhead.

// @zw-run-locally

// 1. Anti-pattern: import inside the loop (re-parses/resolves each time)
for (let i = 0; i < 1_000_000; i++) {
  await zw.log((await zw.import({}, "[email protected]"))().toISOString());
}

// 2. Recommended: reuse a local reference
const dayjs = await zw.import({}, "[email protected]");
for (let i = 0; i < 1_000_000; i++) {
  await zw.log(dayjs().toISOString());
}

// 3. Optional (micro-optimization): reuse across Write JS blocks via run state
const cachedDayjs = await zw.import({}, "[email protected]");
(zw.state.access().cachedResolvedImports ||={}).dayjs = cachedDayjs;
// In a later building block:
await zw.log(zw.state.access().cachedResolvedImports.dayjs().toISOString());


2. Package Management

At a Glance

  • await zw.packages.list()string[] of package IDs

  • await zw.packages.uninstall(id: string) → removes one

  • await zw.packages.uninstallAll() → removes all user-managed packages

Built-ins (axios@^1.6.6, playwright@^1.45.0, and Node core modules like os, fs, etc.) do not appear in list() and cannot be uninstalled.

What’s a Package ID?

A package ID uniquely identifies an installed package instance. It’s an opaque string made of an optional isolation prefix (<taskbotId>_), a lower-cased, sanitized package name, @, and a version tag (<semver>, latest, git, or a commit hash). It may differ from your original import string (subpaths aren’t included; characters are normalized).

Examples

[email protected]
[email protected]
[email protected]   // isolated to a specific TaskBot (prefix includes its ID)
repo_name@git        // Git source without a specific commit
repo_name@9f3a2c4    // Git source pinned to a commit

Use these IDs with zw.packages.uninstall(id).


Examples

List packages

// @zw-run-locally

const ids = await zw.packages.list();
await zw.log("packages", ids);

Uninstall by package name/version

// @zw-run-locally

const ids = await zw.packages.list();
const target = ids.find(id => id.includes("[email protected]"));
if (target) await zw.packages.uninstall(target);

Uninstall all for a specific TaskBot

// @zw-run-locally

const myTaskBotId = 12345;
const ids = await zw.packages.list();
const targets = ids.filter(id => id.startsWith(`${myTaskBotId.toString()}_`));
for (const target of targets) {
  await zw.packages.uninstall(target);
}

Uninstall all

// @zw-run-locally

await zw.packages.uninstallAll();


3. Local and Browser Execution

Package installation and management run locally only. Standard import/require, zw.import(), and zw.packages.* are supported only when the Write JS block runs outside the browser context. Enable Run locally in the app checkbox in the block UI, or add the comment // @zw-run-locally.

How to Use Imported Packages in the Browser

Import locally in one block, expose a function, then call it from a later block that runs in the browser.

// Write JS Block A — runs locally

// @zw-run-locally 
import lodash from "lodash";
const context = zw.browserContext.getContext();
await context.exposeFunction("exposedChunkFn", (arr) => lodash.chunk(arr, 2));
// Write JS Block B — runs in the browser

const result = exposedChunkFn([1, 2, 3, 4]); // [[1,2],[3,4]]
await zw.log("exposedChunkFn result", result);

Last updated

Was this helpful?