Builder Instance

本章节描述了 Builder 实例对象上所有的属性和方法。

builder.context

builder.context 是一个只读对象,提供一些上下文信息。

builder.context.entry

构建入口对象,对应调用 createBuilder 时传入的 entry 选项。

  • 类型
type BuilderEntry = Record<string, string | string[]>;

builder.context.target

构建产物类型,对应调用 createBuilder 时传入的 target 选项。

  • 类型
type BuilderTarget = 'web' | 'node' | 'modern-web' | 'web-worker';

type Context = {
  target: BuilderTarget | BuilderTarget[];
};

builder.context.rootPath

当前执行构建的根路径,对应调用 createBuilder 时传入的 cwd 选项。

  • 类型
type RootPath = string;

builder.context.srcPath

src 目录的绝对路径。

  • 类型
type SrcPath = string;

builder.context.distPath

构建产物输出目录的绝对路径,对应 BuilderConfig 中的 output.distPath.root 配置项。

  • 类型
type DistPath = string;

builder.context.cachePath

构建过程中生成的缓存文件所在的绝对路径。

  • 类型
type CachePath = string;

builder.context.configPath

框架配置文件的绝对路径,对应调用 createBuilder 时传入的 configPath 选项。

  • 类型
type ConfigPath = string | undefined;

builder.context.tsconfigPath

tsconfig.json 文件的绝对路径,若项目中不存在 tsconfig.json 文件,则为 undefined

  • 类型
type TsconfigPath = string | undefined;

builder.context.framework

框架的英文名称,唯一标识符,默认值为 'modern.js'

  • 类型
type Framework = string | undefined;

builder.context.devServer

Dev Server 相关信息,包含了当前 Dev Server 的 hostname 和端口号。

  • 类型
type DevServer = {
  hostname: string;
  port: number;
};

builder.context.bundlerType

当前执行构建的构建工具类型。

  • 类型
type bundlerType = 'webpack' | 'rspack';

builder.build

调用 build 方法时,会执行一次生产环境构建。

  • 类型
type BuildOptions = {
  mode?: 'development' | 'production';
  watch?: boolean;
  // 自定义 Compiler 对象
  compiler?: Compiler | MultiCompiler;
};

function Build(options?: BuildOptions): Promise<void>;
  • Example
await builder.build();

开发环境构建

如果需要执行一次开发环境构建,可以将 mode 参数设置为 'development'

await builder.build({
  mode: 'development',
});

监听文件变化

如果需要自动监听文件变化并重新执行构建,可以将 watch 参数设置为 true

await builder.build({
  watch: true,
});

自定义 Compiler

个别情况下,你可能希望使用自定义的 compiler:

const compiler = webpack({
  // ...
});
await builder.build({
  compiler,
});

builder.startDevServer

启动本地 Dev Server,基于 Modern.js Dev Server 实现。

  • 类型
type StartDevServerOptions = {
  // 是否输出 URL 信息,默认为 true
  printURLs?: boolean | Function;
  // 是否在端口被占用时抛出异常,默认为 false
  strictPort?: boolean;
  // 自定义 Compiler 对象
  compiler?: Compiler | MultiCompiler;
  // 透传与构建无关的 dev server 配置
  serverOptions?: Partial<ModernDevServerOptions>;
  // 是否在启动时静默获取端口号,默认为 false
  getPortSliently?: boolean;
  // 自定义日志输出对象
  logger?: Logger;
};

type StartServerResult = {
  urls: string[];
  port: number;
  server: Server;
};

function StartDevServer(
  options?: StartDevServerOptions,
): Promise<StartServerResult>;
  • Example

启动 Dev Server:

await builder.startDevServer();

成功启动 Dev Server 后,可以看到以下日志信息:

info    Starting dev server...

  > Local:    http://localhost:8080
  > Network:  http://192.168.0.1:8080

startDevServer 会返回以下参数:

  • urls:访问 Dev Server 的 URLs
  • port 实际监听的端口号
  • server:Server 实例对象
