This commit is contained in:
2022-04-07 18:09:31 +05:30
parent 36ea86d8e6
commit 1c779ef816
14095 changed files with 1769361 additions and 47 deletions
+170
View File
@@ -0,0 +1,170 @@
# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [1.3.2](https://github.com/webpack-contrib/html-loader/compare/v1.3.1...v1.3.2) (2020-10-09)
### Chore
* update `schema-utils`
### [1.3.1](https://github.com/webpack-contrib/html-loader/compare/v1.3.0...v1.3.1) (2020-09-21)
### Bug Fixes
* avoid removing redundant attributes ([#324](https://github.com/webpack-contrib/html-loader/issues/324)) ([ab299ac](https://github.com/webpack-contrib/html-loader/commit/ab299acd8784f96f9191988133fb74f72aaf89c8))
## [1.3.0](https://github.com/webpack-contrib/html-loader/compare/v1.2.1...v1.3.0) (2020-08-25)
### Features
* `...` syntax to extend default tag and attributes ([#317](https://github.com/webpack-contrib/html-loader/issues/317)) ([f02bb48](https://github.com/webpack-contrib/html-loader/commit/f02bb48a1124fc567b858234b1cf3d3e68bb6c76))
### Bug Fixes
* handle `script` tags in SVG ([#315](https://github.com/webpack-contrib/html-loader/issues/315)) ([d021e42](https://github.com/webpack-contrib/html-loader/commit/d021e42fc830d36cf2ab3e9d483fd5723af23ba8))
### [1.2.1](https://github.com/webpack-contrib/html-loader/compare/v1.2.0...v1.2.1) (2020-08-19)
### Bug Fixes
* ignore template sources ([#314](https://github.com/webpack-contrib/html-loader/issues/314)) ([078d9bd](https://github.com/webpack-contrib/html-loader/commit/078d9bdc36d256d6a78ab0ae9b8ccddbef11393c))
## [1.2.0](https://github.com/webpack-contrib/html-loader/compare/v1.1.0...v1.2.0) (2020-08-18)
### Features
* support SVG tags ([#302](https://github.com/webpack-contrib/html-loader/issues/302)) ([1acd204](https://github.com/webpack-contrib/html-loader/commit/1acd20448dbe976b883597b135bb8ac9e1b71d1a))
### Bug Fixes
* do not handle non standard `script` types ([ddad9f2](https://github.com/webpack-contrib/html-loader/commit/ddad9f2d6f5ab75fe2afd247bf55b1646c6e1c31))
* inline syntax for sources ([#310](https://github.com/webpack-contrib/html-loader/issues/310)) ([c247cfa](https://github.com/webpack-contrib/html-loader/commit/c247cfa9ad66281b28aef5397c8c2d2786f05867))
* linefeed characters in sources ([#311](https://github.com/webpack-contrib/html-loader/issues/311)) ([b8ee9ee](https://github.com/webpack-contrib/html-loader/commit/b8ee9ee0d60848e84e52fb117c1f3cdc2ebf08d7))
* minimize is more safely ([#304](https://github.com/webpack-contrib/html-loader/issues/304)) ([03152b1](https://github.com/webpack-contrib/html-loader/commit/03152b1d3b807a287d84302f6a9987ceb22d395c))
* perf ([#300](https://github.com/webpack-contrib/html-loader/issues/300)) ([d69f259](https://github.com/webpack-contrib/html-loader/commit/d69f259d2a6b4bc9ba9c163fd2d70989c3f3a6ff))
## [1.1.0](https://github.com/webpack-contrib/html-loader/compare/v1.0.0...v1.1.0) (2020-04-02)
### Features
* added support for an async functions to the `preprocessor` option ([#272](https://github.com/webpack-contrib/html-loader/issues/272)) ([e59324b](https://github.com/webpack-contrib/html-loader/commit/e59324b929557a9e1da5e1713748351cf37efdb1))
* allowed to handle an attribute without a tag ([#273](https://github.com/webpack-contrib/html-loader/issues/273)) ([c0d6d5b](https://github.com/webpack-contrib/html-loader/commit/c0d6d5b9338af219a5cd81779ee2bcd9254420ed))
### Bug Fixes
* hash processing improved ([#275](https://github.com/webpack-contrib/html-loader/issues/275)) ([74d2c60](https://github.com/webpack-contrib/html-loader/commit/74d2c607c519bdaad995a51e74b6317c8cfaad14))
## [1.0.0](https://github.com/ryanclark/html-loader/compare/v0.5.5...v1.0.0) (2020-03-19)
### ⚠ BREAKING CHANGES
* for parsing HTML now we use `htmlparser2` package
* the `attrs` option was renamed to the `attributes` option
* the `interpolate` option was removed, please consider migration on the [`preprocessor`](https://github.com/webpack-contrib/html-loader#preprocessor)
* the `minimize` option is `true` by default in `production` mode. You need to list all options for `html-minifier` if you use `object` notation.
* uppercase tags and uppercase attributes are handled by default
* the `root` option was moved under the `attributes` option, please look at the [documentation](https://github.com/webpack-contrib/html-loader#object)
* emit an error on broken HTML syntax when minimization is enabled
* By default, now we process the following tags with attributes:
* the `src` attribute of the `audio` tag
* the `src` attribute of the `embed` tag
* the `src` attribute of the `img` tag
* the `srcset` attribute of the `img` tag
* the `src` attribute of the `input` tag
* the `href` attribute of the `link` tag (only for stylesheets)
* the `data` attribute of the `object` tag
* the `src` attribute of the `script` tag
* the `src` attribute of the `source` tag
* the `srcset` attribute of the `source` tag
* the `src` attribute of the `track` tag
* the `poster` attribute of the `video` tag
* the `src` attribute of the `video` tag
* the `attributes` option should be `Boolean` or `Object`, please look at the [documentation](https://github.com/webpack-contrib/html-loader#object)
* the `exportAsDefault` option were removed in favor the `esModules` option
* the `exportAsEs6Default` option were removed in favor the `esModules` option
### Features
* handle more tags and attributes
* added the `preprocessor` option
* added the `esModule` option
* add the `rulFilter` option for filtering some of urls, please look at the [documentation](https://github.com/webpack-contrib/html-loader#urlfilter)
* allow to setup how to handle and filter tags and attributes, please look at the [documentation](https://github.com/webpack-contrib/html-loader#list)
* improve error reporting
### Bug Fixes
* adding quotes when necessary for unquoted sources
* do not handle empty attributes
* escape `\u2028` and `\u2029` characters
* handle only valid srcset tags
* parser tags and attributes according spec
* reduce `import`/`require` count
* reduce size of generated modules
* respect `#hash` in sources
* support ES6 syntax in `script` tags when minimize
* support ES6 import of urls
<a name="0.5.5"></a>
## [0.5.5](https://github.com/webpack-contrib/html-loader/compare/v0.5.4...v0.5.5) (2018-01-17)
### Bug Fixes
* **index:** don't prepend `./` to the URL on `interpolate=require` (`options.interpolate`) ([#165](https://github.com/webpack-contrib/html-loader/issues/165)) ([9515410](https://github.com/webpack-contrib/html-loader/commit/9515410))
<a name="0.5.4"></a>
## [0.5.4](https://github.com/webpack-contrib/html-loader/compare/v0.5.1...v0.5.4) (2018-01-05)
### Bug Fixes
* ignore attribute if `mailto:` is present ([#145](https://github.com/webpack-contrib/html-loader/issues/145)) ([4b13d4c](https://github.com/webpack-contrib/html-loader/commit/4b13d4c))
* **index:** escape double quotes correctly (`options.interpolate`) ([#154](https://github.com/webpack-contrib/html-loader/issues/154)) ([1ef5de4](https://github.com/webpack-contrib/html-loader/commit/1ef5de4))
<a name="0.5.1"></a>
## [0.5.1](https://github.com/webpack/html-loader/compare/v0.5.0...v0.5.1) (2017-08-08)
### Bug Fixes
* Support for empty tags in tag-attribute matching ([#133](https://github.com/webpack/html-loader/issues/133)) ([6efa6de](https://github.com/webpack/html-loader/commit/6efa6de)), closes [#129](https://github.com/webpack/html-loader/issues/129)
<a name="0.5.0"></a>
# [0.5.0](https://github.com/webpack/html-loader/compare/v0.4.3...v0.5.0) (2017-07-26)
### Features
* add support for empty tags in `tag:attribute` matching ([#129](https://github.com/webpack/html-loader/issues/129)) ([70370dc](https://github.com/webpack/html-loader/commit/70370dc))
<a name="0.4.5"></a>
## [0.4.5](https://github.com/webpack/html-loader/compare/v0.4.3...v0.4.5) (2017-07-26)
### Bug Fixes
* es6 default export ([fae0309](https://github.com/webpack/html-loader/commit/fae0309))
* Handle es6 default export ([e04e969](https://github.com/webpack/html-loader/commit/e04e969))
* **getOptions:** deprecation warn in loaderUtils ([#114](https://github.com/webpack/html-loader/issues/114)) ([3d47e98](https://github.com/webpack/html-loader/commit/3d47e98))
### Features
* Adds exportAsDefault ([37d40d8](https://github.com/webpack/html-loader/commit/37d40d8))
+20
View File
@@ -0,0 +1,20 @@
Copyright JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+834
View File
@@ -0,0 +1,834 @@
<div align="center">
<img width="200" height="200" src="https://worldvectorlogo.com/logos/html5.svg" alt="html-loader">
<a href="https://github.com/webpack/webpack">
<img width="200" height="200" vspace="" hspace="25" src="https://webpack.js.org/assets/icon-square-big.svg" alt="webpack">
</a>
</div>
[![npm][npm]][npm-url]
[![node][node]][node-url]
[![deps][deps]][deps-url]
[![tests][tests]][tests-url]
[![coverage][cover]][cover-url]
[![chat][chat]][chat-url]
[![size][size]][size-url]
# html-loader
Exports HTML as string. HTML is minimized when the compiler demands.
## Getting Started
To begin, you'll need to install `html-loader`:
```console
npm install --save-dev html-loader
```
Then add the plugin to your `webpack` config. For example:
**file.js**
```js
import html from './file.html';
```
**webpack.config.js**
```js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
loader: 'html-loader',
},
],
},
};
```
## Options
| Name | Type | Default | Description |
| :---------------------------------: | :-----------------: | :------------------------------------------: | :----------------------------------------------- |
| **[`attributes`](#attributes)** | `{Boolean\|Object}` | `true` | Enables/Disables attributes handling |
| **[`preprocessor`](#preprocessor)** | `{Function}` | `undefined` | Allows pre-processing of content before handling |
| **[`minimize`](#minimize)** | `{Boolean\|Object}` | `true` in production mode, otherwise `false` | Tell `html-loader` to minimize HTML |
| **[`esModule`](#esmodule)** | `{Boolean}` | `false` | Use ES modules syntax |
### `attributes`
Type: `Boolean|Object`
Default: `true`
By default every loadable attributes (for example - `<img src="image.png">`) is imported (`const img = require('./image.png')` or `import img from "./image.png""`).
You may need to specify loaders for images in your configuration (recommended `file-loader` or `url-loader`).
Supported tags and attributes:
- the `src` attribute of the `audio` tag
- the `src` attribute of the `embed` tag
- the `src` attribute of the `img` tag
- the `srcset` attribute of the `img` tag
- the `src` attribute of the `input` tag
- the `href` attribute of the `link` tag (only for stylesheets)
- the `data` attribute of the `object` tag
- the `src` attribute of the `script` tag
- the `href` attribute of the `script` tag
- the `xlink:href` attribute of the `script` tag
- the `src` attribute of the `source` tag
- the `srcset` attribute of the `source` tag
- the `src` attribute of the `track` tag
- the `poster` attribute of the `video` tag
- the `src` attribute of the `video` tag
- the `xlink:href` attribute of the `image` tag
- the `href` attribute of the `image` tag
- the `xlink:href` attribute of the `use` tag
- the `href` attribute of the `use` tag
#### `Boolean`
The `true` value enables processing of all default elements and attributes, the `false` disable processing of all attributes.
**webpack.config.js**
```js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
loader: 'html-loader',
options: {
// Disables attributes processing
attributes: false,
},
},
],
},
};
```
#### `Object`
Allows you to specify which tags and attributes to process, filter them, filter urls and process sources starts with `/`.
For example:
**webpack.config.js**
```js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
loader: 'html-loader',
options: {
attributes: {
list: [
// All default supported tags and attributes
'...',
{
tag: 'img',
attribute: 'data-src',
type: 'src',
},
{
tag: 'img',
attribute: 'data-srcset',
type: 'srcset',
},
],
urlFilter: (attribute, value, resourcePath) => {
// The `attribute` argument contains a name of the HTML attribute.
// The `value` argument contains a value of the HTML attribute.
// The `resourcePath` argument contains a path to the loaded HTML file.
if (/example\.pdf$/.test(value)) {
return false;
}
return true;
},
root: '.',
},
},
},
],
},
};
```
#### `list`
Type: `Array`
Default: [supported tags and attributes](#attributes).
Allows to setup which tags and attributes to process and how, and the ability to filter some of them.
Using `...` syntax allows you to extend [default supported tags and attributes](#attributes).
For example:
**webpack.config.js**
```js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
loader: 'html-loader',
options: {
attributes: {
list: [
// All default supported tags and attributes
'...',
{
tag: 'img',
attribute: 'data-src',
type: 'src',
},
{
tag: 'img',
attribute: 'data-srcset',
type: 'srcset',
},
{
// Tag name
tag: 'link',
// Attribute name
attribute: 'href',
// Type of processing, can be `src` or `scrset`
type: 'src',
// Allow to filter some attributes
filter: (tag, attribute, attributes, resourcePath) => {
// The `tag` argument contains a name of the HTML tag.
// The `attribute` argument contains a name of the HTML attribute.
// The `attributes` argument contains all attributes of the tag.
// The `resourcePath` argument contains a path to the loaded HTML file.
if (/my-html\.html$/.test(resourcePath)) {
return false;
}
if (!/stylesheet/i.test(attributes.rel)) {
return false;
}
if (
attributes.type &&
attributes.type.trim().toLowerCase() !== 'text/css'
) {
return false;
}
return true;
},
},
],
},
},
},
],
},
};
```
If the tag name is not specified it will process all the tags.
> You can use your custom filter to specify html elements to be processed.
For example:
**webpack.config.js**
```js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
loader: 'html-loader',
options: {
attributes: {
list: [
{
// Attribute name
attribute: 'src',
// Type of processing, can be `src` or `scrset`
type: 'src',
// Allow to filter some attributes (optional)
filter: (tag, attribute, attributes, resourcePath) => {
// The `tag` argument contains a name of the HTML tag.
// The `attribute` argument contains a name of the HTML attribute.
// The `attributes` argument contains all attributes of the tag.
// The `resourcePath` argument contains a path to the loaded HTML file.
// choose all HTML tags except img tag
return tag.toLowerCase() !== 'img';
},
},
],
},
},
},
],
},
};
```
#### `urlFilter`
Type: `Function`
Default: `undefined`
Allow to filter urls. All filtered urls will not be resolved (left in the code as they were written).
All non requestable sources (for example `<img src="javascript:void(0)">`) do not handle by default.
```js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
loader: 'html-loader',
options: {
attributes: {
urlFilter: (attribute, value, resourcePath) => {
// The `attribute` argument contains a name of the HTML attribute.
// The `value` argument contains a value of the HTML attribute.
// The `resourcePath` argument contains a path to the loaded HTML file.
if (/example\.pdf$/.test(value)) {
return false;
}
return true;
},
},
},
},
],
},
};
```
#### `root`
Type: `String`
Default: `undefined`
For urls that start with a `/`, the default behavior is to not translate them.
If a `root` query parameter is set, however, it will be prepended to the url and then translated.
**webpack.config.js**
```js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
loader: 'html-loader',
options: {
attributes: {
root: '.',
},
},
},
],
},
};
```
### `preprocessor`
Type: `Function`
Default: `undefined`
Allows pre-processing of content before handling.
> ⚠ You should always return valid HTML
**file.hbs**
```hbs
<div>
<p>{{firstname}} {{lastname}}</p>
<img src="image.png" alt="alt" />
<div>
```
#### `Function`
You can set the `preprocessor` option as a `Function` instance.
**webpack.config.js**
```js
const Handlebars = require('handlebars');
module.exports = {
module: {
rules: [
{
test: /\.hbs$/i,
loader: 'html-loader',
options: {
preprocessor: (content, loaderContext) => {
let result;
try {
result = Handlebars.compile(content)({
firstname: 'Value',
lastname: 'OtherValue',
});
} catch (error) {
loaderContext.emitError(error);
return content;
}
return result;
},
},
},
],
},
};
```
You can also set the `preprocessor` option as an asynchronous function instance.
For example:
**webpack.config.js**
```js
const Handlebars = require('handlebars');
module.exports = {
module: {
rules: [
{
test: /\.hbs$/i,
loader: 'html-loader',
options: {
preprocessor: async (content, loaderContext) => {
let result;
try {
result = await Handlebars.compile(content)({
firstname: 'Value',
lastname: 'OtherValue',
});
} catch (error) {
await loaderContext.emitError(error);
return content;
}
return result;
},
},
},
],
},
};
```
### `minimize`
Type: `Boolean|Object`
Default: `true` in production mode, otherwise `false`
Tell `html-loader` to minimize HTML.
#### `Boolean`
The enabled rules for minimizing by default are the following ones:
```js
({
caseSensitive: true,
collapseWhitespace: true,
conservativeCollapse: true,
keepClosingSlash: true,
minifyCSS: true,
minifyJS: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
});
```
**webpack.config.js**
```js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
loader: 'html-loader',
options: {
minimize: true,
},
},
],
},
};
```
#### `Object`
**webpack.config.js**
See [html-minifier-terser](https://github.com/DanielRuf/html-minifier-terser)'s documentation for more information on the available options.
The rules can be disabled using the following options in your `webpack.conf.js`
**webpack.config.js**
```js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
loader: 'html-loader',
options: {
minimize: {
removeComments: false,
collapseWhitespace: false,
},
},
},
],
},
};
```
### `esModule`
Type: `Boolean`
Default: `false`
By default, `html-loader` generates JS modules that use the CommonJS modules syntax.
There are some cases in which using ES modules is beneficial, like in the case of [module concatenation](https://webpack.js.org/plugins/module-concatenation-plugin/) and [tree shaking](https://webpack.js.org/guides/tree-shaking/).
You can enable a ES module syntax using:
**webpack.config.js**
```js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
loader: 'html-loader',
options: {
esModule: true,
},
},
],
},
};
```
## Examples
### CDN
**webpack.config.js**
```js
module.exports = {
module: {
rules: [
{ test: /\.jpg$/, loader: 'file-loader' },
{ test: /\.png$/, loader: 'url-loader' },
],
},
output: {
publicPath: 'http://cdn.example.com/[hash]/',
},
};
```
**file.html**
```html
<img src="image.jpg" data-src="image2x.png" />
```
**index.js**
```js
require('html-loader!./file.html');
// => '<img src="http://cdn.example.com/49eba9f/a992ca.jpg" data-src="image2x.png">'
```
```js
require('html-loader?{"attributes":{"list":[{"tag":"img","attribute":"data-src","type":"src"}]}}!./file.html');
// => '<img src="image.jpg" data-src="data:image/png;base64,..." >'
```
```js
require('html-loader?{"attributes":{"list":[{"tag":"img","attribute":"src","type":"src"},{"tag":"img","attribute":"data-src","type":"src"}]}}!./file.html');
// => '<img src="http://cdn.example.com/49eba9f/a992ca.jpg" data-src="data:image/png;base64,..." >'
```
```js
require('html-loader?-attributes!./file.html');
// => '<img src="image.jpg" data-src="image2x.png" >'
```
> :warning: `-attributes` sets `attributes: false`.
### Process `script` and `link` tags
**script.file.js**
```js
console.log(document);
```
**style.file.css**
```css
a {
color: red;
}
```
**file.html**
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Title of the document</title>
<link rel="stylesheet" type="text/css" href="./style.file.css" />
</head>
<body>
Content of the document......
<script src="./script.file.js"></script>
</body>
</html>
```
**webpack.config.js**
```js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
use: ['file-loader?name=[name].[ext]', 'extract-loader', 'html-loader'],
},
{
test: /\.js$/i,
exclude: /\.file.js$/i,
loader: 'babel-loader',
},
{
test: /\.file.js$/i,
loader: 'file-loader',
},
{
test: /\.css$/i,
exclude: /\.file.css$/i,
loader: 'css-loader',
},
{
test: /\.file.css$/i,
loader: 'file-loader',
},
],
},
};
```
### 'Root-relative' URLs
With the same configuration as in the CDN example:
**file.html**
```html
<img src="/image.jpg" />
```
**scripts.js**
```js
require('html-loader!./file.html');
// => '<img src="/image.jpg">'
```
**other-scripts.js**
```js
require('html-loader?{"attributes":{"root":"."}}!./file.html');
// => '<img src="http://cdn.example.com/49eba9f/a992ca.jpg">'
```
### Templating
You can use any template system. Below is an example for [handlebars](https://handlebarsjs.com/).
**file.hbs**
```hbs
<div>
<p>{{firstname}} {{lastname}}</p>
<img src="image.png" alt="alt" />
<div>
```
**webpack.config.js**
```js
const Handlebars = require('handlebars');
module.exports = {
module: {
rules: [
{
test: /\.hbs$/i,
loader: 'html-loader',
options: {
preprocessor: (content, loaderContext) => {
let result;
try {
result = Handlebars.compile(content)({
firstname: 'Value',
lastname: 'OtherValue',
});
} catch (error) {
loaderContext.emitError(error);
return content;
}
return result;
},
},
},
],
},
};
```
### PostHTML
You can use [PostHTML](https://github.com/posthtml/posthtml) without any additional loaders.
**file.html**
```html
<img src="image.jpg" />
```
**webpack.config.js**
```js
const posthtml = require('posthtml');
const posthtmlWebp = require('posthtml-webp');
module.exports = {
module: {
rules: [
{
test: /\.hbs$/i,
loader: 'html-loader',
options: {
preprocessor: (content, loaderContext) => {
let result;
try {
result = posthtml().use(plugin).process(content, { sync: true });
} catch (error) {
loaderContext.emitError(error);
return content;
}
return result.html;
},
},
},
],
},
};
```
### Export into HTML files
A very common scenario is exporting the HTML into their own _.html_ file, to
serve them directly instead of injecting with javascript. This can be achieved
with a combination of 3 loaders:
- [file-loader](https://github.com/webpack/file-loader)
- [extract-loader](https://github.com/peerigon/extract-loader)
- html-loader
The html-loader will parse the URLs, require the images and everything you
expect. The extract loader will parse the javascript back into a proper html
file, ensuring images are required and point to proper path, and the file loader
will write the _.html_ file for you. Example:
**webpack.config.js**
```js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
use: ['file-loader?name=[name].[ext]', 'extract-loader', 'html-loader'],
},
],
},
};
```
## Contributing
Please take a moment to read our contributing guidelines if you haven't yet done so.
[CONTRIBUTING](./.github/CONTRIBUTING.md)
## License
[MIT](./LICENSE)
[npm]: https://img.shields.io/npm/v/html-loader.svg
[npm-url]: https://npmjs.com/package/html-loader
[node]: https://img.shields.io/node/v/html-loader.svg
[node-url]: https://nodejs.org
[deps]: https://david-dm.org/webpack-contrib/html-loader.svg
[deps-url]: https://david-dm.org/webpack-contrib/html-loader
[tests]: https://github.com/webpack-contrib/html-loader/workflows/html-loader/badge.svg
[tests-url]: https://github.com/webpack-contrib/html-loader/actions
[cover]: https://codecov.io/gh/webpack-contrib/html-loader/branch/master/graph/badge.svg
[cover-url]: https://codecov.io/gh/webpack-contrib/html-loader
[chat]: https://img.shields.io/badge/gitter-webpack%2Fwebpack-brightgreen.svg
[chat-url]: https://gitter.im/webpack/webpack
[size]: https://packagephobia.now.sh/badge?p=html-loader
[size-url]: https://packagephobia.now.sh/result?p=html-loader
+63
View File
@@ -0,0 +1,63 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function getIndices(value) {
const result = [];
let index = value.indexOf('\n');
while (index !== -1) {
result.push(index + 1);
index = value.indexOf('\n', index + 1);
}
result.push(value.length + 1);
return result;
}
function offsetToPosition(source, offset) {
let index = -1;
const indices = getIndices(source);
const {
length
} = indices;
if (offset < 0) {
return {};
} // eslint-disable-next-line no-plusplus
while (++index < length) {
if (indices[index] > offset) {
return {
line: index + 1,
column: offset - (indices[index - 1] || 0) + 1,
offset
};
}
}
return {};
}
class HtmlSourceError extends Error {
constructor(error, startIndex, endIndex, source) {
super(error);
this.name = 'HtmlSourceError';
this.message = `${this.name}: ${this.message}`;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.source = source;
const startPosition = offsetToPosition(source, this.startIndex);
const endPosition = offsetToPosition(source, this.endIndex);
this.message += ` (From line ${startPosition.line}, column ${startPosition.column}; to line ${endPosition.line}, column ${endPosition.column})`; // We don't need stack
this.stack = false;
}
}
exports.default = HtmlSourceError;
+6
View File
@@ -0,0 +1,6 @@
"use strict";
const loader = require('./index');
module.exports = loader.default;
module.exports.raw = loader.raw;
+68
View File
@@ -0,0 +1,68 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = loader;
var _loaderUtils = require("loader-utils");
var _schemaUtils = require("schema-utils");
var _plugins = require("./plugins");
var _utils = require("./utils");
var _options = _interopRequireDefault(require("./options.json"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
async function loader(content) {
const rawOptions = (0, _loaderUtils.getOptions)(this);
(0, _schemaUtils.validate)(_options.default, rawOptions, {
name: 'HTML Loader',
baseDataPath: 'options'
});
const options = (0, _utils.normalizeOptions)(rawOptions, this);
if (options.preprocessor) {
// eslint-disable-next-line no-param-reassign
content = await options.preprocessor(content, this);
}
const plugins = [];
const errors = [];
const imports = [];
const replacements = [];
if (options.attributes) {
plugins.push((0, _plugins.sourcePlugin)({
urlHandler: url => (0, _loaderUtils.stringifyRequest)(this, url),
attributes: options.attributes,
resourcePath: this.resourcePath,
imports,
errors,
replacements
}));
}
if (options.minimize) {
plugins.push((0, _plugins.minimizerPlugin)({
minimize: options.minimize,
errors
}));
}
const {
html
} = (0, _utils.pluginRunner)(plugins).process(content);
for (const error of errors) {
this.emitError(error instanceof Error ? error : new Error(error));
}
const importCode = (0, _utils.getImportCode)(html, this, imports, options);
const moduleCode = (0, _utils.getModuleCode)(html, replacements, options);
const exportCode = (0, _utils.getExportCode)(html, options);
return `${importCode}${moduleCode}${exportCode}`;
}
+73
View File
@@ -0,0 +1,73 @@
{
"type": "object",
"definitions": {
"Attribute": {
"anyOf": [
{
"type": "object",
"properties": {
"tag": {
"type": "string",
"minLength": 1
},
"attribute": {
"type": "string",
"minLength": 1
},
"type": {
"enum": ["src", "srcset"]
},
"filter": {
"instanceof": "Function"
}
},
"required": ["attribute", "type"],
"additionalProperties": false
},
{
"enum": ["..."]
}
]
},
"AttributeList": {
"type": "array",
"items": {
"$ref": "#/definitions/Attribute"
},
"minItems": 1,
"uniqueItems": true
}
},
"properties": {
"preprocessor": {
"instanceof": "Function"
},
"attributes": {
"anyOf": [
{ "type": "boolean" },
{
"type": "object",
"properties": {
"list": {
"$ref": "#/definitions/AttributeList"
},
"urlFilter": {
"instanceof": "Function"
},
"root": {
"type": "string"
}
},
"additionalProperties": false
}
]
},
"minimize": {
"anyOf": [{ "type": "boolean" }, { "type": "object" }]
},
"esModule": {
"type": "boolean"
}
},
"additionalProperties": false
}
+23
View File
@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "sourcePlugin", {
enumerable: true,
get: function () {
return _sourcePlugin.default;
}
});
Object.defineProperty(exports, "minimizerPlugin", {
enumerable: true,
get: function () {
return _minimizerPlugin.default;
}
});
var _sourcePlugin = _interopRequireDefault(require("./source-plugin"));
var _minimizerPlugin = _interopRequireDefault(require("./minimizer-plugin"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+21
View File
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _htmlMinifierTerser = require("html-minifier-terser");
var _default = options => function process(html) {
try {
// eslint-disable-next-line no-param-reassign
html = (0, _htmlMinifierTerser.minify)(html, options.minimize);
} catch (error) {
options.errors.push(error);
}
return html;
};
exports.default = _default;
+271
View File
@@ -0,0 +1,271 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _htmlparser = require("htmlparser2");
var _loaderUtils = require("loader-utils");
var _HtmlSourceError = _interopRequireDefault(require("../HtmlSourceError"));
var _utils = require("../utils");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _default = options => function process(html) {
const {
list,
urlFilter: maybeUrlFilter,
root
} = options.attributes;
const sources = [];
const urlFilter = (0, _utils.getFilter)(maybeUrlFilter, value => (0, _loaderUtils.isUrlRequest)(value, root));
const getAttribute = (tag, attribute, attributes, resourcePath) => {
return list.find(element => {
const foundTag = typeof element.tag === 'undefined' || typeof element.tag !== 'undefined' && element.tag.toLowerCase() === tag.toLowerCase();
const foundAttribute = element.attribute.toLowerCase() === attribute.toLowerCase();
const isNotFiltered = element.filter ? element.filter(tag, attribute, attributes, resourcePath) : true;
return foundTag && foundAttribute && isNotFiltered;
});
};
const {
resourcePath
} = options;
const parser = new _htmlparser.Parser({
attributesMeta: {},
onattribute(name, value) {
// eslint-disable-next-line no-underscore-dangle
const endIndex = parser._tokenizer._index;
const startIndex = endIndex - value.length;
const unquoted = html[endIndex] !== '"' && html[endIndex] !== "'";
this.attributesMeta[name] = {
startIndex,
unquoted
};
},
onopentag(tag, attributes) {
Object.keys(attributes).forEach(attribute => {
const value = attributes[attribute];
const {
startIndex: valueStartIndex,
unquoted
} = this.attributesMeta[attribute];
const foundAttribute = getAttribute(tag, attribute, attributes, resourcePath);
if (!foundAttribute) {
return;
}
const {
type
} = foundAttribute; // eslint-disable-next-line default-case
switch (type) {
case 'src':
{
let source;
try {
source = (0, _utils.parseSrc)(value);
} catch (error) {
options.errors.push(new _HtmlSourceError.default(`Bad value for attribute "${attribute}" on element "${tag}": ${error.message}`, parser.startIndex, parser.endIndex, html));
return;
}
if (!(0, _utils.isUrlRequestable)(source.value, root)) {
return;
}
const startIndex = valueStartIndex + source.startIndex;
const endIndex = startIndex + source.value.length;
sources.push({
name: attribute,
value: source.value,
unquoted,
startIndex,
endIndex
});
break;
}
case 'srcset':
{
let sourceSet;
try {
sourceSet = (0, _utils.parseSrcset)(value);
} catch (error) {
options.errors.push(new _HtmlSourceError.default(`Bad value for attribute "${attribute}" on element "${tag}": ${error.message}`, parser.startIndex, parser.endIndex, html));
return;
}
sourceSet.forEach(sourceItem => {
const {
source
} = sourceItem;
const startIndex = valueStartIndex + source.startIndex;
const endIndex = startIndex + source.value.length;
if (!(0, _utils.isUrlRequestable)(source.value, root)) {
return;
}
sources.push({
name: attribute,
value: source.value,
unquoted,
startIndex,
endIndex
});
});
break;
}
// Need improve
// case 'include': {
// let source;
//
// // eslint-disable-next-line no-underscore-dangle
// if (parser._tokenizer._state === 4) {
// return;
// }
//
// try {
// source = parseSrc(value);
// } catch (error) {
// options.errors.push(
// new HtmlSourceError(
// `Bad value for attribute "${attribute}" on element "${tag}": ${error.message}`,
// parser.startIndex,
// parser.endIndex,
// html
// )
// );
//
// return;
// }
//
// if (!urlFilter(attribute, source.value, resourcePath)) {
// return;
// }
//
// const { startIndex } = parser;
// const closingTag = html
// .slice(startIndex - 1)
// .match(
// new RegExp(`<s*${tag}[^>]*>(?:.*?)</${tag}[^<>]*>`, 's')
// );
//
// if (!closingTag) {
// return;
// }
//
// const endIndex = startIndex + closingTag[0].length;
// const importItem = getImportItem(source.value);
// const replacementItem = getReplacementItem(importItem);
//
// sources.push({ replacementItem, startIndex, endIndex });
//
// break;
// }
}
});
this.attributesMeta = {};
},
onerror(error) {
options.errors.push(error);
}
}, {
decodeEntities: false,
lowerCaseTags: false,
lowerCaseAttributeNames: false,
recognizeCDATA: true,
recognizeSelfClosing: true
});
parser.write(html);
parser.end();
const imports = new Map();
const replacements = new Map();
let offset = 0;
for (const source of sources) {
const {
name,
value,
unquoted,
startIndex,
endIndex
} = source;
let normalizedUrl = value;
let prefix = '';
const queryParts = normalizedUrl.split('!');
if (queryParts.length > 1) {
normalizedUrl = queryParts.pop();
prefix = queryParts.join('!');
}
normalizedUrl = (0, _utils.normalizeUrl)(normalizedUrl);
if (!urlFilter(name, value, resourcePath)) {
// eslint-disable-next-line no-continue
continue;
}
let hash;
const indexHash = normalizedUrl.lastIndexOf('#');
if (indexHash >= 0) {
hash = normalizedUrl.substr(indexHash, indexHash);
normalizedUrl = normalizedUrl.substr(0, indexHash);
}
const request = (0, _utils.requestify)(normalizedUrl, root);
const newUrl = prefix ? `${prefix}!${request}` : request;
const importKey = newUrl;
let importName = imports.get(importKey);
if (!importName) {
importName = `___HTML_LOADER_IMPORT_${imports.size}___`;
imports.set(importKey, importName);
options.imports.push({
importName,
source: options.urlHandler(newUrl)
});
}
const replacementKey = JSON.stringify({
newUrl,
unquoted,
hash
});
let replacementName = replacements.get(replacementKey);
if (!replacementName) {
replacementName = `___HTML_LOADER_REPLACEMENT_${replacements.size}___`;
replacements.set(replacementKey, replacementName);
options.replacements.push({
replacementName,
importName,
hash,
unquoted
});
} // eslint-disable-next-line no-param-reassign
html = html.slice(0, startIndex + offset) + replacementName + html.slice(endIndex + offset);
offset += startIndex + replacementName.length - endIndex;
}
return html;
};
exports.default = _default;
+26
View File
@@ -0,0 +1,26 @@
"use strict";
module.exports = function (url, options) {
if (!options) {
// eslint-disable-next-line no-param-reassign
options = {};
} // eslint-disable-next-line no-underscore-dangle, no-param-reassign
url = url && url.__esModule ? url.default : url;
if (typeof url !== 'string') {
return url;
}
if (options.hash) {
// eslint-disable-next-line no-param-reassign
url += options.hash;
}
if (options.maybeNeedQuotes && /[\t\n\f\r "'=<>`]/.test(url)) {
return "\"".concat(url, "\"");
}
return url;
};
+655
View File
@@ -0,0 +1,655 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.parseSrcset = parseSrcset;
exports.parseSrc = parseSrc;
exports.normalizeUrl = normalizeUrl;
exports.requestify = requestify;
exports.isUrlRequestable = isUrlRequestable;
exports.normalizeOptions = normalizeOptions;
exports.pluginRunner = pluginRunner;
exports.getFilter = getFilter;
exports.getImportCode = getImportCode;
exports.getModuleCode = getModuleCode;
exports.getExportCode = getExportCode;
var _loaderUtils = require("loader-utils");
function isASCIIWhitespace(character) {
return (// Horizontal tab
character === '\u0009' || // New line
character === '\u000A' || // Form feed
character === '\u000C' || // Carriage return
character === '\u000D' || // Space
character === '\u0020'
);
} // (Don't use \s, to avoid matching non-breaking space)
// eslint-disable-next-line no-control-regex
const regexLeadingSpaces = /^[ \t\n\r\u000c]+/; // eslint-disable-next-line no-control-regex
const regexLeadingCommasOrSpaces = /^[, \t\n\r\u000c]+/; // eslint-disable-next-line no-control-regex
const regexLeadingNotSpaces = /^[^ \t\n\r\u000c]+/;
const regexTrailingCommas = /[,]+$/;
const regexNonNegativeInteger = /^\d+$/; // ( Positive or negative or unsigned integers or decimals, without or without exponents.
// Must include at least one digit.
// According to spec tests any decimal point must be followed by a digit.
// No leading plus sign is allowed.)
// https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number
const regexFloatingPoint = /^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/;
function parseSrcset(input) {
// 1. Let input be the value passed to this algorithm.
const inputLength = input.length;
let url;
let descriptors;
let currentDescriptor;
let state;
let c; // 2. Let position be a pointer into input, initially pointing at the start
// of the string.
let position = 0;
let startUrlPosition; // eslint-disable-next-line consistent-return
function collectCharacters(regEx) {
let chars;
const match = regEx.exec(input.substring(position));
if (match) {
[chars] = match;
position += chars.length;
return chars;
}
} // 3. Let candidates be an initially empty source set.
const candidates = []; // 4. Splitting loop: Collect a sequence of characters that are space
// characters or U+002C COMMA characters. If any U+002C COMMA characters
// were collected, that is a parse error.
// eslint-disable-next-line no-constant-condition
while (true) {
collectCharacters(regexLeadingCommasOrSpaces); // 5. If position is past the end of input, return candidates and abort these steps.
if (position >= inputLength) {
if (candidates.length === 0) {
throw new Error('Must contain one or more image candidate strings');
} // (we're done, this is the sole return path)
return candidates;
} // 6. Collect a sequence of characters that are not space characters,
// and let that be url.
startUrlPosition = position;
url = collectCharacters(regexLeadingNotSpaces); // 7. Let descriptors be a new empty list.
descriptors = []; // 8. If url ends with a U+002C COMMA character (,), follow these substeps:
// (1). Remove all trailing U+002C COMMA characters from url. If this removed
// more than one character, that is a parse error.
if (url.slice(-1) === ',') {
url = url.replace(regexTrailingCommas, ''); // (Jump ahead to step 9 to skip tokenization and just push the candidate).
parseDescriptors();
} // Otherwise, follow these substeps:
else {
tokenize();
} // 16. Return to the step labeled splitting loop.
}
/**
* Tokenizes descriptor properties prior to parsing
* Returns undefined.
*/
function tokenize() {
// 8.1. Descriptor tokenizer: Skip whitespace
collectCharacters(regexLeadingSpaces); // 8.2. Let current descriptor be the empty string.
currentDescriptor = ''; // 8.3. Let state be in descriptor.
state = 'in descriptor'; // eslint-disable-next-line no-constant-condition
while (true) {
// 8.4. Let c be the character at position.
c = input.charAt(position); // Do the following depending on the value of state.
// For the purpose of this step, "EOF" is a special character representing
// that position is past the end of input.
// In descriptor
if (state === 'in descriptor') {
// Do the following, depending on the value of c:
// Space character
// If current descriptor is not empty, append current descriptor to
// descriptors and let current descriptor be the empty string.
// Set state to after descriptor.
if (isASCIIWhitespace(c)) {
if (currentDescriptor) {
descriptors.push(currentDescriptor);
currentDescriptor = '';
state = 'after descriptor';
}
} // U+002C COMMA (,)
// Advance position to the next character in input. If current descriptor
// is not empty, append current descriptor to descriptors. Jump to the step
// labeled descriptor parser.
else if (c === ',') {
position += 1;
if (currentDescriptor) {
descriptors.push(currentDescriptor);
}
parseDescriptors();
return;
} // U+0028 LEFT PARENTHESIS (()
// Append c to current descriptor. Set state to in parens.
else if (c === '\u0028') {
currentDescriptor += c;
state = 'in parens';
} // EOF
// If current descriptor is not empty, append current descriptor to
// descriptors. Jump to the step labeled descriptor parser.
else if (c === '') {
if (currentDescriptor) {
descriptors.push(currentDescriptor);
}
parseDescriptors();
return; // Anything else
// Append c to current descriptor.
} else {
currentDescriptor += c;
}
} // In parens
else if (state === 'in parens') {
// U+0029 RIGHT PARENTHESIS ())
// Append c to current descriptor. Set state to in descriptor.
if (c === ')') {
currentDescriptor += c;
state = 'in descriptor';
} // EOF
// Append current descriptor to descriptors. Jump to the step labeled
// descriptor parser.
else if (c === '') {
descriptors.push(currentDescriptor);
parseDescriptors();
return;
} // Anything else
// Append c to current descriptor.
else {
currentDescriptor += c;
}
} // After descriptor
else if (state === 'after descriptor') {
// Do the following, depending on the value of c:
if (isASCIIWhitespace(c)) {// Space character: Stay in this state.
} // EOF: Jump to the step labeled descriptor parser.
else if (c === '') {
parseDescriptors();
return;
} // Anything else
// Set state to in descriptor. Set position to the previous character in input.
else {
state = 'in descriptor';
position -= 1;
}
} // Advance position to the next character in input.
position += 1;
}
}
/**
* Adds descriptor properties to a candidate, pushes to the candidates array
* @return undefined
*/
// Declared outside of the while loop so that it's only created once.
function parseDescriptors() {
// 9. Descriptor parser: Let error be no.
let pError = false; // 10. Let width be absent.
// 11. Let density be absent.
// 12. Let future-compat-h be absent. (We're implementing it now as h)
let w;
let d;
let h;
let i;
const candidate = {};
let desc;
let lastChar;
let value;
let intVal;
let floatVal; // 13. For each descriptor in descriptors, run the appropriate set of steps
// from the following list:
for (i = 0; i < descriptors.length; i++) {
desc = descriptors[i];
lastChar = desc[desc.length - 1];
value = desc.substring(0, desc.length - 1);
intVal = parseInt(value, 10);
floatVal = parseFloat(value); // If the descriptor consists of a valid non-negative integer followed by
// a U+0077 LATIN SMALL LETTER W character
if (regexNonNegativeInteger.test(value) && lastChar === 'w') {
// If width and density are not both absent, then let error be yes.
if (w || d) {
pError = true;
} // Apply the rules for parsing non-negative integers to the descriptor.
// If the result is zero, let error be yes.
// Otherwise, let width be the result.
if (intVal === 0) {
pError = true;
} else {
w = intVal;
}
} // If the descriptor consists of a valid floating-point number followed by
// a U+0078 LATIN SMALL LETTER X character
else if (regexFloatingPoint.test(value) && lastChar === 'x') {
// If width, density and future-compat-h are not all absent, then let error
// be yes.
if (w || d || h) {
pError = true;
} // Apply the rules for parsing floating-point number values to the descriptor.
// If the result is less than zero, let error be yes. Otherwise, let density
// be the result.
if (floatVal < 0) {
pError = true;
} else {
d = floatVal;
}
} // If the descriptor consists of a valid non-negative integer followed by
// a U+0068 LATIN SMALL LETTER H character
else if (regexNonNegativeInteger.test(value) && lastChar === 'h') {
// If height and density are not both absent, then let error be yes.
if (h || d) {
pError = true;
} // Apply the rules for parsing non-negative integers to the descriptor.
// If the result is zero, let error be yes. Otherwise, let future-compat-h
// be the result.
if (intVal === 0) {
pError = true;
} else {
h = intVal;
} // Anything else, Let error be yes.
} else {
pError = true;
}
} // 15. If error is still no, then append a new image source to candidates whose
// URL is url, associated with a width width if not absent and a pixel
// density density if not absent. Otherwise, there is a parse error.
if (!pError) {
candidate.source = {
value: url,
startIndex: startUrlPosition
};
if (w) {
candidate.width = {
value: w
};
}
if (d) {
candidate.density = {
value: d
};
}
if (h) {
candidate.height = {
value: h
};
}
candidates.push(candidate);
} else {
throw new Error(`Invalid srcset descriptor found in '${input}' at '${desc}'`);
}
}
}
function parseSrc(input) {
if (!input) {
throw new Error('Must be non-empty');
}
let startIndex = 0;
let value = input;
while (isASCIIWhitespace(value.substring(0, 1))) {
startIndex += 1;
value = value.substring(1, value.length);
}
while (isASCIIWhitespace(value.substring(value.length - 1, value.length))) {
value = value.substring(0, value.length - 1);
}
if (!value) {
throw new Error('Must be non-empty');
}
return {
value,
startIndex
};
}
function normalizeUrl(url) {
return decodeURIComponent(url).replace(/[\t\n\r]/g, '');
}
function requestify(url, root) {
return (0, _loaderUtils.urlToRequest)(url, root);
}
function isUrlRequestable(url, root) {
return (0, _loaderUtils.isUrlRequest)(url, root);
}
function isProductionMode(loaderContext) {
return loaderContext.mode === 'production' || !loaderContext.mode;
}
const defaultMinimizerOptions = {
caseSensitive: true,
// `collapseBooleanAttributes` is not always safe, since this can break CSS attribute selectors and not safe for XHTML
collapseWhitespace: true,
conservativeCollapse: true,
keepClosingSlash: true,
// We need ability to use cssnano, or setup own function without extra dependencies
minifyCSS: true,
minifyJS: true,
// `minifyURLs` is unsafe, because we can't guarantee what the base URL is
// `removeAttributeQuotes` is not safe in some rare cases, also HTML spec recommends against doing this
removeComments: true,
// `removeEmptyAttributes` is not safe, can affect certain style or script behavior, look at https://github.com/webpack-contrib/html-loader/issues/323
// `removeRedundantAttributes` is not safe, can affect certain style or script behavior, look at https://github.com/webpack-contrib/html-loader/issues/323
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true // `useShortDoctype` is not safe for XHTML
};
function getMinimizeOption(rawOptions, loaderContext) {
if (typeof rawOptions.minimize === 'undefined') {
return isProductionMode(loaderContext) ? defaultMinimizerOptions : false;
}
if (typeof rawOptions.minimize === 'boolean') {
return rawOptions.minimize === true ? defaultMinimizerOptions : false;
}
return rawOptions.minimize;
}
function getAttributeValue(attributes, name) {
const lowercasedAttributes = Object.keys(attributes).reduce((keys, k) => {
// eslint-disable-next-line no-param-reassign
keys[k.toLowerCase()] = k;
return keys;
}, {});
return attributes[lowercasedAttributes[name.toLowerCase()]];
}
function scriptFilter(tag, attribute, attributes) {
if (attributes.type) {
const type = getAttributeValue(attributes, 'type').trim().toLowerCase();
if (type !== 'module' && type !== 'text/javascript' && type !== 'application/javascript') {
return false;
}
}
return true;
}
const defaultAttributes = [{
tag: 'audio',
attribute: 'src',
type: 'src'
}, {
tag: 'embed',
attribute: 'src',
type: 'src'
}, {
tag: 'img',
attribute: 'src',
type: 'src'
}, {
tag: 'img',
attribute: 'srcset',
type: 'srcset'
}, {
tag: 'input',
attribute: 'src',
type: 'src'
}, {
tag: 'link',
attribute: 'href',
type: 'src',
filter: (tag, attribute, attributes) => {
if (!/stylesheet/i.test(getAttributeValue(attributes, 'rel'))) {
return false;
}
if (attributes.type && getAttributeValue(attributes, 'type').trim().toLowerCase() !== 'text/css') {
return false;
}
return true;
}
}, {
tag: 'object',
attribute: 'data',
type: 'src'
}, {
tag: 'script',
attribute: 'src',
type: 'src',
filter: scriptFilter
}, // Using href with <script> is described here: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/script
{
tag: 'script',
attribute: 'href',
type: 'src',
filter: scriptFilter
}, {
tag: 'script',
attribute: 'xlink:href',
type: 'src',
filter: scriptFilter
}, {
tag: 'source',
attribute: 'src',
type: 'src'
}, {
tag: 'source',
attribute: 'srcset',
type: 'srcset'
}, {
tag: 'track',
attribute: 'src',
type: 'src'
}, {
tag: 'video',
attribute: 'poster',
type: 'src'
}, {
tag: 'video',
attribute: 'src',
type: 'src'
}, // SVG
{
tag: 'image',
attribute: 'xlink:href',
type: 'src'
}, {
tag: 'image',
attribute: 'href',
type: 'src'
}, {
tag: 'use',
attribute: 'xlink:href',
type: 'src'
}, {
tag: 'use',
attribute: 'href',
type: 'src'
}];
function smartMergeSources(array, factory) {
if (typeof array === 'undefined') {
return factory();
}
const newArray = [];
for (let i = 0; i < array.length; i++) {
const item = array[i];
if (item === '...') {
const items = factory();
if (typeof items !== 'undefined') {
// eslint-disable-next-line no-shadow
for (const item of items) {
newArray.push(item);
}
}
} else if (typeof newArray !== 'undefined') {
newArray.push(item);
}
}
return newArray;
}
function getAttributesOption(rawOptions) {
if (typeof rawOptions.attributes === 'undefined') {
return {
list: defaultAttributes
};
}
if (typeof rawOptions.attributes === 'boolean') {
return rawOptions.attributes === true ? {
list: defaultAttributes
} : false;
}
const sources = smartMergeSources(rawOptions.attributes.list, () => defaultAttributes);
return {
list: sources,
urlFilter: rawOptions.attributes.urlFilter,
root: rawOptions.attributes.root
};
}
function normalizeOptions(rawOptions, loaderContext) {
return {
preprocessor: rawOptions.preprocessor,
attributes: getAttributesOption(rawOptions),
minimize: getMinimizeOption(rawOptions, loaderContext),
esModule: typeof rawOptions.esModule === 'undefined' ? false : rawOptions.esModule
};
}
function pluginRunner(plugins) {
return {
process: content => {
const result = {};
for (const plugin of plugins) {
// eslint-disable-next-line no-param-reassign
content = plugin(content, result);
}
result.html = content;
return result;
}
};
}
function getFilter(filter, defaultFilter = null) {
return (attribute, value, resourcePath) => {
if (defaultFilter && !defaultFilter(value)) {
return false;
}
if (typeof filter === 'function') {
return filter(attribute, value, resourcePath);
}
return true;
};
}
const GET_SOURCE_FROM_IMPORT_NAME = '___HTML_LOADER_GET_SOURCE_FROM_IMPORT___';
function getImportCode(html, loaderContext, imports, options) {
if (imports.length === 0) {
return '';
}
const stringifiedHelperRequest = (0, _loaderUtils.stringifyRequest)(loaderContext, require.resolve('./runtime/getUrl.js'));
let code = options.esModule ? `import ${GET_SOURCE_FROM_IMPORT_NAME} from ${stringifiedHelperRequest};\n` : `var ${GET_SOURCE_FROM_IMPORT_NAME} = require(${stringifiedHelperRequest});\n`;
for (const item of imports) {
const {
importName,
source
} = item;
code += options.esModule ? `import ${importName} from ${source};\n` : `var ${importName} = require(${source});\n`;
}
return `// Imports\n${code}`;
}
function getModuleCode(html, replacements) {
let code = JSON.stringify(html) // Invalid in JavaScript but valid HTML
.replace(/[\u2028\u2029]/g, str => str === '\u2029' ? '\\u2029' : '\\u2028');
let replacersCode = '';
for (const item of replacements) {
const {
importName,
replacementName,
unquoted,
hash
} = item;
const getUrlOptions = [].concat(hash ? [`hash: ${JSON.stringify(hash)}`] : []).concat(unquoted ? 'maybeNeedQuotes: true' : []);
const preparedOptions = getUrlOptions.length > 0 ? `, { ${getUrlOptions.join(', ')} }` : '';
replacersCode += `var ${replacementName} = ${GET_SOURCE_FROM_IMPORT_NAME}(${importName}${preparedOptions});\n`;
code = code.replace(new RegExp(replacementName, 'g'), () => `" + ${replacementName} + "`);
}
return `// Module\n${replacersCode}var code = ${code};\n`;
}
function getExportCode(html, options) {
if (options.esModule) {
return `// Exports\nexport default code;`;
}
return `// Exports\nmodule.exports = code;`;
}
+20
View File
@@ -0,0 +1,20 @@
Copyright JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+290
View File
@@ -0,0 +1,290 @@
<div align="center">
<a href="http://json-schema.org">
<img width="160" height="160"
src="https://raw.githubusercontent.com/webpack-contrib/schema-utils/master/.github/assets/logo.png">
</a>
<a href="https://github.com/webpack/webpack">
<img width="200" height="200"
src="https://webpack.js.org/assets/icon-square-big.svg">
</a>
</div>
[![npm][npm]][npm-url]
[![node][node]][node-url]
[![deps][deps]][deps-url]
[![tests][tests]][tests-url]
[![coverage][cover]][cover-url]
[![chat][chat]][chat-url]
[![size][size]][size-url]
# schema-utils
Package for validate options in loaders and plugins.
## Getting Started
To begin, you'll need to install `schema-utils`:
```console
npm install schema-utils
```
## API
**schema.json**
```json
{
"type": "object",
"properties": {
"option": {
"type": "boolean"
}
},
"additionalProperties": false
}
```
```js
import schema from "./path/to/schema.json";
import { validate } from "schema-utils";
const options = { option: true };
const configuration = { name: "Loader Name/Plugin Name/Name" };
validate(schema, options, configuration);
```
### `schema`
Type: `String`
JSON schema.
Simple example of schema:
```json
{
"type": "object",
"properties": {
"name": {
"description": "This is description of option.",
"type": "string"
}
},
"additionalProperties": false
}
```
### `options`
Type: `Object`
Object with options.
```js
import schema from "./path/to/schema.json";
import { validate } from "schema-utils";
const options = { foo: "bar" };
validate(schema, { name: 123 }, { name: "MyPlugin" });
```
### `configuration`
Allow to configure validator.
There is an alternative method to configure the `name` and`baseDataPath` options via the `title` property in the schema.
For example:
```json
{
"title": "My Loader options",
"type": "object",
"properties": {
"name": {
"description": "This is description of option.",
"type": "string"
}
},
"additionalProperties": false
}
```
The last word used for the `baseDataPath` option, other words used for the `name` option.
Based on the example above the `name` option equals `My Loader`, the `baseDataPath` option equals `options`.
#### `name`
Type: `Object`
Default: `"Object"`
Allow to setup name in validation errors.
```js
import schema from "./path/to/schema.json";
import { validate } from "schema-utils";
const options = { foo: "bar" };
validate(schema, options, { name: "MyPlugin" });
```
```shell
Invalid configuration object. MyPlugin has been initialised using a configuration object that does not match the API schema.
- configuration.optionName should be a integer.
```
#### `baseDataPath`
Type: `String`
Default: `"configuration"`
Allow to setup base data path in validation errors.
```js
import schema from "./path/to/schema.json";
import { validate } from "schema-utils";
const options = { foo: "bar" };
validate(schema, options, { name: "MyPlugin", baseDataPath: "options" });
```
```shell
Invalid options object. MyPlugin has been initialised using an options object that does not match the API schema.
- options.optionName should be a integer.
```
#### `postFormatter`
Type: `Function`
Default: `undefined`
Allow to reformat errors.
```js
import schema from "./path/to/schema.json";
import { validate } from "schema-utils";
const options = { foo: "bar" };
validate(schema, options, {
name: "MyPlugin",
postFormatter: (formattedError, error) => {
if (error.keyword === "type") {
return `${formattedError}\nAdditional Information.`;
}
return formattedError;
},
});
```
```shell
Invalid options object. MyPlugin has been initialized using an options object that does not match the API schema.
- options.optionName should be a integer.
Additional Information.
```
## Examples
**schema.json**
```json
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"test": {
"anyOf": [
{ "type": "array" },
{ "type": "string" },
{ "instanceof": "RegExp" }
]
},
"transform": {
"instanceof": "Function"
},
"sourceMap": {
"type": "boolean"
}
},
"additionalProperties": false
}
```
### `Loader`
```js
import { getOptions } from "loader-utils";
import { validate } from "schema-utils";
import schema from "path/to/schema.json";
function loader(src, map) {
const options = getOptions(this);
validate(schema, options, {
name: "Loader Name",
baseDataPath: "options",
});
// Code...
}
export default loader;
```
### `Plugin`
```js
import { validate } from "schema-utils";
import schema from "path/to/schema.json";
class Plugin {
constructor(options) {
validate(schema, options, {
name: "Plugin Name",
baseDataPath: "options",
});
this.options = options;
}
apply(compiler) {
// Code...
}
}
export default Plugin;
```
## Contributing
Please take a moment to read our contributing guidelines if you haven't yet done so.
[CONTRIBUTING](./.github/CONTRIBUTING.md)
## License
[MIT](./LICENSE)
[npm]: https://img.shields.io/npm/v/schema-utils.svg
[npm-url]: https://npmjs.com/package/schema-utils
[node]: https://img.shields.io/node/v/schema-utils.svg
[node-url]: https://nodejs.org
[deps]: https://david-dm.org/webpack/schema-utils.svg
[deps-url]: https://david-dm.org/webpack/schema-utils
[tests]: https://github.com/webpack/schema-utils/workflows/schema-utils/badge.svg
[tests-url]: https://github.com/webpack/schema-utils/actions
[cover]: https://codecov.io/gh/webpack/schema-utils/branch/master/graph/badge.svg
[cover-url]: https://codecov.io/gh/webpack/schema-utils
[chat]: https://badges.gitter.im/webpack/webpack.svg
[chat-url]: https://gitter.im/webpack/webpack
[size]: https://packagephobia.com/badge?p=schema-utils
[size-url]: https://packagephobia.com/result?p=schema-utils
@@ -0,0 +1,74 @@
export default ValidationError;
export type JSONSchema6 = import("json-schema").JSONSchema6;
export type JSONSchema7 = import("json-schema").JSONSchema7;
export type Schema = import("./validate").Schema;
export type ValidationErrorConfiguration =
import("./validate").ValidationErrorConfiguration;
export type PostFormatter = import("./validate").PostFormatter;
export type SchemaUtilErrorObject = import("./validate").SchemaUtilErrorObject;
declare class ValidationError extends Error {
/**
* @param {Array<SchemaUtilErrorObject>} errors
* @param {Schema} schema
* @param {ValidationErrorConfiguration} configuration
*/
constructor(
errors: Array<SchemaUtilErrorObject>,
schema: Schema,
configuration?: ValidationErrorConfiguration
);
/** @type {Array<SchemaUtilErrorObject>} */
errors: Array<SchemaUtilErrorObject>;
/** @type {Schema} */
schema: Schema;
/** @type {string} */
headerName: string;
/** @type {string} */
baseDataPath: string;
/** @type {PostFormatter | null} */
postFormatter: PostFormatter | null;
/**
* @param {string} path
* @returns {Schema}
*/
getSchemaPart(path: string): Schema;
/**
* @param {Schema} schema
* @param {boolean} logic
* @param {Array<Object>} prevSchemas
* @returns {string}
*/
formatSchema(
schema: Schema,
logic?: boolean,
prevSchemas?: Array<Object>
): string;
/**
* @param {Schema=} schemaPart
* @param {(boolean | Array<string>)=} additionalPath
* @param {boolean=} needDot
* @param {boolean=} logic
* @returns {string}
*/
getSchemaPartText(
schemaPart?: Schema | undefined,
additionalPath?: (boolean | Array<string>) | undefined,
needDot?: boolean | undefined,
logic?: boolean | undefined
): string;
/**
* @param {Schema=} schemaPart
* @returns {string}
*/
getSchemaPartDescription(schemaPart?: Schema | undefined): string;
/**
* @param {SchemaUtilErrorObject} error
* @returns {string}
*/
formatValidationError(error: SchemaUtilErrorObject): string;
/**
* @param {Array<SchemaUtilErrorObject>} errors
* @returns {string}
*/
formatValidationErrors(errors: Array<SchemaUtilErrorObject>): string;
}
@@ -0,0 +1,3 @@
import { validate } from "./validate";
import { ValidationError } from "./validate";
export { validate, ValidationError };
@@ -0,0 +1,10 @@
export default addAbsolutePathKeyword;
export type Ajv = import("ajv").Ajv;
export type ValidateFunction = import("ajv").ValidateFunction;
export type SchemaUtilErrorObject = import("../validate").SchemaUtilErrorObject;
/**
*
* @param {Ajv} ajv
* @returns {Ajv}
*/
declare function addAbsolutePathKeyword(ajv: Ajv): Ajv;
@@ -0,0 +1,79 @@
export = Range;
/**
* @typedef {[number, boolean]} RangeValue
*/
/**
* @callback RangeValueCallback
* @param {RangeValue} rangeValue
* @returns {boolean}
*/
declare class Range {
/**
* @param {"left" | "right"} side
* @param {boolean} exclusive
* @returns {">" | ">=" | "<" | "<="}
*/
static getOperator(
side: "left" | "right",
exclusive: boolean
): ">" | ">=" | "<" | "<=";
/**
* @param {number} value
* @param {boolean} logic is not logic applied
* @param {boolean} exclusive is range exclusive
* @returns {string}
*/
static formatRight(value: number, logic: boolean, exclusive: boolean): string;
/**
* @param {number} value
* @param {boolean} logic is not logic applied
* @param {boolean} exclusive is range exclusive
* @returns {string}
*/
static formatLeft(value: number, logic: boolean, exclusive: boolean): string;
/**
* @param {number} start left side value
* @param {number} end right side value
* @param {boolean} startExclusive is range exclusive from left side
* @param {boolean} endExclusive is range exclusive from right side
* @param {boolean} logic is not logic applied
* @returns {string}
*/
static formatRange(
start: number,
end: number,
startExclusive: boolean,
endExclusive: boolean,
logic: boolean
): string;
/**
* @param {Array<RangeValue>} values
* @param {boolean} logic is not logic applied
* @return {RangeValue} computed value and it's exclusive flag
*/
static getRangeValue(values: Array<RangeValue>, logic: boolean): RangeValue;
/** @type {Array<RangeValue>} */
_left: Array<RangeValue>;
/** @type {Array<RangeValue>} */
_right: Array<RangeValue>;
/**
* @param {number} value
* @param {boolean=} exclusive
*/
left(value: number, exclusive?: boolean | undefined): void;
/**
* @param {number} value
* @param {boolean=} exclusive
*/
right(value: number, exclusive?: boolean | undefined): void;
/**
* @param {boolean} logic is not logic applied
* @return {string} "smart" range string representation
*/
format(logic?: boolean): string;
}
declare namespace Range {
export { RangeValue, RangeValueCallback };
}
type RangeValue = [number, boolean];
type RangeValueCallback = (rangeValue: RangeValue) => boolean;
@@ -0,0 +1,3 @@
export function stringHints(schema: Schema, logic: boolean): string[];
export function numberHints(schema: Schema, logic: boolean): string[];
export type Schema = import("../validate").Schema;
@@ -0,0 +1,37 @@
export type JSONSchema4 = import("json-schema").JSONSchema4;
export type JSONSchema6 = import("json-schema").JSONSchema6;
export type JSONSchema7 = import("json-schema").JSONSchema7;
export type ErrorObject = import("ajv").ErrorObject;
export type Extend = {
formatMinimum?: number | undefined;
formatMaximum?: number | undefined;
formatExclusiveMinimum?: boolean | undefined;
formatExclusiveMaximum?: boolean | undefined;
link?: string | undefined;
};
export type Schema = (JSONSchema4 | JSONSchema6 | JSONSchema7) & Extend;
export type SchemaUtilErrorObject = ErrorObject & {
children?: Array<ErrorObject>;
};
export type PostFormatter = (
formattedError: string,
error: SchemaUtilErrorObject
) => string;
export type ValidationErrorConfiguration = {
name?: string | undefined;
baseDataPath?: string | undefined;
postFormatter?: PostFormatter | undefined;
};
/**
* @param {Schema} schema
* @param {Array<object> | object} options
* @param {ValidationErrorConfiguration=} configuration
* @returns {void}
*/
export function validate(
schema: Schema,
options: Array<object> | object,
configuration?: ValidationErrorConfiguration | undefined
): void;
import ValidationError from "./ValidationError";
export { ValidationError };
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
"use strict";
const {
validate,
ValidationError
} = require("./validate");
module.exports = {
validate,
ValidationError
};
@@ -0,0 +1,93 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
/** @typedef {import("ajv").Ajv} Ajv */
/** @typedef {import("ajv").ValidateFunction} ValidateFunction */
/** @typedef {import("../validate").SchemaUtilErrorObject} SchemaUtilErrorObject */
/**
* @param {string} message
* @param {object} schema
* @param {string} data
* @returns {SchemaUtilErrorObject}
*/
function errorMessage(message, schema, data) {
return {
// @ts-ignore
// eslint-disable-next-line no-undefined
dataPath: undefined,
// @ts-ignore
// eslint-disable-next-line no-undefined
schemaPath: undefined,
keyword: "absolutePath",
params: {
absolutePath: data
},
message,
parentSchema: schema
};
}
/**
* @param {boolean} shouldBeAbsolute
* @param {object} schema
* @param {string} data
* @returns {SchemaUtilErrorObject}
*/
function getErrorFor(shouldBeAbsolute, schema, data) {
const message = shouldBeAbsolute ? `The provided value ${JSON.stringify(data)} is not an absolute path!` : `A relative path is expected. However, the provided value ${JSON.stringify(data)} is an absolute path!`;
return errorMessage(message, schema, data);
}
/**
*
* @param {Ajv} ajv
* @returns {Ajv}
*/
function addAbsolutePathKeyword(ajv) {
ajv.addKeyword("absolutePath", {
errors: true,
type: "string",
compile(schema, parentSchema) {
/** @type {ValidateFunction} */
const callback = data => {
let passes = true;
const isExclamationMarkPresent = data.includes("!");
if (isExclamationMarkPresent) {
callback.errors = [errorMessage(`The provided value ${JSON.stringify(data)} contains exclamation mark (!) which is not allowed because it's reserved for loader syntax.`, parentSchema, data)];
passes = false;
} // ?:[A-Za-z]:\\ - Windows absolute path
// \\\\ - Windows network absolute path
// \/ - Unix-like OS absolute path
const isCorrectAbsolutePath = schema === /^(?:[A-Za-z]:(\\|\/)|\\\\|\/)/.test(data);
if (!isCorrectAbsolutePath) {
callback.errors = [getErrorFor(schema, parentSchema, data)];
passes = false;
}
return passes;
};
callback.errors = [];
return callback;
}
});
return ajv;
}
var _default = addAbsolutePathKeyword;
exports.default = _default;
@@ -0,0 +1,163 @@
"use strict";
/**
* @typedef {[number, boolean]} RangeValue
*/
/**
* @callback RangeValueCallback
* @param {RangeValue} rangeValue
* @returns {boolean}
*/
class Range {
/**
* @param {"left" | "right"} side
* @param {boolean} exclusive
* @returns {">" | ">=" | "<" | "<="}
*/
static getOperator(side, exclusive) {
if (side === "left") {
return exclusive ? ">" : ">=";
}
return exclusive ? "<" : "<=";
}
/**
* @param {number} value
* @param {boolean} logic is not logic applied
* @param {boolean} exclusive is range exclusive
* @returns {string}
*/
static formatRight(value, logic, exclusive) {
if (logic === false) {
return Range.formatLeft(value, !logic, !exclusive);
}
return `should be ${Range.getOperator("right", exclusive)} ${value}`;
}
/**
* @param {number} value
* @param {boolean} logic is not logic applied
* @param {boolean} exclusive is range exclusive
* @returns {string}
*/
static formatLeft(value, logic, exclusive) {
if (logic === false) {
return Range.formatRight(value, !logic, !exclusive);
}
return `should be ${Range.getOperator("left", exclusive)} ${value}`;
}
/**
* @param {number} start left side value
* @param {number} end right side value
* @param {boolean} startExclusive is range exclusive from left side
* @param {boolean} endExclusive is range exclusive from right side
* @param {boolean} logic is not logic applied
* @returns {string}
*/
static formatRange(start, end, startExclusive, endExclusive, logic) {
let result = "should be";
result += ` ${Range.getOperator(logic ? "left" : "right", logic ? startExclusive : !startExclusive)} ${start} `;
result += logic ? "and" : "or";
result += ` ${Range.getOperator(logic ? "right" : "left", logic ? endExclusive : !endExclusive)} ${end}`;
return result;
}
/**
* @param {Array<RangeValue>} values
* @param {boolean} logic is not logic applied
* @return {RangeValue} computed value and it's exclusive flag
*/
static getRangeValue(values, logic) {
let minMax = logic ? Infinity : -Infinity;
let j = -1;
const predicate = logic ?
/** @type {RangeValueCallback} */
([value]) => value <= minMax :
/** @type {RangeValueCallback} */
([value]) => value >= minMax;
for (let i = 0; i < values.length; i++) {
if (predicate(values[i])) {
[minMax] = values[i];
j = i;
}
}
if (j > -1) {
return values[j];
}
return [Infinity, true];
}
constructor() {
/** @type {Array<RangeValue>} */
this._left = [];
/** @type {Array<RangeValue>} */
this._right = [];
}
/**
* @param {number} value
* @param {boolean=} exclusive
*/
left(value, exclusive = false) {
this._left.push([value, exclusive]);
}
/**
* @param {number} value
* @param {boolean=} exclusive
*/
right(value, exclusive = false) {
this._right.push([value, exclusive]);
}
/**
* @param {boolean} logic is not logic applied
* @return {string} "smart" range string representation
*/
format(logic = true) {
const [start, leftExclusive] = Range.getRangeValue(this._left, logic);
const [end, rightExclusive] = Range.getRangeValue(this._right, !logic);
if (!Number.isFinite(start) && !Number.isFinite(end)) {
return "";
}
const realStart = leftExclusive ? start + 1 : start;
const realEnd = rightExclusive ? end - 1 : end; // e.g. 5 < x < 7, 5 < x <= 6, 6 <= x <= 6
if (realStart === realEnd) {
return `should be ${logic ? "" : "!"}= ${realStart}`;
} // e.g. 4 < x < ∞
if (Number.isFinite(start) && !Number.isFinite(end)) {
return Range.formatLeft(start, logic, leftExclusive);
} // e.g. ∞ < x < 4
if (!Number.isFinite(start) && Number.isFinite(end)) {
return Range.formatRight(end, logic, rightExclusive);
}
return Range.formatRange(start, end, leftExclusive, rightExclusive, logic);
}
}
module.exports = Range;
@@ -0,0 +1,105 @@
"use strict";
const Range = require("./Range");
/** @typedef {import("../validate").Schema} Schema */
/**
* @param {Schema} schema
* @param {boolean} logic
* @return {string[]}
*/
module.exports.stringHints = function stringHints(schema, logic) {
const hints = [];
let type = "string";
const currentSchema = { ...schema
};
if (!logic) {
const tmpLength = currentSchema.minLength;
const tmpFormat = currentSchema.formatMinimum;
const tmpExclusive = currentSchema.formatExclusiveMaximum;
currentSchema.minLength = currentSchema.maxLength;
currentSchema.maxLength = tmpLength;
currentSchema.formatMinimum = currentSchema.formatMaximum;
currentSchema.formatMaximum = tmpFormat;
currentSchema.formatExclusiveMaximum = !currentSchema.formatExclusiveMinimum;
currentSchema.formatExclusiveMinimum = !tmpExclusive;
}
if (typeof currentSchema.minLength === "number") {
if (currentSchema.minLength === 1) {
type = "non-empty string";
} else {
const length = Math.max(currentSchema.minLength - 1, 0);
hints.push(`should be longer than ${length} character${length > 1 ? "s" : ""}`);
}
}
if (typeof currentSchema.maxLength === "number") {
if (currentSchema.maxLength === 0) {
type = "empty string";
} else {
const length = currentSchema.maxLength + 1;
hints.push(`should be shorter than ${length} character${length > 1 ? "s" : ""}`);
}
}
if (currentSchema.pattern) {
hints.push(`should${logic ? "" : " not"} match pattern ${JSON.stringify(currentSchema.pattern)}`);
}
if (currentSchema.format) {
hints.push(`should${logic ? "" : " not"} match format ${JSON.stringify(currentSchema.format)}`);
}
if (currentSchema.formatMinimum) {
hints.push(`should be ${currentSchema.formatExclusiveMinimum ? ">" : ">="} ${JSON.stringify(currentSchema.formatMinimum)}`);
}
if (currentSchema.formatMaximum) {
hints.push(`should be ${currentSchema.formatExclusiveMaximum ? "<" : "<="} ${JSON.stringify(currentSchema.formatMaximum)}`);
}
return [type].concat(hints);
};
/**
* @param {Schema} schema
* @param {boolean} logic
* @return {string[]}
*/
module.exports.numberHints = function numberHints(schema, logic) {
const hints = [schema.type === "integer" ? "integer" : "number"];
const range = new Range();
if (typeof schema.minimum === "number") {
range.left(schema.minimum);
}
if (typeof schema.exclusiveMinimum === "number") {
range.left(schema.exclusiveMinimum, true);
}
if (typeof schema.maximum === "number") {
range.right(schema.maximum);
}
if (typeof schema.exclusiveMaximum === "number") {
range.right(schema.exclusiveMaximum, true);
}
const rangeFormat = range.format(logic);
if (rangeFormat) {
hints.push(rangeFormat);
}
if (typeof schema.multipleOf === "number") {
hints.push(`should${logic ? "" : " not"} be multiple of ${schema.multipleOf}`);
}
return hints;
};
@@ -0,0 +1,163 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.validate = validate;
Object.defineProperty(exports, "ValidationError", {
enumerable: true,
get: function () {
return _ValidationError.default;
}
});
var _absolutePath = _interopRequireDefault(require("./keywords/absolutePath"));
var _ValidationError = _interopRequireDefault(require("./ValidationError"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// Use CommonJS require for ajv libs so TypeScript consumers aren't locked into esModuleInterop (see #110).
const Ajv = require("ajv");
const ajvKeywords = require("ajv-keywords");
/** @typedef {import("json-schema").JSONSchema4} JSONSchema4 */
/** @typedef {import("json-schema").JSONSchema6} JSONSchema6 */
/** @typedef {import("json-schema").JSONSchema7} JSONSchema7 */
/** @typedef {import("ajv").ErrorObject} ErrorObject */
/**
* @typedef {Object} Extend
* @property {number=} formatMinimum
* @property {number=} formatMaximum
* @property {boolean=} formatExclusiveMinimum
* @property {boolean=} formatExclusiveMaximum
* @property {string=} link
*/
/** @typedef {(JSONSchema4 | JSONSchema6 | JSONSchema7) & Extend} Schema */
/** @typedef {ErrorObject & { children?: Array<ErrorObject>}} SchemaUtilErrorObject */
/**
* @callback PostFormatter
* @param {string} formattedError
* @param {SchemaUtilErrorObject} error
* @returns {string}
*/
/**
* @typedef {Object} ValidationErrorConfiguration
* @property {string=} name
* @property {string=} baseDataPath
* @property {PostFormatter=} postFormatter
*/
const ajv = new Ajv({
allErrors: true,
verbose: true,
$data: true
});
ajvKeywords(ajv, ["instanceof", "formatMinimum", "formatMaximum", "patternRequired"]); // Custom keywords
(0, _absolutePath.default)(ajv);
/**
* @param {Schema} schema
* @param {Array<object> | object} options
* @param {ValidationErrorConfiguration=} configuration
* @returns {void}
*/
function validate(schema, options, configuration) {
let errors = [];
if (Array.isArray(options)) {
errors = Array.from(options, nestedOptions => validateObject(schema, nestedOptions));
errors.forEach((list, idx) => {
const applyPrefix =
/**
* @param {SchemaUtilErrorObject} error
*/
error => {
// eslint-disable-next-line no-param-reassign
error.dataPath = `[${idx}]${error.dataPath}`;
if (error.children) {
error.children.forEach(applyPrefix);
}
};
list.forEach(applyPrefix);
});
errors = errors.reduce((arr, items) => {
arr.push(...items);
return arr;
}, []);
} else {
errors = validateObject(schema, options);
}
if (errors.length > 0) {
throw new _ValidationError.default(errors, schema, configuration);
}
}
/**
* @param {Schema} schema
* @param {Array<object> | object} options
* @returns {Array<SchemaUtilErrorObject>}
*/
function validateObject(schema, options) {
const compiledSchema = ajv.compile(schema);
const valid = compiledSchema(options);
if (valid) return [];
return compiledSchema.errors ? filterErrors(compiledSchema.errors) : [];
}
/**
* @param {Array<ErrorObject>} errors
* @returns {Array<SchemaUtilErrorObject>}
*/
function filterErrors(errors) {
/** @type {Array<SchemaUtilErrorObject>} */
let newErrors = [];
for (const error of
/** @type {Array<SchemaUtilErrorObject>} */
errors) {
const {
dataPath
} = error;
/** @type {Array<SchemaUtilErrorObject>} */
let children = [];
newErrors = newErrors.filter(oldError => {
if (oldError.dataPath.includes(dataPath)) {
if (oldError.children) {
children = children.concat(oldError.children.slice(0));
} // eslint-disable-next-line no-undefined, no-param-reassign
oldError.children = undefined;
children.push(oldError);
return false;
}
return true;
});
if (children.length) {
error.children = children;
}
newErrors.push(error);
}
return newErrors;
}
@@ -0,0 +1,78 @@
{
"name": "schema-utils",
"version": "3.1.1",
"description": "webpack Validation Utils",
"license": "MIT",
"repository": "webpack/schema-utils",
"author": "webpack Contrib (https://github.com/webpack-contrib)",
"homepage": "https://github.com/webpack/schema-utils",
"bugs": "https://github.com/webpack/schema-utils/issues",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"main": "dist/index.js",
"types": "declarations/index.d.ts",
"engines": {
"node": ">= 10.13.0"
},
"scripts": {
"start": "npm run build -- -w",
"clean": "del-cli dist declarations",
"prebuild": "npm run clean",
"build:types": "tsc --declaration --emitDeclarationOnly --outDir declarations && prettier \"declarations/**/*.ts\" --write",
"build:code": "cross-env NODE_ENV=production babel src -d dist --copy-files",
"build": "npm-run-all -p \"build:**\"",
"commitlint": "commitlint --from=master",
"security": "npm audit --production",
"fmt:check": "prettier \"{**/*,*}.{js,json,md,yml,css,ts}\" --list-different",
"lint:js": "eslint --cache .",
"lint:types": "tsc --pretty --noEmit",
"lint": "npm-run-all lint:js lint:types fmt:check",
"fmt": "npm run fmt:check -- --write",
"fix:js": "npm run lint:js -- --fix",
"fix": "npm-run-all fix:js fmt",
"test:only": "cross-env NODE_ENV=test jest",
"test:watch": "npm run test:only -- --watch",
"test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage",
"pretest": "npm run lint",
"test": "npm run test:coverage",
"prepare": "npm run build && husky install",
"release": "standard-version"
},
"files": [
"dist",
"declarations"
],
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"devDependencies": {
"@babel/cli": "^7.14.3",
"@babel/core": "^7.14.6",
"@babel/preset-env": "^7.14.7",
"@commitlint/cli": "^12.1.4",
"@commitlint/config-conventional": "^12.1.4",
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"babel-jest": "^27.0.6",
"cross-env": "^7.0.3",
"del": "^6.0.0",
"del-cli": "^3.0.1",
"eslint": "^7.31.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.4",
"husky": "^6.0.0",
"jest": "^27.0.6",
"lint-staged": "^11.0.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.3.2",
"standard-version": "^9.3.1",
"typescript": "^4.3.5",
"webpack": "^5.45.1"
},
"keywords": [
"webpack"
]
}
+86
View File
@@ -0,0 +1,86 @@
{
"name": "html-loader",
"version": "1.3.2",
"description": "Html loader module for webpack",
"license": "MIT",
"repository": "webpack-contrib/html-loader",
"author": "Tobias Koppers @sokra",
"homepage": "https://github.com/webpack-contrib/html-loader",
"bugs": "https://github.com/webpack-contrib/html-loader/issues",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"main": "dist/cjs.js",
"engines": {
"node": ">= 10.13.0"
},
"scripts": {
"start": "npm run build -- -w",
"clean": "del-cli dist",
"validate:runtime": "es-check es5 \"dist/runtime/**/*.js\"",
"prebuild": "npm run clean",
"build": "cross-env NODE_ENV=production babel src -d dist --copy-files",
"postbuild": "npm run validate:runtime",
"commitlint": "commitlint --from=master",
"security": "npm audit",
"lint:prettier": "prettier --list-different .",
"lint:js": "eslint --cache .",
"lint": "npm-run-all -l -p \"lint:**\"",
"test:only": "cross-env NODE_ENV=test jest",
"test:watch": "npm run test:only -- --watch",
"test:coverage": "npm run test:only -- --coverage",
"pretest": "npm run lint",
"test": "npm run test:coverage",
"prepare": "npm run build",
"release": "standard-version",
"defaults": "webpack-defaults"
},
"files": [
"dist"
],
"peerDependencies": {
"webpack": "^4.0.0 || ^5.0.0"
},
"dependencies": {
"html-minifier-terser": "^5.1.1",
"htmlparser2": "^4.1.0",
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
},
"devDependencies": {
"@babel/cli": "^7.11.6",
"@babel/core": "^7.11.6",
"@babel/preset-env": "^7.11.5",
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
"@webpack-contrib/defaults": "^6.3.0",
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"babel-jest": "^26.5.2",
"cross-env": "^7.0.2",
"del": "^6.0.0",
"del-cli": "^3.0.1",
"es-check": "^5.1.1",
"eslint": "^7.10.0",
"eslint-config-prettier": "^6.12.0",
"eslint-plugin-import": "^2.22.1",
"file-loader": "^6.1.1",
"handlebars": "^4.7.6",
"husky": "^4.3.0",
"jest": "^26.5.2",
"lint-staged": "^10.4.0",
"memfs": "^3.2.0",
"npm-run-all": "^4.1.5",
"posthtml": "^0.13.3",
"posthtml-webp": "^2.1.0",
"prettier": "^2.1.2",
"standard-version": "^9.0.0",
"url-loader": "^4.1.1",
"webpack": "^4.44.2"
},
"keywords": [
"webpack",
"html",
"loader"
]
}