diff --git a/README.md b/README.md index 61623117b47353b6d9aebe5dfd9ea3dd6ff2e0b2..18816d1e3abd08308c27d9a9f9711d8e245cfabf 100644 --- a/README.md +++ b/README.md @@ -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/esm/main.js`) +- ESM browser (`dist/esm/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 diff --git a/bin/html-validate.js b/bin/html-validate.js index 8b8075382782844f4b980215fea59ad2aed3d069..ca2b9d0226b0cd9ac94f0c909f8cb3c3d0d1bccc 100755 --- a/bin/html-validate.js +++ b/bin/html-validate.js @@ -1,4 +1,4 @@ #!/usr/bin/env node "use strict"; -require("../dist/cli/html-validate"); +require("../dist/cjs/html-validate"); diff --git a/docs/dev/running-in-browser.md b/docs/dev/running-in-browser.md new file mode 100644 index 0000000000000000000000000000000000000000..e70edd620a75c24f750263b52574950aaea5a6b8 --- /dev/null +++ b/docs/dev/running-in-browser.md @@ -0,0 +1,105 @@ +--- +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. diff --git a/docs/dgeni/inline-validate/processors/validate-results.js b/docs/dgeni/inline-validate/processors/validate-results.js index 5a2250f0edcd3de1fafc27693eac4c3961351ef5..d601ac5881d99e8e763750dd1752f02bfbf4be8f 100644 --- a/docs/dgeni/inline-validate/processors/validate-results.js +++ b/docs/dgeni/inline-validate/processors/validate-results.js @@ -1,6 +1,7 @@ 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, diff --git a/docs/dgeni/processors/rules.js b/docs/dgeni/processors/rules.js index c0ab6816da646bf4394cab0cd1340857927f7a49..ec1e69248e52532cbc3cd8e107cadf0e867012c3 100644 --- a/docs/dgeni/processors/rules.js +++ b/docs/dgeni/processors/rules.js @@ -1,19 +1,8 @@ -const { default: a17y } = require("../../../dist/config/presets/a17y"); -const { default: document } = require("../../../dist/config/presets/document"); -const { default: recommended } = require("../../../dist/config/presets/recommended"); -const { default: 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]); } diff --git a/docs/dgeni/templates/base.template.html b/docs/dgeni/templates/base.template.html index d87609cf71ff30924394b205e7a1b9287e3af89c..be139139ffcba77c689438236ad6e6012b0cfd62 100644 --- a/docs/dgeni/templates/base.template.html +++ b/docs/dgeni/templates/base.template.html @@ -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> diff --git a/jest.d.ts b/jest.d.ts index a5501a872eb0e1a6d6759e420bb62da5dcf99b9b..c0166699aed886fb54dc4e3dbd7a7feb3564a460 100644 --- a/jest.d.ts +++ b/jest.d.ts @@ -1,2 +1,2 @@ /* eslint-disable-next-line import/export, import/no-unresolved */ -export * from "./dist/matchers"; +export * from "./dist/cjs/matchers"; diff --git a/jest.js b/jest.js index 678839a98237aa3f2bd1e02ae254e7b21b09ebb9..b7699d037c16a481369b76c30c834fa88eecba7e 100644 --- a/jest.js +++ b/jest.js @@ -1 +1 @@ -module.exports = require("./dist/matchers"); +module.exports = require("./dist/cjs/matchers"); diff --git a/package-lock.json b/package-lock.json index 18b97e3cca44f425ebf1e6ae71717546dacab307..53755e80676b8daf5fd58839200f6124d21deeeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,10 @@ "@html-validate/prettier-config": "1.1.0", "@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", @@ -77,6 +81,9 @@ "npm-pkg-lint": "1.4.0", "postcss": "8.3.5", "prettier": "2.3.1", + "rollup": "2.51.2", + "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", @@ -3055,6 +3062,81 @@ "@octokit/openapi-types": "^5.2.0" } }, + "node_modules/@rollup/plugin-json": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", + "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.0.8" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/@rollup/plugin-typescript": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.2.1.tgz", + "integrity": "sha512-Qd2E1pleDR4bwyFxqbjt4eJf+wB0UKVMLc7/BAFDGVdAXQMCsD4DUv5/7/ww47BZCYxWtJqe1Lo0KVNswBJlRw==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "resolve": "^1.17.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0", + "tslib": "*", + "typescript": ">=3.7.0" + } + }, + "node_modules/@rollup/plugin-virtual": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-2.0.3.tgz", + "integrity": "sha512-pw6ziJcyjZtntQ//bkad9qXaBx665SgEL8C8KI5wO8G5iU5MPxvdWrQyVaAvjojGm9tJoS8M9Z/EEepbqieYmw==", + "dev": true, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/pluginutils/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, "node_modules/@semantic-release/changelog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-5.0.1.tgz", @@ -3793,6 +3875,15 @@ "integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==", "dev": true }, + "node_modules/@types/fs-extra": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz", + "integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", @@ -8507,6 +8598,12 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, "node_modules/esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", @@ -12585,6 +12682,15 @@ "node": ">=10" } }, + "node_modules/magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.4" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -18548,6 +18654,119 @@ "inherits": "^2.0.1" } }, + "node_modules/rollup": { + "version": "2.51.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.51.2.tgz", + "integrity": "sha512-ReV2eGEadA7hmXSzjxdDKs10neqH2QURf2RxJ6ayAlq93ugy6qIvXMmbc5cWMGCDh1h5T4thuWO1e2VNbMq8FA==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.1" + } + }, + "node_modules/rollup-plugin-copy": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz", + "integrity": "sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ==", + "dev": true, + "dependencies": { + "@types/fs-extra": "^8.0.1", + "colorette": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "10.0.1", + "is-plain-object": "^3.0.0" + }, + "engines": { + "node": ">=8.3" + } + }, + "node_modules/rollup-plugin-copy/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/rollup-plugin-copy/node_modules/globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup-plugin-copy/node_modules/is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup-plugin-copy/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/rollup-plugin-copy/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/rollup-plugin-dts": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-3.0.2.tgz", + "integrity": "sha512-hswlsdWu/x7k5pXzaLP6OvKRKcx8Bzprksz9i9mUe72zvt8LvqAb/AZpzs6FkLgmyRaN8B6rUQOVtzA3yEt9Yw==", + "dev": true, + "dependencies": { + "magic-string": "^0.25.7" + }, + "engines": { + "node": ">=v12.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/Swatinem" + }, + "optionalDependencies": { + "@babel/code-frame": "^7.12.13" + }, + "peerDependencies": { + "rollup": "^2.48.0", + "typescript": "^4.2.4" + } + }, "node_modules/run-parallel": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", @@ -19205,6 +19424,12 @@ "node": ">=0.10.0" } }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, "node_modules/space-separated-tokens": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", @@ -23112,6 +23337,61 @@ "@octokit/openapi-types": "^5.2.0" } }, + "@rollup/plugin-json": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", + "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.8" + } + }, + "@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + } + }, + "@rollup/plugin-typescript": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.2.1.tgz", + "integrity": "sha512-Qd2E1pleDR4bwyFxqbjt4eJf+wB0UKVMLc7/BAFDGVdAXQMCsD4DUv5/7/ww47BZCYxWtJqe1Lo0KVNswBJlRw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "resolve": "^1.17.0" + } + }, + "@rollup/plugin-virtual": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-2.0.3.tgz", + "integrity": "sha512-pw6ziJcyjZtntQ//bkad9qXaBx665SgEL8C8KI5wO8G5iU5MPxvdWrQyVaAvjojGm9tJoS8M9Z/EEepbqieYmw==", + "dev": true, + "requires": {} + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + } + } + }, "@semantic-release/changelog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-5.0.1.tgz", @@ -23665,6 +23945,15 @@ "integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==", "dev": true }, + "@types/fs-extra": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz", + "integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", @@ -27398,6 +27687,12 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", @@ -30578,6 +30873,15 @@ "yallist": "^4.0.0" } }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -34821,6 +35125,88 @@ "inherits": "^2.0.1" } }, + "rollup": { + "version": "2.51.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.51.2.tgz", + "integrity": "sha512-ReV2eGEadA7hmXSzjxdDKs10neqH2QURf2RxJ6ayAlq93ugy6qIvXMmbc5cWMGCDh1h5T4thuWO1e2VNbMq8FA==", + "dev": true, + "requires": { + "fsevents": "~2.3.1" + } + }, + "rollup-plugin-copy": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz", + "integrity": "sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ==", + "dev": true, + "requires": { + "@types/fs-extra": "^8.0.1", + "colorette": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "10.0.1", + "is-plain-object": "^3.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + } + }, + "is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } + } + }, + "rollup-plugin-dts": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-3.0.2.tgz", + "integrity": "sha512-hswlsdWu/x7k5pXzaLP6OvKRKcx8Bzprksz9i9mUe72zvt8LvqAb/AZpzs6FkLgmyRaN8B6rUQOVtzA3yEt9Yw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "magic-string": "^0.25.7" + } + }, "run-parallel": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", @@ -35338,6 +35724,12 @@ } } }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, "space-separated-tokens": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", diff --git a/package.json b/package.json index 83cc8236c352ae25bff911687f06256c6656fa9b..9f91f4f7cc5e12c44fd5a474bf1098eb095cd49d 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,20 @@ "license": "MIT", "author": "David Sveningsson <ext@sidvind.com>", "sideEffects": false, - "main": "dist/index.js", - "browser": "dist/browser.js", + "type": "commonjs", + "exports": { + ".": { + "require": "./dist/cjs/index.js", + "import": "./dist/es/index.js" + }, + "./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 +42,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", @@ -141,6 +150,10 @@ "@html-validate/prettier-config": "1.1.0", "@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", @@ -181,6 +194,9 @@ "npm-pkg-lint": "1.4.0", "postcss": "8.3.5", "prettier": "2.3.1", + "rollup": "2.51.2", + "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", diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000000000000000000000000000000000000..eca9ae70f6715a8e2b0f5382c01a9ac6964c3a77 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,153 @@ +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")]; diff --git a/scripts/pkg b/scripts/pkg new file mode 100755 index 0000000000000000000000000000000000000000..10f58816d2470691b71eda5feb59fcc678963ef4 --- /dev/null +++ b/scripts/pkg @@ -0,0 +1,13 @@ +#!/bin/sh + +cat > dist/cjs/package.json <<EOF +{ + "type": "commonjs" +} +EOF + +cat > dist/es/package.json <<EOF +{ + "type": "module" +} +EOF diff --git a/src/browser.ts b/src/browser.ts index 2bf3631180277df9571400efc13d57f1f2014a38..abf3a68823565dd0b98e7cd3f0edbaddfd2a8529 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -2,12 +2,12 @@ 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 { 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"; @@ -15,7 +15,4 @@ export { 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"; diff --git a/src/cli/html-validate.ts b/src/cli/html-validate.ts index 8cb02aaac4fbb307dd9911253e7f0fdbabb585d2..f2cc05ae982a6a9525f0606073ba7e564f027545 100644 --- a/src/cli/html-validate.ts +++ b/src/cli/html-validate.ts @@ -3,12 +3,10 @@ import path from "path"; import kleur from "kleur"; import minimist from "minimist"; import { TokenDump, SchemaValidationError, UserError, Report, Reporter, Result } from ".."; +import * as pkg from "../package"; import { eventFormatter } from "./json"; - import { CLI } from "./cli"; -const pkg = require("../../package.json"); - enum Mode { LINT, INIT, @@ -125,7 +123,7 @@ function handleUnknownError(err: Error): void { console.error(err); } console.groupEnd(); - const bugUrl = `${pkg.bugs.url}?issuable_template=Bug`; + const bugUrl = `${pkg.bugs}?issuable_template=Bug`; console.error(kleur.red(`This is a bug in ${pkg.name}-${pkg.version}.`)); console.error( kleur.red( diff --git a/src/config/config.ts b/src/config/config.ts index 9d40c6d9537fb00739fe543616293dcc9e5f3cfc..cc2df4438c6b227eca8aacd034532449011b1d23 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -10,6 +10,7 @@ import { Plugin } from "../plugin"; import schema from "../schema/config.json"; import { TransformContext, Transformer, TRANSFORMER_API } from "../transform"; import { requireUncached } from "../utils"; +import { projectRoot } from "../resolve"; import bundledRules from "../rules"; import { Rule } from "../rule"; import { ConfigData, RuleConfig, RuleOptions, TransformMap } from "./config-data"; @@ -251,7 +252,6 @@ export class Config { const metaTable = new MetaTable(); const source = this.config.elements || ["html5"]; - const root = path.resolve(__dirname, "..", ".."); /* extend validation schema from plugins */ for (const plugin of this.getPlugins()) { @@ -271,7 +271,7 @@ export class Config { let filename: string; /* try searching builtin metadata */ - filename = path.join(root, "elements", `${entry}.json`); + filename = path.join(projectRoot, "elements", `${entry}.json`); if (fs.existsSync(filename)) { metaTable.loadFromFile(filename); continue; diff --git a/src/config/index.ts b/src/config/index.ts index 33c5732b28d7f442fd5bc2947a34b950aa8e67b5..94c5153a1074bd5b69a76c110ad1b1039635d5fd 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -2,5 +2,6 @@ export { Config } from "./config"; export { ConfigData, RuleConfig, RuleOptions } from "./config-data"; export { ConfigLoader } from "./config-loader"; export { ConfigError } from "./error"; +export { default as configPresets } from "./presets"; export { ResolvedConfig } from "./resolved-config"; export { Severity } from "./severity"; diff --git a/src/index.ts b/src/index.ts index 873a5ab7e001b1c93f5aafea33363452c0a496e2..06b19d98c232e95fb6ccf9be723ec44486b070b2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ /* used when calling require('htmlvalidate'); */ export * from "./browser"; -export { CLI } from "./cli/cli"; export { Formatter, getFormatter as formatterFactory } from "./formatters"; +export { CLI } from "./cli/cli"; diff --git a/src/package.ts b/src/package.ts new file mode 100644 index 0000000000000000000000000000000000000000..68cbc31ed9fce4f395e19ef34e13b0e495e5fdd6 --- /dev/null +++ b/src/package.ts @@ -0,0 +1,6 @@ +const pkg = require("../package.json"); + +export const name: string = pkg.name; +export const version: string = pkg.version; +export const homepage: string = pkg.homepage; +export const bugs: string = pkg.bugs.url; diff --git a/src/resolve.ts b/src/resolve.ts new file mode 100644 index 0000000000000000000000000000000000000000..fadd2b545ccce86b3e68860ac9e0063aa66db609 --- /dev/null +++ b/src/resolve.ts @@ -0,0 +1,9 @@ +/** + * This module will be overwritten by bundler during compilation. Make sure that + * any changes to this file is present in the virtual module. + */ + +import path from "path"; + +export const projectRoot = path.resolve(__dirname, ".."); +export const distFolder = path.join(projectRoot, "src"); diff --git a/src/rule.ts b/src/rule.ts index f03de32353dfde4b2fcb760993f1ab04dcb967b9..47e7f1ce66eccf41b489154a63b24bb8210f6e4a 100644 --- a/src/rule.ts +++ b/src/rule.ts @@ -8,11 +8,11 @@ import { Parser } from "./parser"; import { Reporter } from "./reporter"; import { MetaTable, MetaLookupableProperty } from "./meta"; import { SchemaValidationError } from "./error"; +import { distFolder } from "./resolve"; +import { homepage } from "./package"; export { SchemaObject } from "ajv"; -const homepage = require("../package.json").homepage; - const remapEvents: Record<string, string> = { "tag:open": "tag:start", "tag:close": "tag:end", @@ -355,7 +355,12 @@ export abstract class Rule<ContextType = void, OptionsType = void> { } export function ruleDocumentationUrl(filename: string): string { + /* during bundling all __filename's are converted to paths relative to the src + * folder and with the @/ prefix, by replacing the @ with the dist folder we + * can resolve the path properly */ + filename = filename.replace("@", distFolder); const p = path.parse(filename); - const rel = path.relative(path.join(__dirname, "rules"), path.join(p.dir, p.name)); + const root = path.join(distFolder, "rules"); + const rel = path.relative(root, path.join(p.dir, p.name)); return `${homepage}/rules/${rel}.html`; } diff --git a/src/rules/unrecognized-char-ref.ts b/src/rules/unrecognized-char-ref.ts index 58d3a99190a8dc858f1eabae5c1bea2050638633..3d70684b81d7cc86e9e9ce6fcdcc9c9dceb44c8f 100644 --- a/src/rules/unrecognized-char-ref.ts +++ b/src/rules/unrecognized-char-ref.ts @@ -1,9 +1,12 @@ +import path from "path"; import { Location, sliceLocation } from "../context"; import { NodeType } from "../dom"; import { AttributeEvent, ElementReadyEvent } from "../event"; +import { projectRoot } from "../resolve"; import { Rule, RuleDocumentation, ruleDocumentationUrl } from "../rule"; -const entities = require("../../elements/entities.json"); +/* eslint-disable-next-line import/no-dynamic-require */ +const entities = require(path.join(projectRoot, "elements/entities.json")); const regexp = /&([a-z0-9]+|#x?[0-9a-f]+);/gi; diff --git a/test-utils.d.ts b/test-utils.d.ts index d800fcb08d97e1b8807750b0289cf92e48f956b8..bf30b564175a1ea7a19665b929704afcfc2d743c 100644 --- a/test-utils.d.ts +++ b/test-utils.d.ts @@ -1,2 +1,2 @@ /* eslint-disable-next-line import/export, import/no-unresolved */ -export * from "./dist/transform/test-utils"; +export * from "./dist/cjs/test-utils"; diff --git a/test-utils.js b/test-utils.js index 2caf80049b9108dc45a50cccb68c83dbd795ef0f..e2983c745c374331c64d00fc92e01587547ba5cd 100644 --- a/test-utils.js +++ b/test-utils.js @@ -1 +1 @@ -module.exports = require("./dist/transform/test-utils"); +module.exports = require("./dist/cjs/test-utils"); diff --git a/tsconfig.json b/tsconfig.json index 881b92cef2450e8960b5900dd1f0d4754ccf46dd..7392f6b9d786606de9bab03149d2f011e3628ecb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,24 @@ { "compilerOptions": { - "alwaysStrict": true, "rootDir": "./src", + "outDir": "dist", + + "emitDeclarationOnly": true, "declaration": true, - "esModuleInterop": true, + "declarationDir": "dist/types", + + "target": "es2017", "lib": ["es2017"], - "module": "commonjs", + "module": "esnext", + "moduleResolution": "node", + + "alwaysStrict": true, + "esModuleInterop": true, "noImplicitAny": true, - "outDir": "dist", "resolveJsonModule": true, "sourceMap": true, - "target": "es2017", "strict": true }, "include": ["src/**/*.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "**/*.spec.ts"] }