const { urls, port, server } = await builder.startDevServer();
console.log(urls); // ['http://localhost:8080', 'http://192.168.0.1:8080']
console.log(port); // 8080

// 关闭 Dev Server
await server.close();

自定义 URL 输出

printURLs 设置为 false 可以禁用默认的 URL 输出,此时你可以输出自定义的日志内容。

await builder.startDevServer({
  printURLs: false,
});

你也可以直接将 printURLs 配置为一个函数来修改 URL,比如给每个 URL 增加一个子路径:

await builder.startDevServer({
  printURLs: urls => {
    return urls.map(({ label, url }) => ({
      label,
      url: `${url}/path`,
    }));
  },
});

严格限制端口

当端口被占用时,Builder 会自动递增端口号,直至找到一个可用端口。

如果你希望在端口被占用时抛出异常,可以将 strictPort 设置为 true

await builder.startDevServer({
  strictPort: true,
});

自定义 Compiler

个别情况下,你可能希望使用自定义的 compiler:

const compiler = webpack({
  // ...
});
await builder.startDevServer({
  compiler,
});

静默获取端口号

某些情况下,默认启动的端口号已经被占用,此时 Builder 会自动递增端口号,直至找到一个可用端口。这个过程会输出提示日志,如果你不希望这段日志,可以将 getPortSliently 设置为 true

await builder.startDevServer({
  getPortSliently: true,
});

自定义日志输出对象

默认情况下,Builder 会使用 @modern-js/utils/logger 来输出日志,你可以通过 logger 参数来自定义日志输出对象。

const customLogger = {
  // 你需要定义以下的方法
  info(msg: string) {
    console.log(msg);
  },
  error(msg: string) {
    console.error(msg);
  },
  warn(msg: string) {
    console.warn(msg);
  },
  success(msg: string) {
    console.log(`✅ msg`);
  },
  debug(msg: string) {
    if (process.env.DEBUG) {
      console.log(msg);
    }
  },
  log(msg: string) {
    console.log(msg);
  };
}

await builder.startDevServer({
  logger: customLogger,
});

这样,Builder 会使用你自定义的日志输出对象来输出日志。

builder.serve

在本地启动 Server 来预览生产环境构建的产物,需要在 builder.build 方法之后执行。

  • 类型
type StartServerResult = {
  urls: string[];
  port: number;
  server: Server;
};

function server(): Promise<StartServerResult>;
  • Example

启动 Server:

await builder.serve();

serve 会返回以下参数:

  • urls:访问 Server 的 URLs
  • port 实际监听的端口号
  • server:Server 实例对象
const { urls, port, server } = await builder.serve();
console.log(urls); // ['http://localhost:8080', 'http://192.168.0.1:8080']
console.log(port); // 8080

// 关闭 Server
await server.close();

builder.createCompiler

创建一个 compiler 对象。

createBuildertarget 选项包含一个值时,返回值为 Compiler;当 target 包含多个值时,返回值为 MultiCompiler

  • 类型
function CreateCompiler(): Promise<Compiler | MultiCompiler>;
  • Example
const compiler = await builder.createCompiler();

大部分场景下,不需要使用该 API,除非需要进行自定义 Dev Server 等高级操作。

builder.addPlugins

注册一个或多个 builder 插件,可以被多次调用。

该方法需要在开始编译前调用,如果在开始编译之后调用,则不会影响编译结果。

  • 类型
type AddPluginsOptions = { before?: string } | { after?: string };

function AddPlugins(
  plugins: BuilderPlugins[],
  options?: AddPluginsOptions,
): Promise<void>;
  • Example
builder.addPlugins([builderPluginFoo(), builderPluginBar()]);

// 在 bar 插件之前插入
builder.addPlugins([builderPluginFoo()], { before: 'bar' });

// 在 bar 插件之后插入
builder.addPlugins([builderPluginFoo()], { after: 'bar' });

builder.removePlugins

