Commits (41)
{
"extends": ["@html-validate", "plugin:security/recommended", "plugin:sonarjs/recommended"],
"plugins": ["security", "sonarjs"],
"extends": ["@html-validate"],
"rules": {
"import/named": "off",
"node/no-unsupported-features/es-syntax": "off",
"security/detect-non-literal-fs-filename": "off",
"security/detect-non-literal-require": "off",
"security/detect-object-injection": "off",
"security/detect-unsafe-regex": "off"
......@@ -17,7 +13,6 @@
"extends": ["@html-validate/typescript"],
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-var-requires": "off"
}
},
......@@ -26,10 +21,8 @@
"excludedFiles": "cypress/**",
"extends": ["@html-validate/jest"],
"rules": {
"sonarjs/no-duplicate-string": "off",
"sonarjs/no-identical-functions": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"tsdoc/syntax": "off"
"@typescript-eslint/no-non-null-assertion": "off"
}
}
]
......
......@@ -106,10 +106,6 @@ Docs:
- npm run --if-present build
- npm run compatibility
Node 10.x:
<<: *compat
image: node:10
Node 12.x (LTS):
<<: *compat
image: node:12
......
# html-validate changelog
## [5.0.0](https://gitlab.com/html-validate/html-validate/compare/v4.14.0...v5.0.0) (2021-06-27)
### ⚠ BREAKING CHANGES
- the library is now shipped as a hybrid CJS/ESM package. If you
are simply consuming the CLI tool or one of the existing integrations this will
not affect you.
For plugin developers and if you consume the API in any way the biggest change
is that the distributed source is now bundled and you can no longer access
individual files.
Typically something like:
```diff
-import foo from "html-validate/dist/foo";
+import { foo } from "html-validate"
```
Feel free to open an issue if some symbol you need isn't exported.
If your usage includes checking presence of rules use the `ruleExists` helper:
```diff
-try {
- require("html-validate/dist/rules/attr-case");
-} catch (err) {
- /* fallback */
-}
+import { ruleExists } from "html-validate";
+if (!ruleExists("attr-case")) {
+ /* fallback */
+}
```
- drop support for NodeJS 10
### Features
- add `compatibilityCheck` helper for plugins ([4758595](https://gitlab.com/html-validate/html-validate/commit/47585951e7faf026bccc228d537f678d69da1c8a))
- cjs/esm hybrid package ([39c960a](https://gitlab.com/html-validate/html-validate/commit/39c960a19f47cedcb55edf0865a3e6bd174a61f6)), closes [#112](https://gitlab.com/html-validate/html-validate/issues/112)
- drop support for NodeJS 10 ([8f74291](https://gitlab.com/html-validate/html-validate/commit/8f74291919e1bcdab88ae6b74ba4199b16a4ef54))
### Dependency upgrades
- **deps:** update dependency ajv to v8 ([cccb73a](https://gitlab.com/html-validate/html-validate/commit/cccb73ad33db7f8032ecef469dc77a3df24eb29f))
## [4.14.0](https://gitlab.com/html-validate/html-validate/compare/v4.13.1...v4.14.0) (2021-06-14)
### Features
......
......@@ -13,6 +13,22 @@ Offline HTML5 validator. Validates either a full document or a smaller
- Strict and non-forgiving parsing. It will not try to correct any incorrect
markup or guess what it should do.
## Bundles
The library comes in four flavours:
- CommonJS full (`dist/cjs/main.js`)
- CommonJS browser (`dist/cjs/browser.js`)
- ESM full (`dist/es/main.js`)
- ESM browser (`dist/es/browser.js`)
The browser versions contains a slimmed version without CLI dependencies.
Your tooling will probably use the correct version but if needed you can import the files directly.
Do note that to run in a browser you still need to polyfill the `fs` nodejs library.
Browsers and bundlers are currently not 100% supported but is possible with some tricks, see [running in browser](https://html-validate.org/dev/running-in-browser.html) for more details.
## Usage
npm install -g html-validate
......@@ -52,7 +68,14 @@ Create `.htmlvalidate.json`:
6:4 error Unexpected close-tag, expected opening tag close-order
```
## Test
## Developing
### Prerequisites
- NodeJS 12
- NPM 7
### Test
Testing is done using jest.
......@@ -62,7 +85,7 @@ or call `jest` directly.
Some tests are autogenerated from documentation examples, use `npm run build:docs` to build those before running.
## Lint
### Lint
Linting is done using ESLint.
......@@ -70,7 +93,7 @@ Linting is done using ESLint.
or call `eslint` directly.
## Build
### Build
npm run build
......
#!/usr/bin/env node
"use strict";
require("../dist/cli/html-validate");
require("../dist/cjs/html-validate");
---
docType: content
title: Running in a browser
---
# Running in a browser
While primarly developed as a NodeJS CLI/backend tool it is possible to run `html-validate` in a browser as well.
<div class="alert alert-info">
<i class="fa fa-info-circle" aria-hidden="true"></i>
<strong>Note</strong>
<p>While it is possible to get <code>html-validate</code> running in a browser it is currently not supported and requires a few workarounds.</p>
</div>
Improvements are welcome!
## Example
There is an example project [try-online-repo] running at [try-online-url] showing that it can be done and the workarounds required.
[try-online-repo]: https://gitlab.com/html-validate/try-online
[try-online-url]: https://online.html-validate.org/
## Configuration loading
By default `html-validate` will traverse the filesystem looking for configuration files (e.g. `.htmlvalidate.json`).
This is true even when using `validateString(..)`.
This will manifest itself with errors such as:
- `Cannot find module 'fs'`
- `Cannot read property 'existsSync' of undefined`
- `fs_1.default.existsSync is not a function`
### Workaround 1: prevent loader from trying to access filesystem
By far the easiest method is to pass a config to the [[HtmlValidate]] constructor with the `root` property to `true`:
```ts
import { HtmlValidate } from "html-validate";
const htmlvalidate = new HtmlValidate({
root: true,
extends: ["html-validate:recommended"],
});
```
Do note that no default configuration will be loaded either so you must explicitly enable rules or extend a preset.
### Workaround 2:
If you are emulating or providing virtual access to a filesystem you can ensure the `fs` module is implemented.
There is no exhaustive list of functions which must be added.
If you are using webpack you can use `resolve.alias` to implement this:
```js
module.exports = {
resolve: {
alias: {
fs$: path.resolve(__dirname, "src/my-fs.js"),
},
},
};
```
## Bundled files
The `html-validate` NPM package contains a few data files such as `elements/html.json`.
These files are dynamically imported and will most likely not be picked up by your bundler.
Either you need to ensure the bundler picks up the files or the configuration loader does not need to import thme.
- `elements/*.json`
This will manifest itself with errors such as:
- `Error: Failed to load elements from "html5": Cannot find module 'html5'`
This is typically archived by passing an object instead of a string when configuring `html-validate`:
```diff
import { HtmlValidate } from "html-validate";
+// check your loader! it must return a plain object (not `default: { ... }`, a path/url, etc)
+import html5 from "html-validate/elements/html5.json";
const htmlvalidate = new HtmlValidate({
root: true,
extends: ["html-validate:recommended"],
- elements: ["html5"],
+ elements: [html5]
});
```
## Webpack
Internally there are many dynamic imports and `fs` access.
You will see warnings such as:
WARNING in ./node_modules/html-validate/dist/config/config.js 81:25-50
Critical dependency: the request of a dependency is an expression
In many cases there is no way to avoid the warning per se but the workaround above are implemented the code paths triggering these issues are not hit.
......@@ -83,7 +83,7 @@ This is useful when the transformer needs to deal with multiple languages and th
Extracts templates from javascript sources.
```typescript
const TemplateExtractor = require("html-validate").TemplateExtractor;
const { TemplateExtractor } = require("html-validate");
const te = TemplateExtractor.fromFilename("my-file.js");
/* finds any {template: '...'} */
......
......@@ -94,26 +94,24 @@ HTML-validate comes with a number of builtin formatters:
- `stylish`
- `text`
Formatters work on the `results` property in a report and all returns a
formatted string:
Formatters work on the `results` property in a report and all returns a formatted string:
```typescript
import { HtmlValidate } from "html-validate";
import text from "html-validate/dist/formatters/text";
import { HtmlValidate, formatterFactory } from "html-validate";
const htmlvalidate = new HtmlValidate();
const report = 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):
Using the CLI API there is a factory function to retrieve formatters (see `html-validate --help` for details about the format):
```typescript
import { getFormatter } from "html-validate/dist/cli/formatter";
import { formatterFactory } from "html-validate";
const stylish = getFormatter("stylish");
const stylish = formatterFactory("stylish");
console.log(stylish(report));
```
......
......@@ -329,3 +329,29 @@ The resulting metadata will now be:
"bar": "original"
}
```
### Version compatibility
- Since: 5.0.0
Plugins can use the `compatibilityCheck` helper to verify the library version is compatible.
```typescript
import { compatibilityCheck } from "html-validate";
const pkg = require("./package.json");
const range = pkg.peerDependencies["html-validate"];
compatibilityCheck(pkg.name, range);
```
The helper will write a friendly notice on console if the version is not supported.
If you want the error to be fatal you it returns `false` if the version is not supported.
Additionally you can pass the `silent` option if you want to disable output`
```typescript
if (!compatibilityCheck(pkg.name, range, { silent: true })) {
/* handle incompatible version */
}
```
const kleur = require("kleur");
const HtmlValidate = require("../../../../dist/htmlvalidate").default;
const codeframe = require("../../../../dist/formatters/codeframe").codeframe;
const { HtmlValidate, formatterFactory } = require("../../../../dist/cjs");
const codeframe = formatterFactory("codeframe");
const formatterOptions = {
showLink: false,
......
const a17y = require("../../../dist/config/presets/a17y");
const document = require("../../../dist/config/presets/document");
const recommended = require("../../../dist/config/presets/recommended");
const standard = require("../../../dist/config/presets/standard");
const { configPresets } = require("../../../dist/cjs");
/* sort order */
const availablePresets = ["recommended", "standard", "a17y", "document"];
/* preset configuration */
const presets = {
a17y,
document,
recommended,
standard,
};
function compareName(a, b) {
if (a.name < b.name) {
return -1;
......@@ -59,7 +48,8 @@ module.exports = function rulesProcessor(renderDocsProcessor) {
category: doc.category,
summary: doc.summary,
presets: availablePresets.reduce((result, presetName) => {
const config = presets[presetName];
const key = `html-validate:${presetName}`;
const config = configPresets[key];
if (config && config.rules) {
result[presetName] = Boolean(config.rules[doc.name]);
}
......
......@@ -74,6 +74,7 @@
<li><a href="{{ pkg.bugs.url }}">File issue</a></li>
<li role="separator" class="divider"></li>
<li>{@link dev/using-api Using API}</li>
<li>{@link dev/running-in-browser Running in a browser}</li>
<li>{@link dev/writing-rules Writing rules}</li>
<li>{@link dev/writing-plugins Writing plugins}</li>
<li>{@link dev/transformers Writing transformers}</li>
......
/* eslint-disable-next-line import/export, import/no-unresolved */
export * from "./dist/matchers";
export * from "./dist/cjs/matchers";
module.exports = require("./dist/matchers");
module.exports = require("./dist/cjs/matchers");
This diff is collapsed.
{
"name": "html-validate",
"version": "4.14.0",
"description": "html linter",
"version": "5.0.0",
"description": "Offline html5 validator",
"keywords": [
"html",
"lint",
......@@ -19,8 +19,23 @@
"license": "MIT",
"author": "David Sveningsson <ext@sidvind.com>",
"sideEffects": false,
"main": "dist/main.js",
"browser": "dist/browser.js",
"type": "commonjs",
"exports": {
".": {
"require": "./dist/cjs/index.js",
"import": "./dist/es/index.js"
},
"./dist/cjs/*": "./dist/cjs/*",
"./dist/es/*": "./dist/es/*",
"./dist/schema/*": "./dist/schema/*",
"./elements/*": "./elements/*",
"./jest.js": "./jest.js",
"./package.json": "./package.json",
"./test-utils.js": "./test-utils.js"
},
"main": "dist/cjs/index.js",
"module": "dist/es/index.js",
"browser": "dist/cjs/browser.js",
"bin": {
"html-validate": "bin/html-validate.js"
},
......@@ -30,17 +45,14 @@
"elements",
"jest.{js,d.ts}",
"test-utils.{js,d.ts}",
"!**/*.map",
"!dist/types/**",
"!**/*.snap",
"!**/*.spec.d.ts",
"!**/*.spec.js",
"!**/*.spec.ts",
"!**/__fixtures__",
"!**/__mocks__",
"!dist/rules/**/*.d.ts"
"!**/*.spec.{js,ts,d.ts}"
],
"scripts": {
"build": "tsc",
"prebuild": "tsc",
"build": "rollup --config rollup.config.js",
"postbuild": "scripts/pkg",
"build:docs": "grunt docs",
"clean": "rm -rf dist public",
"compatibility": "scripts/compatibility.sh",
......@@ -102,7 +114,7 @@
"src/**/*.ts",
"!src/**/*.spec.ts",
"!src/**/index.ts",
"!src/main.ts",
"!src/index.ts",
"!src/browser.ts",
"!src/cli/html-validate.ts"
],
......@@ -112,9 +124,6 @@
"<rootDir>/elements",
"<rootDir>/src",
"<rootDir>/tests"
],
"snapshotSerializers": [
"pretty-format/build/plugins/ConvertAnsi"
]
},
"dependencies": {
......@@ -122,7 +131,7 @@
"@html-validate/stylish": "^1.0.0",
"@sidvind/better-ajv-errors": "^0.9.0",
"acorn-walk": "^8.0.0",
"ajv": "^7.0.0",
"ajv": "^8.0.0",
"deepmerge": "^4.2.0",
"espree": "^7.3.0",
"glob": "^7.1.0",
......@@ -130,30 +139,36 @@
"json-merge-patch": "^1.0.0",
"kleur": "^4.1.0",
"minimist": "^1.2.0",
"prompts": "^2.0.0"
"prompts": "^2.0.0",
"semver": "^7.0.0"
},
"devDependencies": {
"@babel/core": "7.14.5",
"@babel/preset-env": "7.14.5",
"@babel/core": "7.14.6",
"@babel/preset-env": "7.14.7",
"@commitlint/cli": "12.1.4",
"@html-validate/commitlint-config": "1.3.1",
"@html-validate/eslint-config": "4.4.1",
"@html-validate/eslint-config": "4.4.3",
"@html-validate/eslint-config-jest": "4.4.2",
"@html-validate/eslint-config-typescript": "4.4.0",
"@html-validate/jest-config": "1.2.10",
"@html-validate/jest-config": "2.2.0",
"@html-validate/prettier-config": "1.1.0",
"@html-validate/semantic-release-config": "1.2.15",
"@html-validate/semantic-release-config": "2.0.0",
"@lodder/grunt-postcss": "3.0.1",
"@rollup/plugin-json": "4.1.0",
"@rollup/plugin-replace": "2.4.2",
"@rollup/plugin-typescript": "8.2.1",
"@rollup/plugin-virtual": "2.0.3",
"@types/babar": "0.2.0",
"@types/babel__code-frame": "7.0.2",
"@types/estree": "0.0.47",
"@types/glob": "7.1.3",
"@types/inquirer": "7.3.1",
"@types/inquirer": "7.3.2",
"@types/jest": "26.0.23",
"@types/json-merge-patch": "0.0.5",
"@types/minimist": "1.2.1",
"@types/node": "11.15.54",
"@types/prompts": "2.0.13",
"@types/semver": "7.3.6",
"autoprefixer": "10.2.6",
"babar": "0.2.0",
"babelify": "10.0.0",
......@@ -161,11 +176,9 @@
"canonical-path": "1.0.0",
"cssnano": "5.0.6",
"dgeni": "0.4.14",
"dgeni-front-matter": "2.0.3",
"dgeni-front-matter": "3.0.0",
"dgeni-packages": "0.29.1",
"eslint": "7.28.0",
"eslint-plugin-security": "1.4.0",
"eslint-plugin-sonarjs": "0.7.0",
"eslint": "7.29.0",
"font-awesome": "4.7.0",
"front-matter": "4.0.2",
"grunt": "1.4.1",
......@@ -176,23 +189,25 @@
"grunt-sass": "3.1.0",
"highlight.js": "11.0.1",
"husky": "6.0.0",
"jest": "27.0.4",
"jest": "27.0.5",
"jest-diff": "27.0.2",
"jquery": "3.6.0",
"lint-staged": "11.0.0",
"load-grunt-tasks": "5.1.0",
"marked": "2.0.7",
"marked": "2.1.3",
"minimatch": "3.0.4",
"npm-pkg-lint": "1.3.0",
"postcss": "8.3.2",
"npm-pkg-lint": "1.4.0",
"postcss": "8.3.5",
"prettier": "2.3.1",
"pretty-format": "27.0.2",
"sass": "1.34.1",
"semantic-release": "17.4.3",
"rollup": "2.52.3",
"rollup-plugin-copy": "3.4.0",
"rollup-plugin-dts": "3.0.2",
"sass": "1.35.1",
"semantic-release": "17.4.4",
"serve-static": "1.14.1",
"stringmap": "0.2.2",
"ts-jest": "27.0.3",
"typescript": "4.3.2"
"typescript": "4.3.4"
},
"peerDependencies": {
"jest": "^24 || ^25 || ^26 || ^27"
......@@ -203,7 +218,7 @@
}
},
"engines": {
"node": ">= 10.0"
"node": ">= 12.0"
},
"release": {
"extends": "@html-validate/semantic-release-config"
......
import fs from "fs";
import path from "path";
import json from "@rollup/plugin-json";
import replace from "@rollup/plugin-replace";
import virtual from "@rollup/plugin-virtual";
import copy from "rollup-plugin-copy";
import dts from "rollup-plugin-dts";
import typescript from "@rollup/plugin-typescript";
/**
* @typedef {import('rollup').RollupOptions} RollupOptions
*/
/** @type {string[]} */
const entrypoints = [
"src/index.ts",
"src/browser.ts",
"src/cli/html-validate.ts",
"src/matchers.ts",
"src/transform/test-utils.ts",
];
/** @type {string[]} */
const types = [
"dist/types/index.d.ts",
"dist/types/browser.d.ts",
"dist/types/matchers.d.ts",
"dist/types/transform/test-utils.d.ts",
];
/** @type {string[]} */
const inputs = [...entrypoints, ...types];
/** @type {string[]} */
const external = [
/* nodejs */
"fs",
"path",
/* dependencies */
"@babel/code-frame",
"@html-validate/stylish",
"@sidvind/better-ajv-errors",
"ajv",
"deepmerge",
"glob",
"ignore",
"jest-diff",
"json-merge-patch",
"kleur",
"minimist",
"prompts",
];
const packageJson = fs.readFileSync(path.join(__dirname, "package.json"), "utf-8");
/**
* @param {string} id
* @returns {string|undefined}
*/
function manualChunks(id) {
/** @type {string} */
const base = path.relative(__dirname, id);
if (inputs.includes(base)) {
return undefined;
}
/** @type {string} */
const rel = base.startsWith("src/")
? path.relative(path.join(__dirname, "src"), id)
: path.relative(path.join(__dirname, "dist/types"), id);
if (rel.startsWith("cli/")) {
return "cli";
}
return "core";
}
/**
* @param {string} format
* @returns {RollupOptions[]}
*/
function build(format) {
const resolved = `
import path from "path";
export const projectRoot = path.resolve(__dirname, "../../");
export const distFolder = path.resolve(projectRoot, "dist/${format}");
`;
return [
{
input: entrypoints,
output: {
dir: `dist/${format}`,
format,
sourcemap: true,
manualChunks,
chunkFileNames: "[name].js",
},
external,
plugins: [
virtual({
"package.json": packageJson,
"src/resolve": resolved,
}),
typescript({
outDir: `dist/${format}`,
declaration: false,
declarationDir: undefined,
}),
json(),
replace({
preventAssignment: true,
delimiters: ["", ""],
values: {
/**
* Fix the path from src/package.ts
*/
'"../package.json"': '"../../package.json"',
/**
* Replace __filename global with source filename relative to dist folder
*
* @param {string} filename
*/
__filename: (filename) => {
const relative = path.relative(path.join(__dirname, "src"), filename);
return `"@/${relative}"`;
},
},
}),
],
},
{
input: types,
output: {
dir: `dist/${format}`,
format,
manualChunks,
chunkFileNames: "[name].d.ts",
},
plugins: [
dts(),
copy({
verbose: true,
targets: [{ src: "src/schema/*.json", dest: "dist/schema" }],
}),
],
},
];
}
/** @type {RollupOptions[]} */
export default [...build("cjs"), ...build("es")];
#!/bin/sh
cat > dist/cjs/package.json <<EOF
{
"type": "commonjs"
}
EOF
cat > dist/es/package.json <<EOF
{
"type": "module"
}
EOF
......@@ -2,19 +2,17 @@
export { default as HtmlValidate } from "./htmlvalidate";
export { AttributeData } from "./parser";
export { Config, ConfigData, ConfigError, ConfigLoader, Severity } from "./config";
export { Config, ConfigData, ConfigError, ConfigLoader, Severity, configPresets } from "./config";
export { DynamicValue, HtmlElement, NodeClosed, TextNode } from "./dom";
export { UserError } from "./error";
export { EventDump, TokenDump } from "./engine";
export { UserError, SchemaValidationError } from "./error";
export * from "./event";
export { MetaData, MetaElement, MetaTable } from "./meta";
export { MetaData, MetaElement, MetaTable, MetaCopyableProperty } from "./meta";
export { Rule, RuleDocumentation } from "./rule";
export { Source, Location, ProcessElementContext } from "./context";
export { Report, Reporter, Message, Result } from "./reporter";
export { Transformer, TemplateExtractor } from "./transform";
export { TransformContext, Transformer, TemplateExtractor } from "./transform";
export { Plugin } from "./plugin";
export { Parser } from "./parser";
export { ruleExists } from "./utils";
const pkg = require("../package.json");
export const version = pkg.version;
export { version } from "./package";
import { readFileSync } from "fs";
import { ConfigData } from "../config";
import { UserError } from "../error";
import HtmlValidate from "../htmlvalidate";
import { Report } from "../reporter";
import { ConfigData, UserError, HtmlValidate, Report } from "..";
import { expandFiles, ExpandOptions } from "./expand-files";
import { getFormatter } from "./formatter";
import { IsIgnored } from "./is-ignored";
......