Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: experimental active watcher for rollup #364

Merged
merged 10 commits into from
Jun 4, 2024
37 changes: 31 additions & 6 deletions src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { defu } from "defu";
import { createHooks } from "hookable";
import prettyBytes from "pretty-bytes";
import { globby } from "globby";
import type { RollupOptions } from "rollup";
import {
dumpObject,
rmdir,
Expand All @@ -18,7 +19,7 @@ import {
} from "./utils";
import type { BuildContext, BuildConfig, BuildOptions } from "./types";
import { validatePackage, validateDependencies } from "./validate";
import { rollupBuild } from "./builder/rollup";
import { getRollupOptions, rollupBuild } from "./builder/rollup";
import { typesBuild } from "./builder/untyped";
import { mkdistBuild } from "./builder/mkdist";
import { copyBuild } from "./builder/copy";
Expand All @@ -42,18 +43,34 @@ export async function build(

// Invoke build for every build config defined in build.config.ts
const cleanedDirs: string[] = [];
const rollupOptions: RollupOptions[] = [];

const _watchMode = inputConfig.watch === true;
const _stubMode = !_watchMode && (stub || inputConfig.stub === true);

for (const buildConfig of buildConfigs) {
await _build(rootDir, stub, inputConfig, buildConfig, pkg, cleanedDirs);
await _build(
rootDir,
inputConfig,
buildConfig,
pkg,
cleanedDirs,
rollupOptions,
_stubMode,
_watchMode,
);
}
}

async function _build(
rootDir: string,
stub: boolean,
inputConfig: BuildConfig = {},
buildConfig: BuildConfig,
pkg: PackageJson & Record<"unbuild" | "build", BuildConfig>,
cleanedDirs: string[],
rollupOptions: RollupOptions[],
_stubMode: boolean,
_watchMode: boolean,
) {
// Resolve preset
const preset = resolvePreset(
Expand All @@ -78,7 +95,7 @@ async function _build(
clean: true,
declaration: false,
outDir: "dist",
stub,
stub: _stubMode,
stubOptions: {
/**
* See https://github.com/unjs/jiti#options
Expand All @@ -89,6 +106,13 @@ async function _build(
alias: {},
},
},
watch: _watchMode,
watchOptions: _watchMode
? {
exclude: "node_modules/**",
include: "src/**",
}
: undefined,
externals: [
...Module.builtinModules,
...Module.builtinModules.map((m) => "node:" + m),
Expand All @@ -102,6 +126,7 @@ async function _build(
sourcemap: false,
rollup: {
emitCJS: false,
watch: false,
cjsBridge: false,
inlineDependencies: false,
preserveDynamicImports: true,
Expand Down Expand Up @@ -252,8 +277,8 @@ async function _build(
// copy
await copyBuild(ctx);

// Skip rest for stub
if (options.stub) {
// Skip rest for stub and watch mode
if (options.stub || options.watch) {
await ctx.hooks.callHook("build:done", ctx);
return;
}
Expand Down
5 changes: 5 additions & 0 deletions src/builder/copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { relative, resolve } from "pathe";
import { globby } from "globby";
import { symlink, rmdir, warn } from "../utils";
import type { CopyBuildEntry, BuildContext } from "../types";
import consola from "consola";

const copy = fsp.cp || fsp.copyFile;

Expand Down Expand Up @@ -51,4 +52,8 @@ export async function copyBuild(ctx: BuildContext) {
}
}
await ctx.hooks.callHook("copy:done", ctx);

if (entries.length > 0 && ctx.options.watch) {
consola.warn("`untyped` builder does not support watch mode yet.");
}
}
5 changes: 5 additions & 0 deletions src/builder/mkdist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { relative } from "pathe";
import { mkdist, MkdistOptions } from "mkdist";
import { symlink, rmdir } from "../utils";
import type { MkdistBuildEntry, BuildContext } from "../types";
import consola from "consola";

export async function mkdistBuild(ctx: BuildContext) {
const entries = ctx.options.entries.filter(
Expand Down Expand Up @@ -36,4 +37,8 @@ export async function mkdistBuild(ctx: BuildContext) {
}
}
await ctx.hooks.callHook("mkdist:done", ctx);

if (entries.length > 0 && ctx.options.watch) {
consola.warn("`mkdist` builder does not support watch mode yet.");
}
}
57 changes: 56 additions & 1 deletion src/builder/rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,31 @@ import { nodeResolve } from "@rollup/plugin-node-resolve";
import alias from "@rollup/plugin-alias";
import dts from "rollup-plugin-dts";
import replace from "@rollup/plugin-replace";
import { resolve, dirname, normalize, extname, isAbsolute } from "pathe";
import {
resolve,
dirname,
normalize,
extname,
isAbsolute,
relative,
} from "pathe";
import { resolvePath, resolveModuleExportNames } from "mlly";
import { watch as rollupWatch } from "rollup";
import { arrayIncludes, getpkg, tryResolve, warn } from "../utils";
import type { BuildContext } from "../types";
import { esbuild } from "./plugins/esbuild";
import { JSONPlugin } from "./plugins/json";
import { rawPlugin } from "./plugins/raw";
import { cjsPlugin } from "./plugins/cjs";
import { klona } from "klona/full";
import {
shebangPlugin,
makeExecutable,
getShebang,
removeShebangPlugin,
} from "./plugins/shebang";
import consola from "consola";
import chalk from "chalk";

const DEFAULT_EXTENSIONS = [
".ts",
Expand Down Expand Up @@ -193,6 +204,16 @@ export async function rollupBuild(ctx: BuildContext) {
}
}

// Watch
if (ctx.options.watch) {
_watch(rollupOptions);
// TODO: Clone rollup options to continue types watching
if (ctx.options.declaration && ctx.options.watch) {
consola.warn("`rollup` DTS builder does not support watch mode yet.");
}
return;
}

// Types
if (ctx.options.declaration) {
rollupOptions.plugins = [
Expand Down Expand Up @@ -400,3 +421,37 @@ function resolveAliases(ctx: BuildContext) {

return aliases;
}

export function _watch(rollupOptions: RollupOptions) {
const watcher = rollupWatch(rollupOptions);

let inputs: string[];
if (Array.isArray(rollupOptions.input)) {
inputs = rollupOptions.input;
} else if (typeof rollupOptions.input === "string") {
inputs = [rollupOptions.input];
} else {
inputs = Object.keys(rollupOptions.input || {});
}
consola.info(
`[unbuild] [rollup] Starting watchers for entries: ${inputs.map((input) => "./" + relative(process.cwd(), input)).join(", ")}`,
);

consola.warn(
"[unbuild] [rollup] Watch mode is experimental and may be unstable",
);

watcher.on("change", (id, { event }) => {
consola.info(`${chalk.cyan(relative(".", id))} was ${event}d`);
});

watcher.on("restart", () => {
consola.info(chalk.gray("[unbuild] [rollup] Rebuilding bundle"));
});

watcher.on("event", (event) => {
if (event.code === "END") {
consola.success(chalk.green("[unbuild] [rollup] Rebuild finished\n"));
}
});
}
5 changes: 5 additions & 0 deletions src/builder/untyped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import untypedPlugin from "untyped/babel-plugin";
import jiti from "jiti";
import { pascalCase } from "scule";
import type { BuildContext, UntypedBuildEntry, UntypedOutputs } from "../types";
import consola from "consola";

export async function typesBuild(ctx: BuildContext) {
const entries = ctx.options.entries.filter(
Expand Down Expand Up @@ -69,4 +70,8 @@ export async function typesBuild(ctx: BuildContext) {
}
}
await ctx.hooks.callHook("untyped:done", ctx);

if (entries.length > 0 && ctx.options.watch) {
consola.warn("`untyped` builder does not support watch mode yet.");
}
}
8 changes: 7 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ const main = defineCommand({
description: "The directory to build",
required: false,
},
watch: {
type: "boolean",
description: "Watch the src dir and rebuild on change (experimental)",
},
stub: {
type: "boolean",
description: "Stub build",
description: "Stub the package for JIT compilation",
},
minify: {
type: "boolean",
Expand All @@ -34,6 +38,8 @@ const main = defineCommand({
const rootDir = resolve(process.cwd(), args.dir || ".");
await build(rootDir, args.stub, {
sourcemap: args.sourcemap,
stub: args.stub,
watch: args.watch,
rollup: {
esbuild: {
minify: args.minify,
Expand Down
30 changes: 27 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type { PackageJson } from "pkg-types";
import type { Hookable } from "hookable";
import type { RollupOptions, RollupBuild, OutputOptions } from "rollup";
import type {
RollupOptions,
RollupBuild,
OutputOptions,
WatcherOptions,
} from "rollup";
import type { MkdistOptions } from "mkdist";
import type { Schema } from "untyped";
import type { RollupReplaceOptions } from "@rollup/plugin-replace";
Expand Down Expand Up @@ -55,6 +60,13 @@ export interface RollupBuildOptions {
*/
emitCJS?: boolean;

/**
* Enable experimental active watcher
*
* @experimental
*/
watch?: boolean;

/**
* If enabled, unbuild generates CommonJS polyfills for ESM builds.
*/
Expand Down Expand Up @@ -168,11 +180,23 @@ export interface BuildOptions {
outDir: string;

/**
* Whether to generate declaration files.
* [stubbing](https://antfu.me/posts/publish-esm-and-cjs#stubbing)
* Whether to build with JIT stubs.
* Read more: [stubbing](https://antfu.me/posts/publish-esm-and-cjs#stubbing)
*/
stub: boolean;

/**
* Whether to build and actively watch the file changes.
*
* @experimental This feature is experimental and incomplete.
*/
watch: boolean;

/**
* Watch mode options.
*/
watchOptions: WatcherOptions;

/**
* Stub options, where [jiti](https://github.com/unjs/jiti)
* is an object of type `Omit<JITIOptions, "transform" | "onError">`.
Expand Down