移除一个或多个 builder 插件,可以被多次调用。

该方法需要在开始编译前调用,如果在开始编译之后调用,则不会影响编译结果。

  • 类型
function RemovePlugins(pluginNames: string[]): void;
  • Example
// 添加插件
const pluginFoo = builderPluginFoo();
builder.addPlugins(pluginFoo);

// 移除插件
builder.removePlugins([pluginFoo.name]);

builder.isPluginExists

判断某个插件是否已经被注册。

  • 类型
function IsPluginExists(pluginName: string): boolean;
  • Example
builder.addPlugins([builderPluginFoo()]);

builder.isPluginExists(builderPluginFoo().name); // true

builder.inspectConfig

查看 Builder 内部最终生成的 builder 配置和 bundler 配置。

TIP

inspectConfig 方法不支持与 startDevServer / build 方法同时使用。

当你需要在构建过程中查看完整的 builder 和 bundler 配置时,可以使用 调试模式,也可以通过 onBeforeBuildonBeforeCreateCompile 等钩子函数来获取。

  • 类型
type InspectConfigOptions = {
  // 查看指定环境下的配置,默认为 "development",可以设置为 "production"
  env?: BuilderMode;
  // 是否开启冗余模式,展示配置中函数的完整内容,默认为 `false`
  verbose?: boolean;
  // 指定输出路径,默认为 `output.distPath.root` 配置的值
  outputPath?: string;
  // 是否将结果写入到磁盘中,默认为 `false`
  writeToDisk?: boolean;
};

async function InspectConfig(options?: InspectConfigOptions): Promise<{
  builderConfig: string;
  bundlerConfigs: string[];
  origin: {
    builderConfig: BuilderConfig;
    bundlerConfigs: BundlerConfigs[];
  };
}>;
  • Example

拿到字符串格式的 Config 内容:

const { builderConfig, bundlerConfigs } = await builder.inspectConfig();

console.log(builderConfig, bundlerConfigs);

直接将配置内容写入到磁盘上:

await builder.inspectConfig({
  writeToDisk: true,
});

builder.onBeforeCreateCompiler

onBeforeCreateCompiler 是在创建底层 Compiler 实例前触发的回调函数,当你执行 builder.startDevServerbuilder.buildbuilder.createCompiler 时,都会调用此钩子。

你可以通过 bundlerConfigs 参数获取到底层打包工具的最终配置数组:

  • 如果当前打包工具为 webpack,则获取到的是 webpack 配置数组。
  • 如果当前打包工具为 Rspack,则获取到的是 Rspack 配置数组。
  • 配置数组中可能包含一份或多份配置,这取决于你是否开启了 SSR 等功能。

你可以通过 bundlerConfigs 参数获取到底层打包工具的最终配置对象。

  • 类型
function OnBeforeCreateCompiler(
  callback: (params: {
    bundlerConfigs: WebpackConfig[] | RspackConfig[];
  }) => Promise<void> | void,
): void;
  • Example
builder.onBeforeCreateCompiler(({ bundlerConfigs }) => {
  console.log('the bundler config is ', bundlerConfigs);
});

builder.onAfterCreateCompiler

onAfterCreateCompiler 是在创建 Compiler 实例后、执行构建前触发的回调函数,当你执行 builder.startDevServerbuilder.buildbuilder.createCompiler 时,都会调用此钩子。

你可以通过 compiler 参数获取到 Compiler 实例对象:

  • 如果当前打包工具为 webpack,则获取到的是 webpack Compiler 对象。

  • 如果当前打包工具为 Rspack,则获取到的是 Rspack Compiler 对象。

  • 类型

function OnAfterCreateCompiler(callback: (params: {
  compiler: Compiler | MultiCompiler;
}) => Promise<void> | void;): void;
  • Example
builder.onAfterCreateCompiler(({ compiler }) => {
  console.log('the compiler is ', compiler);
});

builder.onBeforeBuild

