Using API

Bundles

The html-validate package contains four bundles:

The default full bundle includes everything (CLI classes etc) while the browser bundles are a bit more stripped and includes only code that runs in a browser1.

  1. Running in a browser is not fully supported yet as there are still calls to NodeJS fs and dynamic require's inside the library, see running in a browser for details.

Validating files

import { HtmlValidate } from "html-validate";

const htmlvalidate = new HtmlValidate();
const report = await htmlvalidate.validateFile("myfile.html");

console.log("valid", report.valid);
if (!report.valid) {
  console.log(report.results);
}

validateFile is a high-level API which automatically locates configuration files, load plugins, runs any transformations etc and is very similar to using the CLI tool (in fact, the CLI tool uses this very API).

A configuration object may optionally be passed to the HtmlValidate constructor:

-const htmlvalidate = new HtmlValidate();
+const htmlvalidate = new HtmlValidate({
+  extends: ["html-validate:recommended"],
+});

If set, it will be used as configuration unless a configuration could be read from .htmlvalidate.json files. Set root: true to prevent configuration files to be searched.

It is also possible to pass a configuration loader to fully customize how the configuration loading is handled:

-import { HtmlValidate } from "html-validate";
+import { StaticConfigLoader, HtmlValidate } from "html-validate";

-const htmlvalidate = new HtmlValidate();
+const loader = new StaticConfigLoader();
+const htmlvalidate = new HtmlValidate(loader);

validateFile(filename: string)

Reads a file and transforms the file according to the configured transformers.

Validating strings and other sources

In addition to validateFile there is also validateString and validateSource. Unlike validateFile no configuration files will be searched from the filesystem and the full configuration must be passed to the HtmlValidate constructor.

const report = await htmlvalidate.validateString("<div>lorem ipsum</span>");
console.log(report.results);
const report = await htmlvalidate.validateSource({
  /* markup to validate */
  data: "<div>lorem ipsum</span>",

  /* filename to put in report, content is not read */
  filename: "myfile.txt",

  /* original source location, i.e. at what position was markup extracted from
   * in original file. */
  line: 12,
  column: 8,
  offset: 59,
});
console.log(report.results);

validateString(markup: string, [filename: string], [config: ConfigData], [hooks: SourceHooks])

Validates the given markup.

The filename can for instance be used by FileSystemConfigLoader to load configuration files from the file system.

validateSource(source: Source, [config: ConfigData])

Validates the given markup (passed in the Source object).

Handling multiple files

To validate multiple files you need to call validateFile for each one, obtaining a report for each one. The reports can then be merged together, forming a new Report object.

import { HtmlValidate, Reporter } from "html-validate";

const htmlvalidate = new HtmlValidate();
const report1 = await htmlvalidate.validateFile("myfile.html");
const report2 = await htmlvalidate.validateFile("anotherfile.html");

/* merge reports together to a single report */
const merged = Reporter.merge([report1, report2]);

/* valid is true only if all reports are valid */
console.log("valid", merged.valid);

/* results holds all files */
if (!merged.valid) {
  console.log(merged.results);
}

Formatting reports

HTML-validate comes with a number of builtin formatters:

Formatters work on the results property in a report and all returns a formatted string:

import { HtmlValidate, formatterFactory } from "html-validate";

const htmlvalidate = new HtmlValidate();
const report = await htmlvalidate.validateFile("myfile.html");
const text = formatterFactory("text");

console.log(text(report.results));

Using the CLI API there is a factory function to retrieve formatters (see html-validate --help for details about the format):

import { CLI } from "html-validate";

const cli = new CLI();
const htmlvalidate = cli.getValidator();
const formatter = cli.getFormatter("stylish,checkstyle=html-validate.xml");
const report = await htmlvalidate.validateFile("myfile.html");
console.log(formatter(report));

In addition, any ESLint compatible reporter will work:

const stylish = require("eslint/lib/formatters/stylish");

console.log(stylish(report.results));

Configuration loaders

Since v6 the HtmlValidate API uses StaticConfigLoader by default which only loads static configuration (configuration passed to constructor or calls to validation functions). The CLI tool uses FileSystemConfigLoader instead which traversess the file system looking for configuration files such as .htmlvalidate.json.

To specify a loader pass it as the first argument to constructor:

import { FileSystemConfigLoader, HtmlValidate } from "html-validate";

const loader = new FileSystemConfigLoader();
const htmlvalidate = new HtmlValidate(loader);

A fully custom loader can be impemented by inheriting from ConfigLoader:

import { Config, ConfigData, ConfigLoader, ResolvedConfig } from "html-validate";

export class MyCustomLoader extends ConfigLoader {
  public override getConfigFor(handle: string, configOverride?: ConfigData): ResolvedConfig {
    /* return config for given handle (e.g. filename passed to validateFile) */
    const override = this.loadFromObject(configOverride || {});
    const merged = this.globalConfig.merge(this.resolvers, override);
    return merged.resolve();
  }