onBeforeBuild 是在执行生产环境构建前触发的回调函数,你可以通过 bundlerConfigs 参数获取到底层打包工具的最终配置数组:

  • 如果当前打包工具为 webpack,则获取到的是 webpack 配置数组。

  • 如果当前打包工具为 Rspack,则获取到的是 Rspack 配置数组。

  • 配置数组中可能包含一份或多份配置,这取决于 Builder 当前 target 配置的值。

  • 类型

function OnBeforeBuild(
  callback: (params: {
    bundlerConfigs?: WebpackConfig[] | RspackConfig[];
  }) => Promise<void> | void,
): void;
  • Example
builder.onBeforeBuild(({ bundlerConfigs }) => {
  console.log('the bundler config is ', bundlerConfigs);
});

builder.onAfterBuild

onAfterBuild 是在执行生产环境构建后触发的回调函数,你可以通过 stats 参数获取到构建结果信息:

  • 如果当前打包工具为 webpack,则获取到的是 webpack Stats。

  • 如果当前打包工具为 Rspack,则获取到的是 Rspack Stats。

  • 类型

function OnAfterBuild(
  callback: (params: { stats?: Stats | MultiStats }) => Promise<void> | void,
): void;
  • Example
builder.onAfterBuild(({ stats }) => {
  console.log(stats?.toJson());
});

builder.onBeforeStartDevServer

在启动开发服务器前调用。

  • 类型
function OnBeforeStartDevServer(callback: () => Promise<void> | void): void;
  • Example
builder.onBeforeStartDevServer(() => {
  console.log('before start!');
});

builder.onAfterStartDevServer

在启动开发服务器后调用,你可以通过 port 参数获得开发服务器监听的端口号。

  • 类型
function OnAfterStartDevServer(
  callback: (params: { port: number }) => Promise<void> | void,
): void;
  • Example
builder.onAfterStartDevServer(({ port }) => {
  console.log('this port is: ', port);
});

builder.onDevCompileDone

在每次开发环境构建结束后调用,你可以通过 isFirstCompile 来判断是否为首次构建。

  • 类型
function OnDevCompileDone(
  callback: (params: { isFirstCompile: boolean }) => Promise<void> | void,
): void;
  • Example
builder.onDevCompileDone(({ isFirstCompile }) => {
  if (isFirstCompile) {
    console.log('first compile!');
  } else {
    console.log('re-compile!');
  }
});

builder.onExit

在进程即将退出时调用,这个钩子只能执行同步代码。

  • 类型
function OnExit(callback: () => void): void;
  • Example
builder.onExit(() => {
  console.log('exit!');
});

builder.getBuilderConfig

获取 Builder 配置,该方法必须在 modifyBuilderConfig 钩子执行完成后才能被调用。

  • 类型
function GetBuilderConfig(): Readonly<BuilderConfig>;
  • Example
builder.onBeforeBuild(() => {
  const config = api.getBuilderConfig();
  console.log(config.html?.title);
});

builder.getNormalizedConfig

获取归一化后的 Builder 配置,该方法必须在 modifyBuilderConfig 钩子执行完成后才能被调用。

相较于 getBuilderConfig 方法,该方法返回的配置经过了归一化处理,配置的类型定义会得到收敛,比如 config.htmlundefined 类型将被移除。

推荐优先使用该方法获取配置。

  • 类型
function GetNormalizedConfig(): Readonly<NormalizedConfig>;
  • Example
builder.onBeforeBuild(() => {
  const config = api.getNormalizedConfig();
  console.log(config.html.title);
});

builder.getHTMLPaths

获取所有 HTML 产物的路径信息。

该方法会返回一个对象,对象的 key 为 entry 名称,value 为 HTML 文件在产物目录下的相对路径。

  • 类型
function GetHTMLPaths(): Record<string, string>;
  • Example
builder.onBeforeBuild(() => {
  const htmlPaths = api.getHTMLPaths();
  console.log(htmlPaths); // { main: 'html/main/index.html' };
});