    public override flushCache(handle?: string): void {
    /* do nothing for this example */
  }

  protected defaultConfig(): Config {
    /* return default configuration, used when no config is passed to constructor */
    return this.loadFromObject({
      extends: ["html-validate:recommended"],
      elements: ["html5"],
    });
  }
}
Note

getConfigFor(..) can for backwards compatibility return a Config instance. This is deprecated and will be removed in the next major version.

The custom loader is used the same as builtin loaders:

-const loader = new FileSystemConfigLoader();
+const loader = new MyCustomLoader();
 const htmlvalidate = new HtmlValidate(loader);

When markup is validated the library will call the loader to fetch configuration, e.g:

htmlvalidate.validateFile("foo.html");
htmlvalidate.validateString("..", "my-fancy-handle");

This will generate calls to getConfigFor("foo.html") and getConfigFor("my-fancy-handle") respectively. While validateFile requires the file to be readable, the second argument to validateString can be any handle the API user wants as long as the loader can understand it.

FileSystemConfigLoader([config: ConfigData])

Loader which traverses filesystem looking for .htmlvalidate.json configuration files, starting at the directory of the target filename.

The result from the configuration files are merged both with a global configuration and optionally explicit overrides from the calls to validateFile, validateString and validateSource.

import { FileSystemConfigLoader, HtmlValidate } from "html-validate";

const loader = new FileSystemConfigLoader();
const htmlvalidate = new HtmlValidate(loader);

/* given filenames will be passed to the loader which will traverse the
 * filesystem for configurations */
htmlvalidate.validateFile("/path/to/my-file.html");
htmlvalidate.validateString("..", "/path/to/my-file.html");

StaticConfigLoader([config: ConfigData]) (default)

Default loader which loads configuration only from the configuration passed to the constructor or explicit overrides to validateString(..).

import { StaticConfigLoader, HtmlValidate } from "html-validate";

const loader = new StaticConfigLoader({
  /* your global configuration here */
});
const htmlvalidate = new HtmlValidate(loader);

The global configuration is used by default when using validateFile, validateString and validateSource without any arguments:

htmlvalidate.validateFile("myfile.html");
htmlvalidate.validateString("..");
htmlvalidate.validateSource({
  data: "..",
  filename: "myfile.html",
  line: 1,
  column: 1,
  offset: 0,
});

Each call may also pass a configuration override (merged with global):

htmlvalidate.validateString("..", {
  /* config override */
});

Resolvers

Since v8 the HtmlValidate API uses StaticResolver by default which only loads predefined elements, configurations, plugins and transformers.

A Resolver implements the following interface:

export interface Resolver {
  name: string;
  resolveElements?(id: string, options: ResolverOptions): MetaDataTable | null;
  resolveConfig?(id: string, options: ResolverOptions): ConfigData | null;
  resolvePlugin?(id: string, options: ResolverOptions): Plugin | null;
  resolveTransformer?(id: string, options: ResolverOptions): Transformer | null;
}

The library comes with two builtin resolvers:

Configuration cache

HtmlValidate is mostly stateless, it only acts on the input source and its configuration.

However, for performance configuration is cached (per instance) and must be flushed if configuration is changed. Normally this wont matter but when writing integrations with tools it might be desirable to keep a single instance of HtmlValidate around and in that case the cache needs to be flushed if configuration changes are detected.

/* flush everything */
htmlvalidate.flushConfigCache();

/* flush configuration for a single file */
htmlvalidate.flushConfigCache("myfile.html");

Unit testing

If using jest to write tests there is a couple of helpers to assist writing tests:

import { HtmlValidate, ConfigData } from "html-validate";
import "html-validate/jest";

const config: ConfigData = {
  rules: {
    "my-rule": "error",
  },
};

test("should frobnicate a tux", () => {
  const htmlvalidate = new HtmlValidate(config);
  const report = htmlvalidate.validateString("...");
  expect(report).toBeValid();
});

test("should not frobnicate a flux", () => {
  const htmlvalidate = new HtmlValidate(config);
  const report = htmlvalidate.validateString("...");
  expect(report).toBeInvalid();
  expect(report).toHaveError("my-rule", "the tux should not be frobnicated by a flux");
});

See Jest for more details.

CLI tools

The CLI interface can be wrapped using the CLI class.

import { CLI } from "html-validate";

/* replace with your favourite cli arg parser */
const argv = {
  configFile: "myconfig.json",
  formatter: "stylish",
};

const cli = new CLI({
  configFile: argv.configFile,
});

const htmlvalidate = cli.getValidator();
const formatter = cli.getFormatter(argv.formatter);
const files = cli.expandFiles(["**/*.html"]);
const report = await htmlvalidate.validateMultipleFiles(files);
console.log(formatter(report));