ui
This commit is contained in:
+20
@@ -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.
|
||||
+742
@@ -0,0 +1,742 @@
|
||||
<div align="center">
|
||||
<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]
|
||||
[![cover][cover]][cover-url]
|
||||
[![chat][chat]][chat-url]
|
||||
[![size][size]][size-url]
|
||||
|
||||
# terser-webpack-plugin
|
||||
|
||||
This plugin uses [terser](https://github.com/terser/terser) to minify/minimize your JavaScript.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Webpack v5 comes with the latest `terser-webpack-plugin` out of the box. If you are using Webpack v5 or above and wish to customize the options, you will still need to install `terser-webpack-plugin`. Using Webpack v4, you have to install `terser-webpack-plugin` v4.
|
||||
|
||||
To begin, you'll need to install `terser-webpack-plugin`:
|
||||
|
||||
```console
|
||||
$ npm install terser-webpack-plugin --save-dev
|
||||
```
|
||||
|
||||
Then add the plugin to your `webpack` config. For example:
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [new TerserPlugin()],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
And run `webpack` via your preferred method.
|
||||
|
||||
## Note about source maps
|
||||
|
||||
**Works only with `source-map`, `inline-source-map`, `hidden-source-map` and `nosources-source-map` values for the [`devtool`](https://webpack.js.org/configuration/devtool/) option.**
|
||||
|
||||
Why?
|
||||
|
||||
- `eval` wraps modules in `eval("string")` and the minimizer does not handle strings.
|
||||
- `cheap` has not column information and minimizer generate only a single line, which leave only a single mapping.
|
||||
|
||||
Using supported `devtool` values enable source map generation.
|
||||
|
||||
## Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| :---------------------------------------: | :-----------------------------------------------------------------------------: | :----------------------------------------------------------: | :------------------------------------------------------------------------ |
|
||||
| **[`test`](#test)** | `String\|RegExp\|Array<String\|RegExp>` | `/\.m?js(\?.*)?$/i` | Test to match files against. |
|
||||
| **[`include`](#include)** | `String\|RegExp\|Array<String\|RegExp>` | `undefined` | Files to include. |
|
||||
| **[`exclude`](#exclude)** | `String\|RegExp\|Array<String\|RegExp>` | `undefined` | Files to exclude. |
|
||||
| **[`parallel`](#parallel)** | `Boolean\|Number` | `true` | Use multi-process parallel running to improve the build speed. |
|
||||
| **[`minify`](#minify)** | `Function` | `TerserPlugin.terserMinify` | Allows you to override default minify function. |
|
||||
| **[`terserOptions`](#terseroptions)** | `Object` | [`default`](https://github.com/terser/terser#minify-options) | Terser [minify options](https://github.com/terser/terser#minify-options). |
|
||||
| **[`extractComments`](#extractcomments)** | `Boolean\|String\|RegExp\|Function<(node, comment) -> Boolean\|Object>\|Object` | `true` | Whether comments shall be extracted to a separate file. |
|
||||
|
||||
### `test`
|
||||
|
||||
Type: `String|RegExp|Array<String|RegExp>`
|
||||
Default: `/\.m?js(\?.*)?$/i`
|
||||
|
||||
Test to match files against.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
test: /\.js(\?.*)?$/i,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### `include`
|
||||
|
||||
Type: `String|RegExp|Array<String|RegExp>`
|
||||
Default: `undefined`
|
||||
|
||||
Files to include.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
include: /\/includes/,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### `exclude`
|
||||
|
||||
Type: `String|RegExp|Array<String|RegExp>`
|
||||
Default: `undefined`
|
||||
|
||||
Files to exclude.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
exclude: /\/excludes/,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### `parallel`
|
||||
|
||||
Type: `Boolean|Number`
|
||||
Default: `true`
|
||||
|
||||
Use multi-process parallel running to improve the build speed.
|
||||
Default number of concurrent runs: `os.cpus().length - 1`.
|
||||
|
||||
> ℹ️ Parallelization can speedup your build significantly and is therefore **highly recommended**.
|
||||
|
||||
> ⚠️ If you use **Circle CI** or any other environment that doesn't provide real available count of CPUs then you need to setup explicitly number of CPUs to avoid `Error: Call retries were exceeded` (see [#143](https://github.com/webpack-contrib/terser-webpack-plugin/issues/143), [#202](https://github.com/webpack-contrib/terser-webpack-plugin/issues/202)).
|
||||
|
||||
#### `Boolean`
|
||||
|
||||
Enable/disable multi-process parallel running.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
parallel: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### `Number`
|
||||
|
||||
Enable multi-process parallel running and set number of concurrent runs.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
parallel: 4,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### `minify`
|
||||
|
||||
Type: `Function`
|
||||
Default: `TerserPlugin.terserMinify`
|
||||
|
||||
Allows you to override default minify function.
|
||||
By default plugin uses [terser](https://github.com/terser/terser) package.
|
||||
Useful for using and testing unpublished versions or forks.
|
||||
|
||||
> ⚠️ **Always use `require` inside `minify` function when `parallel` option enabled**.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
// Can be async
|
||||
const minify = (input, sourceMap, minimizerOptions, extractsComments) => {
|
||||
// The `minimizerOptions` option contains option from the `terserOptions` option
|
||||
// You can use `minimizerOptions.myCustomOption`
|
||||
|
||||
// Custom logic for extract comments
|
||||
const { map, code } = require("uglify-module") // Or require('./path/to/uglify-module')
|
||||
.minify(input, {
|
||||
/* Your options for minification */
|
||||
});
|
||||
|
||||
return { map, code, warnings: [], errors: [], extractedComments: [] };
|
||||
};
|
||||
|
||||
// Used to regenerate `fullhash`/`chunkhash` between different implementation
|
||||
// Example: you fix a bug in custom minimizer/custom function, but unfortunately webpack doesn't know about it, so you will get the same fullhash/chunkhash
|
||||
// to avoid this you can provide version of your custom minimizer
|
||||
// You don't need if you use only `contenthash`
|
||||
minify.getMinimizerVersion = () => {
|
||||
let packageJson;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
||||
packageJson = require("uglify-module/package.json");
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
return packageJson && packageJson.version;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
myCustomOption: true,
|
||||
},
|
||||
minify,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### `terserOptions`
|
||||
|
||||
Type: `Object`
|
||||
Default: [default](https://github.com/terser/terser#minify-options)
|
||||
|
||||
Terser [options](https://github.com/terser/terser#minify-options).
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
ecma: undefined,
|
||||
parse: {},
|
||||
compress: {},
|
||||
mangle: true, // Note `mangle.properties` is `false` by default.
|
||||
module: false,
|
||||
// Deprecated
|
||||
output: null,
|
||||
format: null,
|
||||
toplevel: false,
|
||||
nameCache: null,
|
||||
ie8: false,
|
||||
keep_classnames: undefined,
|
||||
keep_fnames: false,
|
||||
safari10: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### `extractComments`
|
||||
|
||||
Type: `Boolean|String|RegExp|Function<(node, comment) -> Boolean|Object>|Object`
|
||||
Default: `true`
|
||||
|
||||
Whether comments shall be extracted to a separate file, (see [details](https://github.com/webpack/webpack/commit/71933e979e51c533b432658d5e37917f9e71595a)).
|
||||
By default extract only comments using `/^\**!|@preserve|@license|@cc_on/i` regexp condition and remove remaining comments.
|
||||
If the original file is named `foo.js`, then the comments will be stored to `foo.js.LICENSE.txt`.
|
||||
The `terserOptions.format.comments` option specifies whether the comment will be preserved, i.e. it is possible to preserve some comments (e.g. annotations) while extracting others or even preserving comments that have been extracted.
|
||||
|
||||
#### `Boolean`
|
||||
|
||||
Enable/disable extracting comments.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### `String`
|
||||
|
||||
Extract `all` or `some` (use `/^\**!|@preserve|@license|@cc_on/i` RegExp) comments.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: "all",
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### `RegExp`
|
||||
|
||||
All comments that match the given expression will be extracted to the separate file.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: /@extract/i,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### `Function<(node, comment) -> Boolean>`
|
||||
|
||||
All comments that match the given expression will be extracted to the separate file.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: (astNode, comment) => {
|
||||
if (/@extract/i.test(comment.value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### `Object`
|
||||
|
||||
Allow to customize condition for extract comments, specify extracted file name and banner.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: {
|
||||
condition: /^\**!|@preserve|@license|@cc_on/i,
|
||||
filename: (fileData) => {
|
||||
// The "fileData" argument contains object with "filename", "basename", "query" and "hash"
|
||||
return `${fileData.filename}.LICENSE.txt${fileData.query}`;
|
||||
},
|
||||
banner: (licenseFile) => {
|
||||
return `License information can be found in ${licenseFile}`;
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
##### `condition`
|
||||
|
||||
Type: `Boolean|String|RegExp|Function<(node, comment) -> Boolean|Object>`
|
||||
|
||||
Condition what comments you need extract.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: {
|
||||
condition: "some",
|
||||
filename: (fileData) => {
|
||||
// The "fileData" argument contains object with "filename", "basename", "query" and "hash"
|
||||
return `${fileData.filename}.LICENSE.txt${fileData.query}`;
|
||||
},
|
||||
banner: (licenseFile) => {
|
||||
return `License information can be found in ${licenseFile}`;
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
##### `filename`
|
||||
|
||||
Type: `String|Function<(string) -> String>`
|
||||
Default: `[file].LICENSE.txt[query]`
|
||||
|
||||
Available placeholders: `[file]`, `[query]` and `[filebase]` (`[base]` for webpack 5).
|
||||
|
||||
The file where the extracted comments will be stored.
|
||||
Default is to append the suffix `.LICENSE.txt` to the original filename.
|
||||
|
||||
> ⚠️ We highly recommend using the `txt` extension. Using `js`/`cjs`/`mjs` extensions may conflict with existing assets which leads to broken code.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: {
|
||||
condition: /^\**!|@preserve|@license|@cc_on/i,
|
||||
filename: "extracted-comments.js",
|
||||
banner: (licenseFile) => {
|
||||
return `License information can be found in ${licenseFile}`;
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
##### `banner`
|
||||
|
||||
Type: `Boolean|String|Function<(string) -> String>`
|
||||
Default: `/*! For license information please see ${commentsFile} */`
|
||||
|
||||
The banner text that points to the extracted file and will be added on top of the original file.
|
||||
Can be `false` (no banner), a `String`, or a `Function<(string) -> String>` that will be called with the filename where extracted comments have been stored.
|
||||
Will be wrapped into comment.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: {
|
||||
condition: true,
|
||||
filename: (fileData) => {
|
||||
// The "fileData" argument contains object with "filename", "basename", "query" and "hash"
|
||||
return `${fileData.filename}.LICENSE.txt${fileData.query}`;
|
||||
},
|
||||
banner: (commentsFile) => {
|
||||
return `My custom banner about license information ${commentsFile}`;
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Preserve Comments
|
||||
|
||||
Extract all legal comments (i.e. `/^\**!|@preserve|@license|@cc_on/i`) and preserve `/@license/i` comments.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
format: {
|
||||
comments: /@license/i,
|
||||
},
|
||||
},
|
||||
extractComments: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Remove Comments
|
||||
|
||||
If you avoid building with comments, use this config:
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
format: {
|
||||
comments: false,
|
||||
},
|
||||
},
|
||||
extractComments: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### [`uglify-js`](https://github.com/mishoo/UglifyJS)
|
||||
|
||||
[`UglifyJS`](https://github.com/mishoo/UglifyJS) is a JavaScript parser, minifier, compressor and beautifier toolkit.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
minify: TerserPlugin.uglifyJsMinify,
|
||||
// `terserOptions` options will be passed to `uglify-js`
|
||||
// Link to options - https://github.com/mishoo/UglifyJS#minify-options
|
||||
terserOptions: {},
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### [`swc`](https://github.com/swc-project/swc)
|
||||
|
||||
[`swc`](https://github.com/swc-project/swc) is a super-fast compiler written in rust; producing widely-supported javascript from modern standards and typescript.
|
||||
|
||||
> ⚠ the `extractComments` option is not supported and all comments will be removed by default, it will be fixed in future
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
minify: TerserPlugin.swcMinify,
|
||||
// `terserOptions` options will be passed to `swc` (`@swc/core`)
|
||||
// Link to options - https://swc.rs/docs/config-js-minify
|
||||
terserOptions: {},
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### [`esbuild`](https://github.com/evanw/esbuild)
|
||||
|
||||
[`esbuild`](https://github.com/evanw/esbuild) is an extremely fast JavaScript bundler and minifier.
|
||||
|
||||
> ⚠ the `extractComments` option is not supported and all legal comments (i.e. copyright, licenses and etc) will be preserved
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
minify: TerserPlugin.esbuildMinify,
|
||||
// `terserOptions` options will be passed to `esbuild`
|
||||
// Link to options - https://esbuild.github.io/api/#minify
|
||||
// Note: the `minify` options is true by default (and override other `minify*` options), so if you want to disable the `minifyIdentifiers` option (or other `minify*` options) please use:
|
||||
// terserOptions: {
|
||||
// minify: false,
|
||||
// minifyWhitespace: true,
|
||||
// minifyIdentifiers: false,
|
||||
// minifySyntax: true,
|
||||
// },
|
||||
terserOptions: {},
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Custom Minify Function
|
||||
|
||||
Override default minify function - use `uglify-js` for minification.
|
||||
|
||||
**webpack.config.js**
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
minify: (file, sourceMap) => {
|
||||
// https://github.com/mishoo/UglifyJS2#minify-options
|
||||
const uglifyJsOptions = {
|
||||
/* your `uglify-js` package options */
|
||||
};
|
||||
|
||||
if (sourceMap) {
|
||||
uglifyJsOptions.sourceMap = {
|
||||
content: sourceMap,
|
||||
};
|
||||
}
|
||||
|
||||
return require("uglify-js").minify(file, uglifyJsOptions);
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Typescript
|
||||
|
||||
With default terser minify function:
|
||||
|
||||
```ts
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
compress: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
With built-in minify functions:
|
||||
|
||||
```ts
|
||||
import type { JsMinifyOptions as SwcOptions } from "@swc/core";
|
||||
import type { MinifyOptions as UglifyJSOptions } from "uglify-js";
|
||||
import type { TransformOptions as EsbuildOptions } from "esbuild";
|
||||
import type { MinifyOptions as TerserOptions } from "terser";
|
||||
|
||||
module.exports = {
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin<SwcOptions>({
|
||||
minify: TerserPlugin.swcMinify,
|
||||
terserOptions: {
|
||||
// `swc` options
|
||||
},
|
||||
}),
|
||||
new TerserPlugin<UglifyJSOptions>({
|
||||
minify: TerserPlugin.uglifyJsMinify,
|
||||
terserOptions: {
|
||||
// `uglif-js` options
|
||||
},
|
||||
}),
|
||||
new TerserPlugin<EsbuildOptions>({
|
||||
minify: TerserPlugin.esbuildMinify,
|
||||
terserOptions: {
|
||||
// `esbuild` options
|
||||
},
|
||||
}),
|
||||
|
||||
// Alternative usage:
|
||||
new TerserPlugin<TerserOptions>({
|
||||
minify: TerserPlugin.terserMinify,
|
||||
terserOptions: {
|
||||
// `terser` options
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## 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/terser-webpack-plugin.svg
|
||||
[npm-url]: https://npmjs.com/package/terser-webpack-plugin
|
||||
[node]: https://img.shields.io/node/v/terser-webpack-plugin.svg
|
||||
[node-url]: https://nodejs.org
|
||||
[deps]: https://david-dm.org/webpack-contrib/terser-webpack-plugin.svg
|
||||
[deps-url]: https://david-dm.org/webpack-contrib/terser-webpack-plugin
|
||||
[tests]: https://github.com/webpack-contrib/terser-webpack-plugin/workflows/terser-webpack-plugin/badge.svg
|
||||
[tests-url]: https://github.com/webpack-contrib/terser-webpack-plugin/actions
|
||||
[cover]: https://codecov.io/gh/webpack-contrib/terser-webpack-plugin/branch/master/graph/badge.svg
|
||||
[cover-url]: https://codecov.io/gh/webpack-contrib/terser-webpack-plugin
|
||||
[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=terser-webpack-plugin
|
||||
[size-url]: https://packagephobia.now.sh/result?p=terser-webpack-plugin
|
||||
+798
@@ -0,0 +1,798 @@
|
||||
"use strict";
|
||||
|
||||
const path = require("path");
|
||||
|
||||
const os = require("os");
|
||||
|
||||
const {
|
||||
SourceMapConsumer
|
||||
} = require("source-map");
|
||||
|
||||
const {
|
||||
validate
|
||||
} = require("schema-utils");
|
||||
|
||||
const serialize = require("serialize-javascript");
|
||||
|
||||
const {
|
||||
Worker
|
||||
} = require("jest-worker");
|
||||
|
||||
const {
|
||||
throttleAll,
|
||||
terserMinify,
|
||||
uglifyJsMinify,
|
||||
swcMinify,
|
||||
esbuildMinify
|
||||
} = require("./utils");
|
||||
|
||||
const schema = require("./options.json");
|
||||
|
||||
const {
|
||||
minify
|
||||
} = require("./minify");
|
||||
/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
|
||||
|
||||
/** @typedef {import("webpack").Compiler} Compiler */
|
||||
|
||||
/** @typedef {import("webpack").Compilation} Compilation */
|
||||
|
||||
/** @typedef {import("webpack").WebpackError} WebpackError */
|
||||
|
||||
/** @typedef {import("webpack").Asset} Asset */
|
||||
|
||||
/** @typedef {import("./utils.js").TerserECMA} TerserECMA */
|
||||
|
||||
/** @typedef {import("./utils.js").TerserOptions} TerserOptions */
|
||||
|
||||
/** @typedef {import("jest-worker").Worker} JestWorker */
|
||||
|
||||
/** @typedef {import("source-map").RawSourceMap} RawSourceMap */
|
||||
|
||||
/** @typedef {RegExp | string} Rule */
|
||||
|
||||
/** @typedef {Rule[] | Rule} Rules */
|
||||
|
||||
/**
|
||||
* @callback ExtractCommentsFunction
|
||||
* @param {any} astNode
|
||||
* @param {{ value: string, type: 'comment1' | 'comment2' | 'comment3' | 'comment4', pos: number, line: number, col: number }} comment
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {boolean | 'all' | 'some' | RegExp | ExtractCommentsFunction} ExtractCommentsCondition
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {string | ((fileData: any) => string)} ExtractCommentsFilename
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {boolean | string | ((commentsFile: string) => string)} ExtractCommentsBanner
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ExtractCommentsObject
|
||||
* @property {ExtractCommentsCondition} [condition]
|
||||
* @property {ExtractCommentsFilename} [filename]
|
||||
* @property {ExtractCommentsBanner} [banner]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {ExtractCommentsCondition | ExtractCommentsObject} ExtractCommentsOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MinimizedResult
|
||||
* @property {string} code
|
||||
* @property {RawSourceMap} [map]
|
||||
* @property {Array<Error | string>} [errors]
|
||||
* @property {Array<Error | string>} [warnings]
|
||||
* @property {Array<string>} [extractedComments]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{ [file: string]: string }} Input
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{ [key: string]: any }} CustomOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {T extends infer U ? U : CustomOptions} InferDefaultType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} PredefinedOptions
|
||||
* @property {boolean} [module]
|
||||
* @property {TerserECMA} [ecma]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {PredefinedOptions & InferDefaultType<T>} MinimizerOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @callback BasicMinimizerImplementation
|
||||
* @param {Input} input
|
||||
* @param {RawSourceMap | undefined} sourceMap
|
||||
* @param {MinimizerOptions<T>} minifyOptions
|
||||
* @param {ExtractCommentsOptions | undefined} extractComments
|
||||
* @returns {Promise<MinimizedResult>}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} MinimizeFunctionHelpers
|
||||
* @property {() => string | undefined} [getMinimizerVersion]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {BasicMinimizerImplementation<T> & MinimizeFunctionHelpers} MinimizerImplementation
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Object} InternalOptions
|
||||
* @property {string} name
|
||||
* @property {string} input
|
||||
* @property {RawSourceMap | undefined} inputSourceMap
|
||||
* @property {ExtractCommentsOptions | undefined} extractComments
|
||||
* @property {{ implementation: MinimizerImplementation<T>, options: MinimizerOptions<T> }} minimizer
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {JestWorker & { transform: (options: string) => MinimizedResult, minify: (options: InternalOptions<T>) => MinimizedResult }} MinimizerWorker
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {undefined | boolean | number} Parallel
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} BasePluginOptions
|
||||
* @property {Rules} [test]
|
||||
* @property {Rules} [include]
|
||||
* @property {Rules} [exclude]
|
||||
* @property {ExtractCommentsOptions} [extractComments]
|
||||
* @property {Parallel} [parallel]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {T extends TerserOptions ? { minify?: MinimizerImplementation<T> | undefined, terserOptions?: MinimizerOptions<T> | undefined } : { minify: MinimizerImplementation<T>, terserOptions?: MinimizerOptions<T> | undefined }} DefinedDefaultMinimizerAndOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {BasePluginOptions & { minimizer: { implementation: MinimizerImplementation<T>, options: MinimizerOptions<T> } }} InternalPluginOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template [T=TerserOptions]
|
||||
*/
|
||||
|
||||
|
||||
class TerserPlugin {
|
||||
/**
|
||||
* @param {BasePluginOptions & DefinedDefaultMinimizerAndOptions<T>} [options]
|
||||
*/
|
||||
constructor(options) {
|
||||
validate(
|
||||
/** @type {Schema} */
|
||||
schema, options || {}, {
|
||||
name: "Terser Plugin",
|
||||
baseDataPath: "options"
|
||||
}); // TODO make `minimizer` option instead `minify` and `terserOptions` in the next major release, also rename `terserMinify` to `terserMinimize`
|
||||
|
||||
const {
|
||||
minify =
|
||||
/** @type {MinimizerImplementation<T>} */
|
||||
terserMinify,
|
||||
terserOptions =
|
||||
/** @type {MinimizerOptions<T>} */
|
||||
{},
|
||||
test = /\.[cm]?js(\?.*)?$/i,
|
||||
extractComments = true,
|
||||
parallel = true,
|
||||
include,
|
||||
exclude
|
||||
} = options || {};
|
||||
/**
|
||||
* @private
|
||||
* @type {InternalPluginOptions<T>}
|
||||
*/
|
||||
|
||||
this.options = {
|
||||
test,
|
||||
extractComments,
|
||||
parallel,
|
||||
include,
|
||||
exclude,
|
||||
minimizer: {
|
||||
implementation: minify,
|
||||
options: terserOptions
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {any} input
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
||||
|
||||
static isSourceMap(input) {
|
||||
// All required options for `new SourceMapConsumer(...options)`
|
||||
// https://github.com/mozilla/source-map#new-sourcemapconsumerrawsourcemap
|
||||
return Boolean(input && input.version && input.sources && Array.isArray(input.sources) && typeof input.mappings === "string");
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {unknown} warning
|
||||
* @param {string} file
|
||||
* @returns {Error}
|
||||
*/
|
||||
|
||||
|
||||
static buildWarning(warning, file) {
|
||||
/**
|
||||
* @type {Error & { hideStack: true, file: string }}
|
||||
*/
|
||||
// @ts-ignore
|
||||
const builtWarning = new Error(warning.toString());
|
||||
builtWarning.name = "Warning";
|
||||
builtWarning.hideStack = true;
|
||||
builtWarning.file = file;
|
||||
return builtWarning;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {any} error
|
||||
* @param {string} file
|
||||
* @param {SourceMapConsumer} [sourceMap]
|
||||
* @param {Compilation["requestShortener"]} [requestShortener]
|
||||
* @returns {Error}
|
||||
*/
|
||||
|
||||
|
||||
static buildError(error, file, sourceMap, requestShortener) {
|
||||
/**
|
||||
* @type {Error & { file?: string }}
|
||||
*/
|
||||
let builtError;
|
||||
|
||||
if (typeof error === "string") {
|
||||
builtError = new Error(`${file} from Terser plugin\n${error}`);
|
||||
builtError.file = file;
|
||||
return builtError;
|
||||
}
|
||||
|
||||
if (error.line) {
|
||||
const original = sourceMap && sourceMap.originalPositionFor({
|
||||
line: error.line,
|
||||
column: error.col
|
||||
});
|
||||
|
||||
if (original && original.source && requestShortener) {
|
||||
builtError = new Error(`${file} from Terser plugin\n${error.message} [${requestShortener.shorten(original.source)}:${original.line},${original.column}][${file}:${error.line},${error.col}]${error.stack ? `\n${error.stack.split("\n").slice(1).join("\n")}` : ""}`);
|
||||
builtError.file = file;
|
||||
return builtError;
|
||||
}
|
||||
|
||||
builtError = new Error(`${file} from Terser plugin\n${error.message} [${file}:${error.line},${error.col}]${error.stack ? `\n${error.stack.split("\n").slice(1).join("\n")}` : ""}`);
|
||||
builtError.file = file;
|
||||
return builtError;
|
||||
}
|
||||
|
||||
if (error.stack) {
|
||||
builtError = new Error(`${file} from Terser plugin\n${typeof error.message !== "undefined" ? error.message : ""}\n${error.stack}`);
|
||||
builtError.file = file;
|
||||
return builtError;
|
||||
}
|
||||
|
||||
builtError = new Error(`${file} from Terser plugin\n${error.message}`);
|
||||
builtError.file = file;
|
||||
return builtError;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Parallel} parallel
|
||||
* @returns {number}
|
||||
*/
|
||||
|
||||
|
||||
static getAvailableNumberOfCores(parallel) {
|
||||
// In some cases cpus() returns undefined
|
||||
// https://github.com/nodejs/node/issues/19022
|
||||
const cpus = os.cpus() || {
|
||||
length: 1
|
||||
};
|
||||
return parallel === true ? cpus.length - 1 : Math.min(Number(parallel) || 0, cpus.length - 1);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Compiler} compiler
|
||||
* @param {Compilation} compilation
|
||||
* @param {Record<string, import("webpack").sources.Source>} assets
|
||||
* @param {{availableNumberOfCores: number}} optimizeOptions
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
|
||||
|
||||
async optimize(compiler, compilation, assets, optimizeOptions) {
|
||||
const cache = compilation.getCache("TerserWebpackPlugin");
|
||||
let numberOfAssets = 0;
|
||||
const assetsForMinify = await Promise.all(Object.keys(assets).filter(name => {
|
||||
const {
|
||||
info
|
||||
} =
|
||||
/** @type {Asset} */
|
||||
compilation.getAsset(name);
|
||||
|
||||
if ( // Skip double minimize assets from child compilation
|
||||
info.minimized || // Skip minimizing for extracted comments assets
|
||||
info.extractedComments) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!compiler.webpack.ModuleFilenameHelpers.matchObject.bind( // eslint-disable-next-line no-undefined
|
||||
undefined, this.options)(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).map(async name => {
|
||||
const {
|
||||
info,
|
||||
source
|
||||
} =
|
||||
/** @type {Asset} */
|
||||
compilation.getAsset(name);
|
||||
const eTag = cache.getLazyHashedEtag(source);
|
||||
const cacheItem = cache.getItemCache(name, eTag);
|
||||
const output = await cacheItem.getPromise();
|
||||
|
||||
if (!output) {
|
||||
numberOfAssets += 1;
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
info,
|
||||
inputSource: source,
|
||||
output,
|
||||
cacheItem
|
||||
};
|
||||
}));
|
||||
|
||||
if (assetsForMinify.length === 0) {
|
||||
return;
|
||||
}
|
||||
/** @type {undefined | (() => MinimizerWorker<T>)} */
|
||||
|
||||
|
||||
let getWorker;
|
||||
/** @type {undefined | MinimizerWorker<T>} */
|
||||
|
||||
let initializedWorker;
|
||||
/** @type {undefined | number} */
|
||||
|
||||
let numberOfWorkers;
|
||||
|
||||
if (optimizeOptions.availableNumberOfCores > 0) {
|
||||
// Do not create unnecessary workers when the number of files is less than the available cores, it saves memory
|
||||
numberOfWorkers = Math.min(numberOfAssets, optimizeOptions.availableNumberOfCores); // eslint-disable-next-line consistent-return
|
||||
|
||||
getWorker = () => {
|
||||
if (initializedWorker) {
|
||||
return initializedWorker;
|
||||
}
|
||||
|
||||
initializedWorker =
|
||||
/** @type {MinimizerWorker<T>} */
|
||||
new Worker(require.resolve("./minify"), {
|
||||
numWorkers: numberOfWorkers,
|
||||
enableWorkerThreads: true
|
||||
}); // https://github.com/facebook/jest/issues/8872#issuecomment-524822081
|
||||
|
||||
const workerStdout = initializedWorker.getStdout();
|
||||
|
||||
if (workerStdout) {
|
||||
workerStdout.on("data", chunk => process.stdout.write(chunk));
|
||||
}
|
||||
|
||||
const workerStderr = initializedWorker.getStderr();
|
||||
|
||||
if (workerStderr) {
|
||||
workerStderr.on("data", chunk => process.stderr.write(chunk));
|
||||
}
|
||||
|
||||
return initializedWorker;
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
SourceMapSource,
|
||||
ConcatSource,
|
||||
RawSource
|
||||
} = compiler.webpack.sources;
|
||||
/** @typedef {{ extractedCommentsSource : import("webpack").sources.RawSource, commentsFilename: string }} ExtractedCommentsInfo */
|
||||
|
||||
/** @type {Map<string, ExtractedCommentsInfo>} */
|
||||
|
||||
const allExtractedComments = new Map();
|
||||
const scheduledTasks = [];
|
||||
|
||||
for (const asset of assetsForMinify) {
|
||||
scheduledTasks.push(async () => {
|
||||
const {
|
||||
name,
|
||||
inputSource,
|
||||
info,
|
||||
cacheItem
|
||||
} = asset;
|
||||
let {
|
||||
output
|
||||
} = asset;
|
||||
|
||||
if (!output) {
|
||||
let input;
|
||||
/** @type {RawSourceMap | undefined} */
|
||||
|
||||
let inputSourceMap;
|
||||
const {
|
||||
source: sourceFromInputSource,
|
||||
map
|
||||
} = inputSource.sourceAndMap();
|
||||
input = sourceFromInputSource;
|
||||
|
||||
if (map) {
|
||||
if (!TerserPlugin.isSourceMap(map)) {
|
||||
compilation.warnings.push(
|
||||
/** @type {WebpackError} */
|
||||
new Error(`${name} contains invalid source map`));
|
||||
} else {
|
||||
inputSourceMap =
|
||||
/** @type {RawSourceMap} */
|
||||
map;
|
||||
}
|
||||
}
|
||||
|
||||
if (Buffer.isBuffer(input)) {
|
||||
input = input.toString();
|
||||
}
|
||||
/**
|
||||
* @type {InternalOptions<T>}
|
||||
*/
|
||||
|
||||
|
||||
const options = {
|
||||
name,
|
||||
input,
|
||||
inputSourceMap,
|
||||
minimizer: {
|
||||
implementation: this.options.minimizer.implementation,
|
||||
// @ts-ignore https://github.com/Microsoft/TypeScript/issues/10727
|
||||
options: { ...this.options.minimizer.options
|
||||
}
|
||||
},
|
||||
extractComments: this.options.extractComments
|
||||
};
|
||||
|
||||
if (typeof options.minimizer.options.module === "undefined") {
|
||||
if (typeof info.javascriptModule !== "undefined") {
|
||||
options.minimizer.options.module = info.javascriptModule;
|
||||
} else if (/\.mjs(\?.*)?$/i.test(name)) {
|
||||
options.minimizer.options.module = true;
|
||||
} else if (/\.cjs(\?.*)?$/i.test(name)) {
|
||||
options.minimizer.options.module = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof options.minimizer.options.ecma === "undefined") {
|
||||
options.minimizer.options.ecma = TerserPlugin.getEcmaVersion(compiler.options.output.environment || {});
|
||||
}
|
||||
|
||||
try {
|
||||
output = await (getWorker ? getWorker().transform(serialize(options)) : minify(options));
|
||||
} catch (error) {
|
||||
const hasSourceMap = inputSourceMap && TerserPlugin.isSourceMap(inputSourceMap);
|
||||
compilation.errors.push(
|
||||
/** @type {WebpackError} */
|
||||
TerserPlugin.buildError(error, name, hasSourceMap ? new SourceMapConsumer(
|
||||
/** @type {RawSourceMap} */
|
||||
inputSourceMap) : // eslint-disable-next-line no-undefined
|
||||
undefined, // eslint-disable-next-line no-undefined
|
||||
hasSourceMap ? compilation.requestShortener : undefined));
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof output.code === "undefined") {
|
||||
compilation.errors.push(
|
||||
/** @type {WebpackError} */
|
||||
new Error(`${name} from Terser plugin\nMinimizer doesn't return result`));
|
||||
return;
|
||||
}
|
||||
|
||||
if (output.warnings && output.warnings.length > 0) {
|
||||
output.warnings = output.warnings.map(
|
||||
/**
|
||||
* @param {Error | string} item
|
||||
*/
|
||||
item => TerserPlugin.buildWarning(item, name));
|
||||
}
|
||||
|
||||
if (output.errors && output.errors.length > 0) {
|
||||
const hasSourceMap = inputSourceMap && TerserPlugin.isSourceMap(inputSourceMap);
|
||||
output.errors = output.errors.map(
|
||||
/**
|
||||
* @param {Error | string} item
|
||||
*/
|
||||
item => TerserPlugin.buildError(item, name, hasSourceMap ? new SourceMapConsumer(
|
||||
/** @type {RawSourceMap} */
|
||||
inputSourceMap) : // eslint-disable-next-line no-undefined
|
||||
undefined, // eslint-disable-next-line no-undefined
|
||||
hasSourceMap ? compilation.requestShortener : undefined));
|
||||
}
|
||||
|
||||
let shebang;
|
||||
|
||||
if (
|
||||
/** @type {ExtractCommentsObject} */
|
||||
this.options.extractComments.banner !== false && output.extractedComments && output.extractedComments.length > 0 && output.code.startsWith("#!")) {
|
||||
const firstNewlinePosition = output.code.indexOf("\n");
|
||||
shebang = output.code.substring(0, firstNewlinePosition);
|
||||
output.code = output.code.substring(firstNewlinePosition + 1);
|
||||
}
|
||||
|
||||
if (output.map) {
|
||||
output.source = new SourceMapSource(output.code, name, output.map, input,
|
||||
/** @type {RawSourceMap} */
|
||||
inputSourceMap, true);
|
||||
} else {
|
||||
output.source = new RawSource(output.code);
|
||||
}
|
||||
|
||||
if (output.extractedComments && output.extractedComments.length > 0) {
|
||||
const commentsFilename =
|
||||
/** @type {ExtractCommentsObject} */
|
||||
this.options.extractComments.filename || "[file].LICENSE.txt[query]";
|
||||
let query = "";
|
||||
let filename = name;
|
||||
const querySplit = filename.indexOf("?");
|
||||
|
||||
if (querySplit >= 0) {
|
||||
query = filename.substr(querySplit);
|
||||
filename = filename.substr(0, querySplit);
|
||||
}
|
||||
|
||||
const lastSlashIndex = filename.lastIndexOf("/");
|
||||
const basename = lastSlashIndex === -1 ? filename : filename.substr(lastSlashIndex + 1);
|
||||
const data = {
|
||||
filename,
|
||||
basename,
|
||||
query
|
||||
};
|
||||
output.commentsFilename = compilation.getPath(commentsFilename, data);
|
||||
let banner; // Add a banner to the original file
|
||||
|
||||
if (
|
||||
/** @type {ExtractCommentsObject} */
|
||||
this.options.extractComments.banner !== false) {
|
||||
banner =
|
||||
/** @type {ExtractCommentsObject} */
|
||||
this.options.extractComments.banner || `For license information please see ${path.relative(path.dirname(name), output.commentsFilename).replace(/\\/g, "/")}`;
|
||||
|
||||
if (typeof banner === "function") {
|
||||
banner = banner(output.commentsFilename);
|
||||
}
|
||||
|
||||
if (banner) {
|
||||
output.source = new ConcatSource(shebang ? `${shebang}\n` : "", `/*! ${banner} */\n`, output.source);
|
||||
}
|
||||
}
|
||||
|
||||
const extractedCommentsString = output.extractedComments.sort().join("\n\n");
|
||||
output.extractedCommentsSource = new RawSource(`${extractedCommentsString}\n`);
|
||||
}
|
||||
|
||||
await cacheItem.storePromise({
|
||||
source: output.source,
|
||||
errors: output.errors,
|
||||
warnings: output.warnings,
|
||||
commentsFilename: output.commentsFilename,
|
||||
extractedCommentsSource: output.extractedCommentsSource
|
||||
});
|
||||
}
|
||||
|
||||
if (output.warnings && output.warnings.length > 0) {
|
||||
for (const warning of output.warnings) {
|
||||
compilation.warnings.push(
|
||||
/** @type {WebpackError} */
|
||||
warning);
|
||||
}
|
||||
}
|
||||
|
||||
if (output.errors && output.errors.length > 0) {
|
||||
for (const error of output.errors) {
|
||||
compilation.errors.push(
|
||||
/** @type {WebpackError} */
|
||||
error);
|
||||
}
|
||||
}
|
||||
/** @type {Record<string, any>} */
|
||||
|
||||
|
||||
const newInfo = {
|
||||
minimized: true
|
||||
};
|
||||
const {
|
||||
source,
|
||||
extractedCommentsSource
|
||||
} = output; // Write extracted comments to commentsFilename
|
||||
|
||||
if (extractedCommentsSource) {
|
||||
const {
|
||||
commentsFilename
|
||||
} = output;
|
||||
newInfo.related = {
|
||||
license: commentsFilename
|
||||
};
|
||||
allExtractedComments.set(name, {
|
||||
extractedCommentsSource,
|
||||
commentsFilename
|
||||
});
|
||||
}
|
||||
|
||||
compilation.updateAsset(name, source, newInfo);
|
||||
});
|
||||
}
|
||||
|
||||
const limit = getWorker && numberOfAssets > 0 ?
|
||||
/** @type {number} */
|
||||
numberOfWorkers : scheduledTasks.length;
|
||||
await throttleAll(limit, scheduledTasks);
|
||||
|
||||
if (initializedWorker) {
|
||||
await initializedWorker.end();
|
||||
}
|
||||
/** @typedef {{ source: import("webpack").sources.Source, commentsFilename: string, from: string }} ExtractedCommentsInfoWIthFrom */
|
||||
|
||||
|
||||
await Array.from(allExtractedComments).sort().reduce(
|
||||
/**
|
||||
* @param {Promise<unknown>} previousPromise
|
||||
* @param {[string, ExtractedCommentsInfo]} extractedComments
|
||||
* @returns {Promise<ExtractedCommentsInfoWIthFrom>}
|
||||
*/
|
||||
async (previousPromise, [from, value]) => {
|
||||
const previous =
|
||||
/** @type {ExtractedCommentsInfoWIthFrom | undefined} **/
|
||||
await previousPromise;
|
||||
const {
|
||||
commentsFilename,
|
||||
extractedCommentsSource
|
||||
} = value;
|
||||
|
||||
if (previous && previous.commentsFilename === commentsFilename) {
|
||||
const {
|
||||
from: previousFrom,
|
||||
source: prevSource
|
||||
} = previous;
|
||||
const mergedName = `${previousFrom}|${from}`;
|
||||
const name = `${commentsFilename}|${mergedName}`;
|
||||
const eTag = [prevSource, extractedCommentsSource].map(item => cache.getLazyHashedEtag(item)).reduce((previousValue, currentValue) => cache.mergeEtags(previousValue, currentValue));
|
||||
let source = await cache.getPromise(name, eTag);
|
||||
|
||||
if (!source) {
|
||||
source = new ConcatSource(Array.from(new Set([...
|
||||
/** @type {string}*/
|
||||
prevSource.source().split("\n\n"), ...
|
||||
/** @type {string}*/
|
||||
extractedCommentsSource.source().split("\n\n")])).join("\n\n"));
|
||||
await cache.storePromise(name, eTag, source);
|
||||
}
|
||||
|
||||
compilation.updateAsset(commentsFilename, source);
|
||||
return {
|
||||
source,
|
||||
commentsFilename,
|
||||
from: mergedName
|
||||
};
|
||||
}
|
||||
|
||||
const existingAsset = compilation.getAsset(commentsFilename);
|
||||
|
||||
if (existingAsset) {
|
||||
return {
|
||||
source: existingAsset.source,
|
||||
commentsFilename,
|
||||
from: commentsFilename
|
||||
};
|
||||
}
|
||||
|
||||
compilation.emitAsset(commentsFilename, extractedCommentsSource, {
|
||||
extractedComments: true
|
||||
});
|
||||
return {
|
||||
source: extractedCommentsSource,
|
||||
commentsFilename,
|
||||
from
|
||||
};
|
||||
},
|
||||
/** @type {Promise<unknown>} */
|
||||
Promise.resolve());
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {any} environment
|
||||
* @returns {TerserECMA}
|
||||
*/
|
||||
|
||||
|
||||
static getEcmaVersion(environment) {
|
||||
// ES 6th
|
||||
if (environment.arrowFunction || environment.const || environment.destructuring || environment.forOf || environment.module) {
|
||||
return 2015;
|
||||
} // ES 11th
|
||||
|
||||
|
||||
if (environment.bigIntLiteral || environment.dynamicImport) {
|
||||
return 2020;
|
||||
}
|
||||
|
||||
return 5;
|
||||
}
|
||||
/**
|
||||
* @param {Compiler} compiler
|
||||
* @returns {void}
|
||||
*/
|
||||
|
||||
|
||||
apply(compiler) {
|
||||
const pluginName = this.constructor.name;
|
||||
const availableNumberOfCores = TerserPlugin.getAvailableNumberOfCores(this.options.parallel);
|
||||
compiler.hooks.compilation.tap(pluginName, compilation => {
|
||||
const hooks = compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(compilation);
|
||||
const data = serialize({
|
||||
minimizer: typeof this.options.minimizer.implementation.getMinimizerVersion !== "undefined" ? this.options.minimizer.implementation.getMinimizerVersion() || "0.0.0" : "0.0.0",
|
||||
options: this.options.minimizer.options
|
||||
});
|
||||
hooks.chunkHash.tap(pluginName, (chunk, hash) => {
|
||||
hash.update("TerserPlugin");
|
||||
hash.update(data);
|
||||
});
|
||||
compilation.hooks.processAssets.tapPromise({
|
||||
name: pluginName,
|
||||
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
|
||||
additionalAssets: true
|
||||
}, assets => this.optimize(compiler, compilation, assets, {
|
||||
availableNumberOfCores
|
||||
}));
|
||||
compilation.hooks.statsPrinter.tap(pluginName, stats => {
|
||||
stats.hooks.print.for("asset.info.minimized").tap("terser-webpack-plugin", (minimized, {
|
||||
green,
|
||||
formatFlag
|
||||
}) => minimized ?
|
||||
/** @type {Function} */
|
||||
green(
|
||||
/** @type {Function} */
|
||||
formatFlag("minimized")) : "");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TerserPlugin.terserMinify = terserMinify;
|
||||
TerserPlugin.uglifyJsMinify = uglifyJsMinify;
|
||||
TerserPlugin.swcMinify = swcMinify;
|
||||
TerserPlugin.esbuildMinify = esbuildMinify;
|
||||
module.exports = TerserPlugin;
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
"use strict";
|
||||
|
||||
/** @typedef {import("./index.js").MinimizedResult} MinimizedResult */
|
||||
|
||||
/** @typedef {import("./index.js").CustomOptions} CustomOptions */
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {import("./index.js").InternalOptions<T>} options
|
||||
* @returns {Promise<MinimizedResult>}
|
||||
*/
|
||||
async function minify(options) {
|
||||
const {
|
||||
name,
|
||||
input,
|
||||
inputSourceMap,
|
||||
extractComments
|
||||
} = options;
|
||||
const {
|
||||
implementation,
|
||||
options: minimizerOptions
|
||||
} = options.minimizer;
|
||||
return implementation({
|
||||
[name]: input
|
||||
}, inputSourceMap, minimizerOptions, extractComments);
|
||||
}
|
||||
/**
|
||||
* @param {string} options
|
||||
* @returns {Promise<MinimizedResult>}
|
||||
*/
|
||||
|
||||
|
||||
async function transform(options) {
|
||||
// 'use strict' => this === undefined (Clean Scope)
|
||||
// Safer for possible security issues, albeit not critical at all here
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
const evaluatedOptions =
|
||||
/**
|
||||
* @template T
|
||||
* @type {import("./index.js").InternalOptions<T>}
|
||||
* */
|
||||
// eslint-disable-next-line no-new-func
|
||||
new Function("exports", "require", "module", "__filename", "__dirname", `'use strict'\nreturn ${options}`)(exports, require, module, __filename, __dirname);
|
||||
return minify(evaluatedOptions);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
minify,
|
||||
transform
|
||||
};
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
{
|
||||
"definitions": {
|
||||
"Rule": {
|
||||
"description": "Filtering rule as regex or string.",
|
||||
"anyOf": [
|
||||
{
|
||||
"instanceof": "RegExp",
|
||||
"tsType": "RegExp"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"Rules": {
|
||||
"description": "Filtering rules.",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"description": "A rule condition.",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Rule"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/Rule"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"title": "TerserPluginOptions",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"test": {
|
||||
"description": "Include all modules that pass test assertion.",
|
||||
"link": "https://github.com/webpack-contrib/terser-webpack-plugin#test",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Rules"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": {
|
||||
"description": "Include all modules matching any of these conditions.",
|
||||
"link": "https://github.com/webpack-contrib/terser-webpack-plugin#include",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Rules"
|
||||
}
|
||||
]
|
||||
},
|
||||
"exclude": {
|
||||
"description": "Exclude all modules matching any of these conditions.",
|
||||
"link": "https://github.com/webpack-contrib/terser-webpack-plugin#exclude",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Rules"
|
||||
}
|
||||
]
|
||||
},
|
||||
"terserOptions": {
|
||||
"description": "Options for `terser` (by default) or custom `minify` function.",
|
||||
"link": "https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions",
|
||||
"additionalProperties": true,
|
||||
"type": "object"
|
||||
},
|
||||
"extractComments": {
|
||||
"description": "Whether comments shall be extracted to a separate file.",
|
||||
"link": "https://github.com/webpack-contrib/terser-webpack-plugin#extractcomments",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
{
|
||||
"instanceof": "RegExp"
|
||||
},
|
||||
{
|
||||
"instanceof": "Function"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"condition": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
{
|
||||
"instanceof": "RegExp"
|
||||
},
|
||||
{
|
||||
"instanceof": "Function"
|
||||
}
|
||||
],
|
||||
"description": "Condition what comments you need extract.",
|
||||
"link": "https://github.com/webpack-contrib/terser-webpack-plugin#condition"
|
||||
},
|
||||
"filename": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
{
|
||||
"instanceof": "Function"
|
||||
}
|
||||
],
|
||||
"description": "The file where the extracted comments will be stored. Default is to append the suffix .LICENSE.txt to the original filename.",
|
||||
"link": "https://github.com/webpack-contrib/terser-webpack-plugin#filename"
|
||||
},
|
||||
"banner": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
{
|
||||
"instanceof": "Function"
|
||||
}
|
||||
],
|
||||
"description": "The banner text that points to the extracted file and will be added on top of the original file",
|
||||
"link": "https://github.com/webpack-contrib/terser-webpack-plugin#banner"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"parallel": {
|
||||
"description": "Use multi-process parallel running to improve the build speed.",
|
||||
"link": "https://github.com/webpack-contrib/terser-webpack-plugin#parallel",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"minify": {
|
||||
"description": "Allows you to override default minify function.",
|
||||
"link": "https://github.com/webpack-contrib/terser-webpack-plugin#number",
|
||||
"instanceof": "Function"
|
||||
}
|
||||
}
|
||||
}
|
||||
+687
@@ -0,0 +1,687 @@
|
||||
"use strict";
|
||||
|
||||
/** @typedef {import("source-map").RawSourceMap} RawSourceMap */
|
||||
|
||||
/** @typedef {import("terser").FormatOptions} TerserFormatOptions */
|
||||
|
||||
/** @typedef {import("terser").MinifyOptions} TerserOptions */
|
||||
|
||||
/** @typedef {import("terser").ECMA} TerserECMA */
|
||||
|
||||
/** @typedef {import("./index.js").ExtractCommentsOptions} ExtractCommentsOptions */
|
||||
|
||||
/** @typedef {import("./index.js").ExtractCommentsFunction} ExtractCommentsFunction */
|
||||
|
||||
/** @typedef {import("./index.js").ExtractCommentsCondition} ExtractCommentsCondition */
|
||||
|
||||
/** @typedef {import("./index.js").Input} Input */
|
||||
|
||||
/** @typedef {import("./index.js").MinimizedResult} MinimizedResult */
|
||||
|
||||
/** @typedef {import("./index.js").PredefinedOptions} PredefinedOptions */
|
||||
|
||||
/** @typedef {import("./index.js").CustomOptions} CustomOptions */
|
||||
|
||||
/**
|
||||
* @typedef {Array<string>} ExtractedComments
|
||||
*/
|
||||
const notSettled = Symbol(`not-settled`);
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {() => Promise<T>} Task
|
||||
*/
|
||||
|
||||
/**
|
||||
* Run tasks with limited concurency.
|
||||
* @template T
|
||||
* @param {number} limit - Limit of tasks that run at once.
|
||||
* @param {Task<T>[]} tasks - List of tasks to run.
|
||||
* @returns {Promise<T[]>} A promise that fulfills to an array of the results
|
||||
*/
|
||||
|
||||
function throttleAll(limit, tasks) {
|
||||
if (!Number.isInteger(limit) || limit < 1) {
|
||||
throw new TypeError(`Expected \`limit\` to be a finite number > 0, got \`${limit}\` (${typeof limit})`);
|
||||
}
|
||||
|
||||
if (!Array.isArray(tasks) || !tasks.every(task => typeof task === `function`)) {
|
||||
throw new TypeError(`Expected \`tasks\` to be a list of functions returning a promise`);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const result = Array(tasks.length).fill(notSettled);
|
||||
const entries = tasks.entries();
|
||||
|
||||
const next = () => {
|
||||
const {
|
||||
done,
|
||||
value
|
||||
} = entries.next();
|
||||
|
||||
if (done) {
|
||||
const isLast = !result.includes(notSettled);
|
||||
if (isLast) resolve(
|
||||
/** @type{T[]} **/
|
||||
result);
|
||||
return;
|
||||
}
|
||||
|
||||
const [index, task] = value;
|
||||
/**
|
||||
* @param {T} x
|
||||
*/
|
||||
|
||||
const onFulfilled = x => {
|
||||
result[index] = x;
|
||||
next();
|
||||
};
|
||||
|
||||
task().then(onFulfilled, reject);
|
||||
};
|
||||
|
||||
Array(limit).fill(0).forEach(next);
|
||||
});
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
|
||||
/**
|
||||
* @param {Input} input
|
||||
* @param {RawSourceMap | undefined} sourceMap
|
||||
* @param {PredefinedOptions & CustomOptions} minimizerOptions
|
||||
* @param {ExtractCommentsOptions | undefined} extractComments
|
||||
* @return {Promise<MinimizedResult>}
|
||||
*/
|
||||
|
||||
|
||||
async function terserMinify(input, sourceMap, minimizerOptions, extractComments) {
|
||||
/**
|
||||
* @param {any} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isObject = value => {
|
||||
const type = typeof value;
|
||||
return value != null && (type === "object" || type === "function");
|
||||
};
|
||||
/**
|
||||
* @param {TerserOptions & { sourceMap: undefined } & ({ output: TerserFormatOptions & { beautify: boolean } } | { format: TerserFormatOptions & { beautify: boolean } })} terserOptions
|
||||
* @param {ExtractedComments} extractedComments
|
||||
* @returns {ExtractCommentsFunction}
|
||||
*/
|
||||
|
||||
|
||||
const buildComments = (terserOptions, extractedComments) => {
|
||||
/** @type {{ [index: string]: ExtractCommentsCondition }} */
|
||||
const condition = {};
|
||||
let comments;
|
||||
|
||||
if (terserOptions.format) {
|
||||
({
|
||||
comments
|
||||
} = terserOptions.format);
|
||||
} else if (terserOptions.output) {
|
||||
({
|
||||
comments
|
||||
} = terserOptions.output);
|
||||
}
|
||||
|
||||
condition.preserve = typeof comments !== "undefined" ? comments : false;
|
||||
|
||||
if (typeof extractComments === "boolean" && extractComments) {
|
||||
condition.extract = "some";
|
||||
} else if (typeof extractComments === "string" || extractComments instanceof RegExp) {
|
||||
condition.extract = extractComments;
|
||||
} else if (typeof extractComments === "function") {
|
||||
condition.extract = extractComments;
|
||||
} else if (extractComments && isObject(extractComments)) {
|
||||
condition.extract = typeof extractComments.condition === "boolean" && extractComments.condition ? "some" : typeof extractComments.condition !== "undefined" ? extractComments.condition : "some";
|
||||
} else {
|
||||
// No extract
|
||||
// Preserve using "commentsOpts" or "some"
|
||||
condition.preserve = typeof comments !== "undefined" ? comments : "some";
|
||||
condition.extract = false;
|
||||
} // Ensure that both conditions are functions
|
||||
|
||||
|
||||
["preserve", "extract"].forEach(key => {
|
||||
/** @type {undefined | string} */
|
||||
let regexStr;
|
||||
/** @type {undefined | RegExp} */
|
||||
|
||||
let regex;
|
||||
|
||||
switch (typeof condition[key]) {
|
||||
case "boolean":
|
||||
condition[key] = condition[key] ? () => true : () => false;
|
||||
break;
|
||||
|
||||
case "function":
|
||||
break;
|
||||
|
||||
case "string":
|
||||
if (condition[key] === "all") {
|
||||
condition[key] = () => true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (condition[key] === "some") {
|
||||
condition[key] =
|
||||
/** @type {ExtractCommentsFunction} */
|
||||
(astNode, comment) => (comment.type === "comment2" || comment.type === "comment1") && /@preserve|@lic|@cc_on|^\**!/i.test(comment.value);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
regexStr =
|
||||
/** @type {string} */
|
||||
condition[key];
|
||||
|
||||
condition[key] =
|
||||
/** @type {ExtractCommentsFunction} */
|
||||
(astNode, comment) => new RegExp(
|
||||
/** @type {string} */
|
||||
regexStr).test(comment.value);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
regex =
|
||||
/** @type {RegExp} */
|
||||
condition[key];
|
||||
|
||||
condition[key] =
|
||||
/** @type {ExtractCommentsFunction} */
|
||||
(astNode, comment) =>
|
||||
/** @type {RegExp} */
|
||||
regex.test(comment.value);
|
||||
|
||||
}
|
||||
}); // Redefine the comments function to extract and preserve
|
||||
// comments according to the two conditions
|
||||
|
||||
return (astNode, comment) => {
|
||||
if (
|
||||
/** @type {{ extract: ExtractCommentsFunction }} */
|
||||
condition.extract(astNode, comment)) {
|
||||
const commentText = comment.type === "comment2" ? `/*${comment.value}*/` : `//${comment.value}`; // Don't include duplicate comments
|
||||
|
||||
if (!extractedComments.includes(commentText)) {
|
||||
extractedComments.push(commentText);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
/** @type {{ preserve: ExtractCommentsFunction }} */
|
||||
condition.preserve(astNode, comment)
|
||||
);
|
||||
};
|
||||
};
|
||||
/**
|
||||
* @param {PredefinedOptions & TerserOptions} [terserOptions={}]
|
||||
* @returns {TerserOptions & { sourceMap: undefined } & ({ output: TerserFormatOptions & { beautify: boolean } } | { format: TerserFormatOptions & { beautify: boolean } })}
|
||||
*/
|
||||
|
||||
|
||||
const buildTerserOptions = (terserOptions = {}) => {
|
||||
// Need deep copy objects to avoid https://github.com/terser/terser/issues/366
|
||||
return { ...terserOptions,
|
||||
compress: typeof terserOptions.compress === "boolean" ? terserOptions.compress : { ...terserOptions.compress
|
||||
},
|
||||
// ecma: terserOptions.ecma,
|
||||
// ie8: terserOptions.ie8,
|
||||
// keep_classnames: terserOptions.keep_classnames,
|
||||
// keep_fnames: terserOptions.keep_fnames,
|
||||
mangle: terserOptions.mangle == null ? true : typeof terserOptions.mangle === "boolean" ? terserOptions.mangle : { ...terserOptions.mangle
|
||||
},
|
||||
// module: terserOptions.module,
|
||||
// nameCache: { ...terserOptions.toplevel },
|
||||
// the `output` option is deprecated
|
||||
...(terserOptions.format ? {
|
||||
format: {
|
||||
beautify: false,
|
||||
...terserOptions.format
|
||||
}
|
||||
} : {
|
||||
output: {
|
||||
beautify: false,
|
||||
...terserOptions.output
|
||||
}
|
||||
}),
|
||||
parse: { ...terserOptions.parse
|
||||
},
|
||||
// safari10: terserOptions.safari10,
|
||||
// Ignoring sourceMap from options
|
||||
// eslint-disable-next-line no-undefined
|
||||
sourceMap: undefined // toplevel: terserOptions.toplevel
|
||||
|
||||
};
|
||||
}; // eslint-disable-next-line global-require
|
||||
|
||||
|
||||
const {
|
||||
minify
|
||||
} = require("terser"); // Copy `terser` options
|
||||
|
||||
|
||||
const terserOptions = buildTerserOptions(minimizerOptions); // Let terser generate a SourceMap
|
||||
|
||||
if (sourceMap) {
|
||||
// @ts-ignore
|
||||
terserOptions.sourceMap = {
|
||||
asObject: true
|
||||
};
|
||||
}
|
||||
/** @type {ExtractedComments} */
|
||||
|
||||
|
||||
const extractedComments = [];
|
||||
|
||||
if (terserOptions.output) {
|
||||
terserOptions.output.comments = buildComments(terserOptions, extractedComments);
|
||||
} else if (terserOptions.format) {
|
||||
terserOptions.format.comments = buildComments(terserOptions, extractedComments);
|
||||
}
|
||||
|
||||
const [[filename, code]] = Object.entries(input);
|
||||
const result = await minify({
|
||||
[filename]: code
|
||||
}, terserOptions);
|
||||
return {
|
||||
code:
|
||||
/** @type {string} **/
|
||||
result.code,
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line no-undefined
|
||||
map: result.map ?
|
||||
/** @type {RawSourceMap} **/
|
||||
result.map : undefined,
|
||||
extractedComments
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
|
||||
|
||||
terserMinify.getMinimizerVersion = () => {
|
||||
let packageJson;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line global-require
|
||||
packageJson = require("terser/package.json");
|
||||
} catch (error) {// Ignore
|
||||
}
|
||||
|
||||
return packageJson && packageJson.version;
|
||||
};
|
||||
/* istanbul ignore next */
|
||||
|
||||
/**
|
||||
* @param {Input} input
|
||||
* @param {RawSourceMap | undefined} sourceMap
|
||||
* @param {PredefinedOptions & CustomOptions} minimizerOptions
|
||||
* @param {ExtractCommentsOptions | undefined} extractComments
|
||||
* @return {Promise<MinimizedResult>}
|
||||
*/
|
||||
|
||||
|
||||
async function uglifyJsMinify(input, sourceMap, minimizerOptions, extractComments) {
|
||||
/**
|
||||
* @param {any} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isObject = value => {
|
||||
const type = typeof value;
|
||||
return value != null && (type === "object" || type === "function");
|
||||
};
|
||||
/**
|
||||
* @param {import("uglify-js").MinifyOptions & { sourceMap: undefined } & { output: import("uglify-js").OutputOptions & { beautify: boolean }}} uglifyJsOptions
|
||||
* @param {ExtractedComments} extractedComments
|
||||
* @returns {ExtractCommentsFunction}
|
||||
*/
|
||||
|
||||
|
||||
const buildComments = (uglifyJsOptions, extractedComments) => {
|
||||
/** @type {{ [index: string]: ExtractCommentsCondition }} */
|
||||
const condition = {};
|
||||
const {
|
||||
comments
|
||||
} = uglifyJsOptions.output;
|
||||
condition.preserve = typeof comments !== "undefined" ? comments : false;
|
||||
|
||||
if (typeof extractComments === "boolean" && extractComments) {
|
||||
condition.extract = "some";
|
||||
} else if (typeof extractComments === "string" || extractComments instanceof RegExp) {
|
||||
condition.extract = extractComments;
|
||||
} else if (typeof extractComments === "function") {
|
||||
condition.extract = extractComments;
|
||||
} else if (extractComments && isObject(extractComments)) {
|
||||
condition.extract = typeof extractComments.condition === "boolean" && extractComments.condition ? "some" : typeof extractComments.condition !== "undefined" ? extractComments.condition : "some";
|
||||
} else {
|
||||
// No extract
|
||||
// Preserve using "commentsOpts" or "some"
|
||||
condition.preserve = typeof comments !== "undefined" ? comments : "some";
|
||||
condition.extract = false;
|
||||
} // Ensure that both conditions are functions
|
||||
|
||||
|
||||
["preserve", "extract"].forEach(key => {
|
||||
/** @type {undefined | string} */
|
||||
let regexStr;
|
||||
/** @type {undefined | RegExp} */
|
||||
|
||||
let regex;
|
||||
|
||||
switch (typeof condition[key]) {
|
||||
case "boolean":
|
||||
condition[key] = condition[key] ? () => true : () => false;
|
||||
break;
|
||||
|
||||
case "function":
|
||||
break;
|
||||
|
||||
case "string":
|
||||
if (condition[key] === "all") {
|
||||
condition[key] = () => true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (condition[key] === "some") {
|
||||
condition[key] =
|
||||
/** @type {ExtractCommentsFunction} */
|
||||
(astNode, comment) => (comment.type === "comment2" || comment.type === "comment1") && /@preserve|@lic|@cc_on|^\**!/i.test(comment.value);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
regexStr =
|
||||
/** @type {string} */
|
||||
condition[key];
|
||||
|
||||
condition[key] =
|
||||
/** @type {ExtractCommentsFunction} */
|
||||
(astNode, comment) => new RegExp(
|
||||
/** @type {string} */
|
||||
regexStr).test(comment.value);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
regex =
|
||||
/** @type {RegExp} */
|
||||
condition[key];
|
||||
|
||||
condition[key] =
|
||||
/** @type {ExtractCommentsFunction} */
|
||||
(astNode, comment) =>
|
||||
/** @type {RegExp} */
|
||||
regex.test(comment.value);
|
||||
|
||||
}
|
||||
}); // Redefine the comments function to extract and preserve
|
||||
// comments according to the two conditions
|
||||
|
||||
return (astNode, comment) => {
|
||||
if (
|
||||
/** @type {{ extract: ExtractCommentsFunction }} */
|
||||
condition.extract(astNode, comment)) {
|
||||
const commentText = comment.type === "comment2" ? `/*${comment.value}*/` : `//${comment.value}`; // Don't include duplicate comments
|
||||
|
||||
if (!extractedComments.includes(commentText)) {
|
||||
extractedComments.push(commentText);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
/** @type {{ preserve: ExtractCommentsFunction }} */
|
||||
condition.preserve(astNode, comment)
|
||||
);
|
||||
};
|
||||
};
|
||||
/**
|
||||
* @param {PredefinedOptions & import("uglify-js").MinifyOptions} [uglifyJsOptions={}]
|
||||
* @returns {import("uglify-js").MinifyOptions & { sourceMap: undefined } & { output: import("uglify-js").OutputOptions & { beautify: boolean }}}
|
||||
*/
|
||||
|
||||
|
||||
const buildUglifyJsOptions = (uglifyJsOptions = {}) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
delete minimizerOptions.ecma; // eslint-disable-next-line no-param-reassign
|
||||
|
||||
delete minimizerOptions.module; // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
|
||||
|
||||
return { ...uglifyJsOptions,
|
||||
// warnings: uglifyJsOptions.warnings,
|
||||
parse: { ...uglifyJsOptions.parse
|
||||
},
|
||||
compress: typeof uglifyJsOptions.compress === "boolean" ? uglifyJsOptions.compress : { ...uglifyJsOptions.compress
|
||||
},
|
||||
mangle: uglifyJsOptions.mangle == null ? true : typeof uglifyJsOptions.mangle === "boolean" ? uglifyJsOptions.mangle : { ...uglifyJsOptions.mangle
|
||||
},
|
||||
output: {
|
||||
beautify: false,
|
||||
...uglifyJsOptions.output
|
||||
},
|
||||
// Ignoring sourceMap from options
|
||||
// eslint-disable-next-line no-undefined
|
||||
sourceMap: undefined // toplevel: uglifyJsOptions.toplevel
|
||||
// nameCache: { ...uglifyJsOptions.toplevel },
|
||||
// ie8: uglifyJsOptions.ie8,
|
||||
// keep_fnames: uglifyJsOptions.keep_fnames,
|
||||
|
||||
};
|
||||
}; // eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
||||
|
||||
|
||||
const {
|
||||
minify
|
||||
} = require("uglify-js"); // Copy `uglify-js` options
|
||||
|
||||
|
||||
const uglifyJsOptions = buildUglifyJsOptions(minimizerOptions); // Let terser generate a SourceMap
|
||||
|
||||
if (sourceMap) {
|
||||
// @ts-ignore
|
||||
uglifyJsOptions.sourceMap = true;
|
||||
}
|
||||
/** @type {ExtractedComments} */
|
||||
|
||||
|
||||
const extractedComments = []; // @ts-ignore
|
||||
|
||||
uglifyJsOptions.output.comments = buildComments(uglifyJsOptions, extractedComments);
|
||||
const [[filename, code]] = Object.entries(input);
|
||||
const result = await minify({
|
||||
[filename]: code
|
||||
}, uglifyJsOptions);
|
||||
return {
|
||||
code: result.code,
|
||||
// eslint-disable-next-line no-undefined
|
||||
map: result.map ? JSON.parse(result.map) : undefined,
|
||||
errors: result.error ? [result.error] : [],
|
||||
warnings: result.warnings || [],
|
||||
extractedComments
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
|
||||
|
||||
uglifyJsMinify.getMinimizerVersion = () => {
|
||||
let packageJson;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
||||
packageJson = require("uglify-js/package.json");
|
||||
} catch (error) {// Ignore
|
||||
}
|
||||
|
||||
return packageJson && packageJson.version;
|
||||
};
|
||||
/* istanbul ignore next */
|
||||
|
||||
/**
|
||||
* @param {Input} input
|
||||
* @param {RawSourceMap | undefined} sourceMap
|
||||
* @param {PredefinedOptions & CustomOptions} minimizerOptions
|
||||
* @return {Promise<MinimizedResult>}
|
||||
*/
|
||||
|
||||
|
||||
async function swcMinify(input, sourceMap, minimizerOptions) {
|
||||
/**
|
||||
* @param {PredefinedOptions & import("@swc/core").JsMinifyOptions} [swcOptions={}]
|
||||
* @returns {import("@swc/core").JsMinifyOptions & { sourceMap: undefined }}
|
||||
*/
|
||||
const buildSwcOptions = (swcOptions = {}) => {
|
||||
// Need deep copy objects to avoid https://github.com/terser/terser/issues/366
|
||||
return { ...swcOptions,
|
||||
compress: typeof swcOptions.compress === "boolean" ? swcOptions.compress : { ...swcOptions.compress
|
||||
},
|
||||
mangle: swcOptions.mangle == null ? true : typeof swcOptions.mangle === "boolean" ? swcOptions.mangle : { ...swcOptions.mangle
|
||||
},
|
||||
// ecma: swcOptions.ecma,
|
||||
// keep_classnames: swcOptions.keep_classnames,
|
||||
// keep_fnames: swcOptions.keep_fnames,
|
||||
// module: swcOptions.module,
|
||||
// safari10: swcOptions.safari10,
|
||||
// toplevel: swcOptions.toplevel
|
||||
// eslint-disable-next-line no-undefined
|
||||
sourceMap: undefined
|
||||
};
|
||||
}; // eslint-disable-next-line import/no-extraneous-dependencies, global-require
|
||||
|
||||
|
||||
const swc = require("@swc/core"); // Copy `swc` options
|
||||
|
||||
|
||||
const swcOptions = buildSwcOptions(minimizerOptions); // Let `swc` generate a SourceMap
|
||||
|
||||
if (sourceMap) {
|
||||
// @ts-ignore
|
||||
swcOptions.sourceMap = true;
|
||||
}
|
||||
|
||||
const [[filename, code]] = Object.entries(input);
|
||||
const result = await swc.minify(code, swcOptions);
|
||||
let map;
|
||||
|
||||
if (result.map) {
|
||||
map = JSON.parse(result.map); // TODO workaround for swc because `filename` is not preset as in `swc` signature as for `terser`
|
||||
|
||||
map.sources = [filename];
|
||||
delete map.sourcesContent;
|
||||
}
|
||||
|
||||
return {
|
||||
code: result.code,
|
||||
map
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
|
||||
|
||||
swcMinify.getMinimizerVersion = () => {
|
||||
let packageJson;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
||||
packageJson = require("@swc/core/package.json");
|
||||
} catch (error) {// Ignore
|
||||
}
|
||||
|
||||
return packageJson && packageJson.version;
|
||||
};
|
||||
/* istanbul ignore next */
|
||||
|
||||
/**
|
||||
* @param {Input} input
|
||||
* @param {RawSourceMap | undefined} sourceMap
|
||||
* @param {PredefinedOptions & CustomOptions} minimizerOptions
|
||||
* @return {Promise<MinimizedResult>}
|
||||
*/
|
||||
|
||||
|
||||
async function esbuildMinify(input, sourceMap, minimizerOptions) {
|
||||
/**
|
||||
* @param {PredefinedOptions & import("esbuild").TransformOptions} [esbuildOptions={}]
|
||||
* @returns {import("esbuild").TransformOptions}
|
||||
*/
|
||||
const buildEsbuildOptions = (esbuildOptions = {}) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
delete esbuildOptions.ecma;
|
||||
|
||||
if (esbuildOptions.module) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
esbuildOptions.format = "esm";
|
||||
} // eslint-disable-next-line no-param-reassign
|
||||
|
||||
|
||||
delete esbuildOptions.module; // Need deep copy objects to avoid https://github.com/terser/terser/issues/366
|
||||
|
||||
return {
|
||||
minify: true,
|
||||
legalComments: "inline",
|
||||
...esbuildOptions,
|
||||
sourcemap: false
|
||||
};
|
||||
}; // eslint-disable-next-line import/no-extraneous-dependencies, global-require
|
||||
|
||||
|
||||
const esbuild = require("esbuild"); // Copy `esbuild` options
|
||||
|
||||
|
||||
const esbuildOptions = buildEsbuildOptions(minimizerOptions); // Let `esbuild` generate a SourceMap
|
||||
|
||||
if (sourceMap) {
|
||||
esbuildOptions.sourcemap = true;
|
||||
esbuildOptions.sourcesContent = false;
|
||||
}
|
||||
|
||||
const [[filename, code]] = Object.entries(input);
|
||||
esbuildOptions.sourcefile = filename;
|
||||
const result = await esbuild.transform(code, esbuildOptions);
|
||||
return {
|
||||
code: result.code,
|
||||
// eslint-disable-next-line no-undefined
|
||||
map: result.map ? JSON.parse(result.map) : undefined,
|
||||
warnings: result.warnings.length > 0 ? result.warnings.map(item => {
|
||||
return {
|
||||
name: "Warning",
|
||||
source: item.location && item.location.file,
|
||||
line: item.location && item.location.line,
|
||||
column: item.location && item.location.column,
|
||||
plugin: item.pluginName,
|
||||
message: `${item.text}${item.detail ? `\nDetails:\n${item.detail}` : ""}${item.notes.length > 0 ? `\n\nNotes:\n${item.notes.map(note => `${note.location ? `[${note.location.file}:${note.location.line}:${note.location.column}] ` : ""}${note.text}${note.location ? `\nSuggestion: ${note.location.suggestion}` : ""}${note.location ? `\nLine text:\n${note.location.lineText}\n` : ""}`).join("\n")}` : ""}`
|
||||
};
|
||||
}) : []
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
|
||||
|
||||
esbuildMinify.getMinimizerVersion = () => {
|
||||
let packageJson;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
||||
packageJson = require("esbuild/package.json");
|
||||
} catch (error) {// Ignore
|
||||
}
|
||||
|
||||
return packageJson && packageJson.version;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
throttleAll,
|
||||
terserMinify,
|
||||
uglifyJsMinify,
|
||||
swcMinify,
|
||||
esbuildMinify
|
||||
};
|
||||
+20
@@ -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
@@ -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
|
||||
Generated
Vendored
+74
@@ -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;
|
||||
}
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import { validate } from "./validate";
|
||||
import { ValidationError } from "./validate";
|
||||
export { validate, ValidationError };
|
||||
Generated
Vendored
+10
@@ -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;
|
||||
Generated
Vendored
+79
@@ -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;
|
||||
Generated
Vendored
+3
@@ -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;
|
||||
Generated
Vendored
+37
@@ -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 };
|
||||
Generated
Vendored
+1271
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
validate,
|
||||
ValidationError
|
||||
} = require("./validate");
|
||||
|
||||
module.exports = {
|
||||
validate,
|
||||
ValidationError
|
||||
};
|
||||
Generated
Vendored
+93
@@ -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;
|
||||
Generated
Vendored
+163
@@ -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;
|
||||
Generated
Vendored
+105
@@ -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;
|
||||
};
|
||||
Generated
Vendored
+163
@@ -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;
|
||||
}
|
||||
Generated
Vendored
+78
@@ -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"
|
||||
]
|
||||
}
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
# Change Log
|
||||
|
||||
## 0.5.6
|
||||
|
||||
* Fix for regression when people were using numbers as names in source maps. See
|
||||
#236.
|
||||
|
||||
## 0.5.5
|
||||
|
||||
* Fix "regression" of unsupported, implementation behavior that half the world
|
||||
happens to have come to depend on. See #235.
|
||||
|
||||
* Fix regression involving function hoisting in SpiderMonkey. See #233.
|
||||
|
||||
## 0.5.4
|
||||
|
||||
* Large performance improvements to source-map serialization. See #228 and #229.
|
||||
|
||||
## 0.5.3
|
||||
|
||||
* Do not include unnecessary distribution files. See
|
||||
commit ef7006f8d1647e0a83fdc60f04f5a7ca54886f86.
|
||||
|
||||
## 0.5.2
|
||||
|
||||
* Include browser distributions of the library in package.json's `files`. See
|
||||
issue #212.
|
||||
|
||||
## 0.5.1
|
||||
|
||||
* Fix latent bugs in IndexedSourceMapConsumer.prototype._parseMappings. See
|
||||
ff05274becc9e6e1295ed60f3ea090d31d843379.
|
||||
|
||||
## 0.5.0
|
||||
|
||||
* Node 0.8 is no longer supported.
|
||||
|
||||
* Use webpack instead of dryice for bundling.
|
||||
|
||||
* Big speedups serializing source maps. See pull request #203.
|
||||
|
||||
* Fix a bug with `SourceMapConsumer.prototype.sourceContentFor` and sources that
|
||||
explicitly start with the source root. See issue #199.
|
||||
|
||||
## 0.4.4
|
||||
|
||||
* Fix an issue where using a `SourceMapGenerator` after having created a
|
||||
`SourceMapConsumer` from it via `SourceMapConsumer.fromSourceMap` failed. See
|
||||
issue #191.
|
||||
|
||||
* Fix an issue with where `SourceMapGenerator` would mistakenly consider
|
||||
different mappings as duplicates of each other and avoid generating them. See
|
||||
issue #192.
|
||||
|
||||
## 0.4.3
|
||||
|
||||
* A very large number of performance improvements, particularly when parsing
|
||||
source maps. Collectively about 75% of time shaved off of the source map
|
||||
parsing benchmark!
|
||||
|
||||
* Fix a bug in `SourceMapConsumer.prototype.allGeneratedPositionsFor` and fuzzy
|
||||
searching in the presence of a column option. See issue #177.
|
||||
|
||||
* Fix a bug with joining a source and its source root when the source is above
|
||||
the root. See issue #182.
|
||||
|
||||
* Add the `SourceMapConsumer.prototype.hasContentsOfAllSources` method to
|
||||
determine when all sources' contents are inlined into the source map. See
|
||||
issue #190.
|
||||
|
||||
## 0.4.2
|
||||
|
||||
* Add an `.npmignore` file so that the benchmarks aren't pulled down by
|
||||
dependent projects. Issue #169.
|
||||
|
||||
* Add an optional `column` argument to
|
||||
`SourceMapConsumer.prototype.allGeneratedPositionsFor` and better handle lines
|
||||
with no mappings. Issues #172 and #173.
|
||||
|
||||
## 0.4.1
|
||||
|
||||
* Fix accidentally defining a global variable. #170.
|
||||
|
||||
## 0.4.0
|
||||
|
||||
* The default direction for fuzzy searching was changed back to its original
|
||||
direction. See #164.
|
||||
|
||||
* There is now a `bias` option you can supply to `SourceMapConsumer` to control
|
||||
the fuzzy searching direction. See #167.
|
||||
|
||||
* About an 8% speed up in parsing source maps. See #159.
|
||||
|
||||
* Added a benchmark for parsing and generating source maps.
|
||||
|
||||
## 0.3.0
|
||||
|
||||
* Change the default direction that searching for positions fuzzes when there is
|
||||
not an exact match. See #154.
|
||||
|
||||
* Support for environments using json2.js for JSON serialization. See #156.
|
||||
|
||||
## 0.2.0
|
||||
|
||||
* Support for consuming "indexed" source maps which do not have any remote
|
||||
sections. See pull request #127. This introduces a minor backwards
|
||||
incompatibility if you are monkey patching `SourceMapConsumer.prototype`
|
||||
methods.
|
||||
|
||||
## 0.1.43
|
||||
|
||||
* Performance improvements for `SourceMapGenerator` and `SourceNode`. See issue
|
||||
#148 for some discussion and issues #150, #151, and #152 for implementations.
|
||||
|
||||
## 0.1.42
|
||||
|
||||
* Fix an issue where `SourceNode`s from different versions of the source-map
|
||||
library couldn't be used in conjunction with each other. See issue #142.
|
||||
|
||||
## 0.1.41
|
||||
|
||||
* Fix a bug with getting the source content of relative sources with a "./"
|
||||
prefix. See issue #145 and [Bug 1090768](bugzil.la/1090768).
|
||||
|
||||
* Add the `SourceMapConsumer.prototype.computeColumnSpans` method to compute the
|
||||
column span of each mapping.
|
||||
|
||||
* Add the `SourceMapConsumer.prototype.allGeneratedPositionsFor` method to find
|
||||
all generated positions associated with a given original source and line.
|
||||
|
||||
## 0.1.40
|
||||
|
||||
* Performance improvements for parsing source maps in SourceMapConsumer.
|
||||
|
||||
## 0.1.39
|
||||
|
||||
* Fix a bug where setting a source's contents to null before any source content
|
||||
had been set before threw a TypeError. See issue #131.
|
||||
|
||||
## 0.1.38
|
||||
|
||||
* Fix a bug where finding relative paths from an empty path were creating
|
||||
absolute paths. See issue #129.
|
||||
|
||||
## 0.1.37
|
||||
|
||||
* Fix a bug where if the source root was an empty string, relative source paths
|
||||
would turn into absolute source paths. Issue #124.
|
||||
|
||||
## 0.1.36
|
||||
|
||||
* Allow the `names` mapping property to be an empty string. Issue #121.
|
||||
|
||||
## 0.1.35
|
||||
|
||||
* A third optional parameter was added to `SourceNode.fromStringWithSourceMap`
|
||||
to specify a path that relative sources in the second parameter should be
|
||||
relative to. Issue #105.
|
||||
|
||||
* If no file property is given to a `SourceMapGenerator`, then the resulting
|
||||
source map will no longer have a `null` file property. The property will
|
||||
simply not exist. Issue #104.
|
||||
|
||||
* Fixed a bug where consecutive newlines were ignored in `SourceNode`s.
|
||||
Issue #116.
|
||||
|
||||
## 0.1.34
|
||||
|
||||
* Make `SourceNode` work with windows style ("\r\n") newlines. Issue #103.
|
||||
|
||||
* Fix bug involving source contents and the
|
||||
`SourceMapGenerator.prototype.applySourceMap`. Issue #100.
|
||||
|
||||
## 0.1.33
|
||||
|
||||
* Fix some edge cases surrounding path joining and URL resolution.
|
||||
|
||||
* Add a third parameter for relative path to
|
||||
`SourceMapGenerator.prototype.applySourceMap`.
|
||||
|
||||
* Fix issues with mappings and EOLs.
|
||||
|
||||
## 0.1.32
|
||||
|
||||
* Fixed a bug where SourceMapConsumer couldn't handle negative relative columns
|
||||
(issue 92).
|
||||
|
||||
* Fixed test runner to actually report number of failed tests as its process
|
||||
exit code.
|
||||
|
||||
* Fixed a typo when reporting bad mappings (issue 87).
|
||||
|
||||
## 0.1.31
|
||||
|
||||
* Delay parsing the mappings in SourceMapConsumer until queried for a source
|
||||
location.
|
||||
|
||||
* Support Sass source maps (which at the time of writing deviate from the spec
|
||||
in small ways) in SourceMapConsumer.
|
||||
|
||||
## 0.1.30
|
||||
|
||||
* Do not join source root with a source, when the source is a data URI.
|
||||
|
||||
* Extend the test runner to allow running single specific test files at a time.
|
||||
|
||||
* Performance improvements in `SourceNode.prototype.walk` and
|
||||
`SourceMapConsumer.prototype.eachMapping`.
|
||||
|
||||
* Source map browser builds will now work inside Workers.
|
||||
|
||||
* Better error messages when attempting to add an invalid mapping to a
|
||||
`SourceMapGenerator`.
|
||||
|
||||
## 0.1.29
|
||||
|
||||
* Allow duplicate entries in the `names` and `sources` arrays of source maps
|
||||
(usually from TypeScript) we are parsing. Fixes github issue 72.
|
||||
|
||||
## 0.1.28
|
||||
|
||||
* Skip duplicate mappings when creating source maps from SourceNode; github
|
||||
issue 75.
|
||||
|
||||
## 0.1.27
|
||||
|
||||
* Don't throw an error when the `file` property is missing in SourceMapConsumer,
|
||||
we don't use it anyway.
|
||||
|
||||
## 0.1.26
|
||||
|
||||
* Fix SourceNode.fromStringWithSourceMap for empty maps. Fixes github issue 70.
|
||||
|
||||
## 0.1.25
|
||||
|
||||
* Make compatible with browserify
|
||||
|
||||
## 0.1.24
|
||||
|
||||
* Fix issue with absolute paths and `file://` URIs. See
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=885597
|
||||
|
||||
## 0.1.23
|
||||
|
||||
* Fix issue with absolute paths and sourcesContent, github issue 64.
|
||||
|
||||
## 0.1.22
|
||||
|
||||
* Ignore duplicate mappings in SourceMapGenerator. Fixes github issue 21.
|
||||
|
||||
## 0.1.21
|
||||
|
||||
* Fixed handling of sources that start with a slash so that they are relative to
|
||||
the source root's host.
|
||||
|
||||
## 0.1.20
|
||||
|
||||
* Fixed github issue #43: absolute URLs aren't joined with the source root
|
||||
anymore.
|
||||
|
||||
## 0.1.19
|
||||
|
||||
* Using Travis CI to run tests.
|
||||
|
||||
## 0.1.18
|
||||
|
||||
* Fixed a bug in the handling of sourceRoot.
|
||||
|
||||
## 0.1.17
|
||||
|
||||
* Added SourceNode.fromStringWithSourceMap.
|
||||
|
||||
## 0.1.16
|
||||
|
||||
* Added missing documentation.
|
||||
|
||||
* Fixed the generating of empty mappings in SourceNode.
|
||||
|
||||
## 0.1.15
|
||||
|
||||
* Added SourceMapGenerator.applySourceMap.
|
||||
|
||||
## 0.1.14
|
||||
|
||||
* The sourceRoot is now handled consistently.
|
||||
|
||||
## 0.1.13
|
||||
|
||||
* Added SourceMapGenerator.fromSourceMap.
|
||||
|
||||
## 0.1.12
|
||||
|
||||
* SourceNode now generates empty mappings too.
|
||||
|
||||
## 0.1.11
|
||||
|
||||
* Added name support to SourceNode.
|
||||
|
||||
## 0.1.10
|
||||
|
||||
* Added sourcesContent support to the customer and generator.
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
|
||||
Copyright (c) 2009-2011, Mozilla Foundation and contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the names of the Mozilla Foundation nor the names of project
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+742
@@ -0,0 +1,742 @@
|
||||
# Source Map
|
||||
|
||||
[](https://travis-ci.org/mozilla/source-map)
|
||||
|
||||
[](https://www.npmjs.com/package/source-map)
|
||||
|
||||
This is a library to generate and consume the source map format
|
||||
[described here][format].
|
||||
|
||||
[format]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
|
||||
|
||||
## Use with Node
|
||||
|
||||
$ npm install source-map
|
||||
|
||||
## Use on the Web
|
||||
|
||||
<script src="https://raw.githubusercontent.com/mozilla/source-map/master/dist/source-map.min.js" defer></script>
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
<!-- `npm run toc` to regenerate the Table of Contents -->
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
## Table of Contents
|
||||
|
||||
- [Examples](#examples)
|
||||
- [Consuming a source map](#consuming-a-source-map)
|
||||
- [Generating a source map](#generating-a-source-map)
|
||||
- [With SourceNode (high level API)](#with-sourcenode-high-level-api)
|
||||
- [With SourceMapGenerator (low level API)](#with-sourcemapgenerator-low-level-api)
|
||||
- [API](#api)
|
||||
- [SourceMapConsumer](#sourcemapconsumer)
|
||||
- [new SourceMapConsumer(rawSourceMap)](#new-sourcemapconsumerrawsourcemap)
|
||||
- [SourceMapConsumer.prototype.computeColumnSpans()](#sourcemapconsumerprototypecomputecolumnspans)
|
||||
- [SourceMapConsumer.prototype.originalPositionFor(generatedPosition)](#sourcemapconsumerprototypeoriginalpositionforgeneratedposition)
|
||||
- [SourceMapConsumer.prototype.generatedPositionFor(originalPosition)](#sourcemapconsumerprototypegeneratedpositionfororiginalposition)
|
||||
- [SourceMapConsumer.prototype.allGeneratedPositionsFor(originalPosition)](#sourcemapconsumerprototypeallgeneratedpositionsfororiginalposition)
|
||||
- [SourceMapConsumer.prototype.hasContentsOfAllSources()](#sourcemapconsumerprototypehascontentsofallsources)
|
||||
- [SourceMapConsumer.prototype.sourceContentFor(source[, returnNullOnMissing])](#sourcemapconsumerprototypesourcecontentforsource-returnnullonmissing)
|
||||
- [SourceMapConsumer.prototype.eachMapping(callback, context, order)](#sourcemapconsumerprototypeeachmappingcallback-context-order)
|
||||
- [SourceMapGenerator](#sourcemapgenerator)
|
||||
- [new SourceMapGenerator([startOfSourceMap])](#new-sourcemapgeneratorstartofsourcemap)
|
||||
- [SourceMapGenerator.fromSourceMap(sourceMapConsumer)](#sourcemapgeneratorfromsourcemapsourcemapconsumer)
|
||||
- [SourceMapGenerator.prototype.addMapping(mapping)](#sourcemapgeneratorprototypeaddmappingmapping)
|
||||
- [SourceMapGenerator.prototype.setSourceContent(sourceFile, sourceContent)](#sourcemapgeneratorprototypesetsourcecontentsourcefile-sourcecontent)
|
||||
- [SourceMapGenerator.prototype.applySourceMap(sourceMapConsumer[, sourceFile[, sourceMapPath]])](#sourcemapgeneratorprototypeapplysourcemapsourcemapconsumer-sourcefile-sourcemappath)
|
||||
- [SourceMapGenerator.prototype.toString()](#sourcemapgeneratorprototypetostring)
|
||||
- [SourceNode](#sourcenode)
|
||||
- [new SourceNode([line, column, source[, chunk[, name]]])](#new-sourcenodeline-column-source-chunk-name)
|
||||
- [SourceNode.fromStringWithSourceMap(code, sourceMapConsumer[, relativePath])](#sourcenodefromstringwithsourcemapcode-sourcemapconsumer-relativepath)
|
||||
- [SourceNode.prototype.add(chunk)](#sourcenodeprototypeaddchunk)
|
||||
- [SourceNode.prototype.prepend(chunk)](#sourcenodeprototypeprependchunk)
|
||||
- [SourceNode.prototype.setSourceContent(sourceFile, sourceContent)](#sourcenodeprototypesetsourcecontentsourcefile-sourcecontent)
|
||||
- [SourceNode.prototype.walk(fn)](#sourcenodeprototypewalkfn)
|
||||
- [SourceNode.prototype.walkSourceContents(fn)](#sourcenodeprototypewalksourcecontentsfn)
|
||||
- [SourceNode.prototype.join(sep)](#sourcenodeprototypejoinsep)
|
||||
- [SourceNode.prototype.replaceRight(pattern, replacement)](#sourcenodeprototypereplacerightpattern-replacement)
|
||||
- [SourceNode.prototype.toString()](#sourcenodeprototypetostring)
|
||||
- [SourceNode.prototype.toStringWithSourceMap([startOfSourceMap])](#sourcenodeprototypetostringwithsourcemapstartofsourcemap)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Examples
|
||||
|
||||
### Consuming a source map
|
||||
|
||||
```js
|
||||
var rawSourceMap = {
|
||||
version: 3,
|
||||
file: 'min.js',
|
||||
names: ['bar', 'baz', 'n'],
|
||||
sources: ['one.js', 'two.js'],
|
||||
sourceRoot: 'http://example.com/www/js/',
|
||||
mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
|
||||
};
|
||||
|
||||
var smc = new SourceMapConsumer(rawSourceMap);
|
||||
|
||||
console.log(smc.sources);
|
||||
// [ 'http://example.com/www/js/one.js',
|
||||
// 'http://example.com/www/js/two.js' ]
|
||||
|
||||
console.log(smc.originalPositionFor({
|
||||
line: 2,
|
||||
column: 28
|
||||
}));
|
||||
// { source: 'http://example.com/www/js/two.js',
|
||||
// line: 2,
|
||||
// column: 10,
|
||||
// name: 'n' }
|
||||
|
||||
console.log(smc.generatedPositionFor({
|
||||
source: 'http://example.com/www/js/two.js',
|
||||
line: 2,
|
||||
column: 10
|
||||
}));
|
||||
// { line: 2, column: 28 }
|
||||
|
||||
smc.eachMapping(function (m) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
### Generating a source map
|
||||
|
||||
In depth guide:
|
||||
[**Compiling to JavaScript, and Debugging with Source Maps**](https://hacks.mozilla.org/2013/05/compiling-to-javascript-and-debugging-with-source-maps/)
|
||||
|
||||
#### With SourceNode (high level API)
|
||||
|
||||
```js
|
||||
function compile(ast) {
|
||||
switch (ast.type) {
|
||||
case 'BinaryExpression':
|
||||
return new SourceNode(
|
||||
ast.location.line,
|
||||
ast.location.column,
|
||||
ast.location.source,
|
||||
[compile(ast.left), " + ", compile(ast.right)]
|
||||
);
|
||||
case 'Literal':
|
||||
return new SourceNode(
|
||||
ast.location.line,
|
||||
ast.location.column,
|
||||
ast.location.source,
|
||||
String(ast.value)
|
||||
);
|
||||
// ...
|
||||
default:
|
||||
throw new Error("Bad AST");
|
||||
}
|
||||
}
|
||||
|
||||
var ast = parse("40 + 2", "add.js");
|
||||
console.log(compile(ast).toStringWithSourceMap({
|
||||
file: 'add.js'
|
||||
}));
|
||||
// { code: '40 + 2',
|
||||
// map: [object SourceMapGenerator] }
|
||||
```
|
||||
|
||||
#### With SourceMapGenerator (low level API)
|
||||
|
||||
```js
|
||||
var map = new SourceMapGenerator({
|
||||
file: "source-mapped.js"
|
||||
});
|
||||
|
||||
map.addMapping({
|
||||
generated: {
|
||||
line: 10,
|
||||
column: 35
|
||||
},
|
||||
source: "foo.js",
|
||||
original: {
|
||||
line: 33,
|
||||
column: 2
|
||||
},
|
||||
name: "christopher"
|
||||
});
|
||||
|
||||
console.log(map.toString());
|
||||
// '{"version":3,"file":"source-mapped.js","sources":["foo.js"],"names":["christopher"],"mappings":";;;;;;;;;mCAgCEA"}'
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
Get a reference to the module:
|
||||
|
||||
```js
|
||||
// Node.js
|
||||
var sourceMap = require('source-map');
|
||||
|
||||
// Browser builds
|
||||
var sourceMap = window.sourceMap;
|
||||
|
||||
// Inside Firefox
|
||||
const sourceMap = require("devtools/toolkit/sourcemap/source-map.js");
|
||||
```
|
||||
|
||||
### SourceMapConsumer
|
||||
|
||||
A SourceMapConsumer instance represents a parsed source map which we can query
|
||||
for information about the original file positions by giving it a file position
|
||||
in the generated source.
|
||||
|
||||
#### new SourceMapConsumer(rawSourceMap)
|
||||
|
||||
The only parameter is the raw source map (either as a string which can be
|
||||
`JSON.parse`'d, or an object). According to the spec, source maps have the
|
||||
following attributes:
|
||||
|
||||
* `version`: Which version of the source map spec this map is following.
|
||||
|
||||
* `sources`: An array of URLs to the original source files.
|
||||
|
||||
* `names`: An array of identifiers which can be referenced by individual
|
||||
mappings.
|
||||
|
||||
* `sourceRoot`: Optional. The URL root from which all sources are relative.
|
||||
|
||||
* `sourcesContent`: Optional. An array of contents of the original source files.
|
||||
|
||||
* `mappings`: A string of base64 VLQs which contain the actual mappings.
|
||||
|
||||
* `file`: Optional. The generated filename this source map is associated with.
|
||||
|
||||
```js
|
||||
var consumer = new sourceMap.SourceMapConsumer(rawSourceMapJsonData);
|
||||
```
|
||||
|
||||
#### SourceMapConsumer.prototype.computeColumnSpans()
|
||||
|
||||
Compute the last column for each generated mapping. The last column is
|
||||
inclusive.
|
||||
|
||||
```js
|
||||
// Before:
|
||||
consumer.allGeneratedPositionsFor({ line: 2, source: "foo.coffee" })
|
||||
// [ { line: 2,
|
||||
// column: 1 },
|
||||
// { line: 2,
|
||||
// column: 10 },
|
||||
// { line: 2,
|
||||
// column: 20 } ]
|
||||
|
||||
consumer.computeColumnSpans();
|
||||
|
||||
// After:
|
||||
consumer.allGeneratedPositionsFor({ line: 2, source: "foo.coffee" })
|
||||
// [ { line: 2,
|
||||
// column: 1,
|
||||
// lastColumn: 9 },
|
||||
// { line: 2,
|
||||
// column: 10,
|
||||
// lastColumn: 19 },
|
||||
// { line: 2,
|
||||
// column: 20,
|
||||
// lastColumn: Infinity } ]
|
||||
|
||||
```
|
||||
|
||||
#### SourceMapConsumer.prototype.originalPositionFor(generatedPosition)
|
||||
|
||||
Returns the original source, line, and column information for the generated
|
||||
source's line and column positions provided. The only argument is an object with
|
||||
the following properties:
|
||||
|
||||
* `line`: The line number in the generated source. Line numbers in
|
||||
this library are 1-based (note that the underlying source map
|
||||
specification uses 0-based line numbers -- this library handles the
|
||||
translation).
|
||||
|
||||
* `column`: The column number in the generated source. Column numbers
|
||||
in this library are 0-based.
|
||||
|
||||
* `bias`: Either `SourceMapConsumer.GREATEST_LOWER_BOUND` or
|
||||
`SourceMapConsumer.LEAST_UPPER_BOUND`. Specifies whether to return the closest
|
||||
element that is smaller than or greater than the one we are searching for,
|
||||
respectively, if the exact element cannot be found. Defaults to
|
||||
`SourceMapConsumer.GREATEST_LOWER_BOUND`.
|
||||
|
||||
and an object is returned with the following properties:
|
||||
|
||||
* `source`: The original source file, or null if this information is not
|
||||
available.
|
||||
|
||||
* `line`: The line number in the original source, or null if this information is
|
||||
not available. The line number is 1-based.
|
||||
|
||||
* `column`: The column number in the original source, or null if this
|
||||
information is not available. The column number is 0-based.
|
||||
|
||||
* `name`: The original identifier, or null if this information is not available.
|
||||
|
||||
```js
|
||||
consumer.originalPositionFor({ line: 2, column: 10 })
|
||||
// { source: 'foo.coffee',
|
||||
// line: 2,
|
||||
// column: 2,
|
||||
// name: null }
|
||||
|
||||
consumer.originalPositionFor({ line: 99999999999999999, column: 999999999999999 })
|
||||
// { source: null,
|
||||
// line: null,
|
||||
// column: null,
|
||||
// name: null }
|
||||
```
|
||||
|
||||
#### SourceMapConsumer.prototype.generatedPositionFor(originalPosition)
|
||||
|
||||
Returns the generated line and column information for the original source,
|
||||
line, and column positions provided. The only argument is an object with
|
||||
the following properties:
|
||||
|
||||
* `source`: The filename of the original source.
|
||||
|
||||
* `line`: The line number in the original source. The line number is
|
||||
1-based.
|
||||
|
||||
* `column`: The column number in the original source. The column
|
||||
number is 0-based.
|
||||
|
||||
and an object is returned with the following properties:
|
||||
|
||||
* `line`: The line number in the generated source, or null. The line
|
||||
number is 1-based.
|
||||
|
||||
* `column`: The column number in the generated source, or null. The
|
||||
column number is 0-based.
|
||||
|
||||
```js
|
||||
consumer.generatedPositionFor({ source: "example.js", line: 2, column: 10 })
|
||||
// { line: 1,
|
||||
// column: 56 }
|
||||
```
|
||||
|
||||
#### SourceMapConsumer.prototype.allGeneratedPositionsFor(originalPosition)
|
||||
|
||||
Returns all generated line and column information for the original source, line,
|
||||
and column provided. If no column is provided, returns all mappings
|
||||
corresponding to a either the line we are searching for or the next closest line
|
||||
that has any mappings. Otherwise, returns all mappings corresponding to the
|
||||
given line and either the column we are searching for or the next closest column
|
||||
that has any offsets.
|
||||
|
||||
The only argument is an object with the following properties:
|
||||
|
||||
* `source`: The filename of the original source.
|
||||
|
||||
* `line`: The line number in the original source. The line number is
|
||||
1-based.
|
||||
|
||||
* `column`: Optional. The column number in the original source. The
|
||||
column number is 0-based.
|
||||
|
||||
and an array of objects is returned, each with the following properties:
|
||||
|
||||
* `line`: The line number in the generated source, or null. The line
|
||||
number is 1-based.
|
||||
|
||||
* `column`: The column number in the generated source, or null. The
|
||||
column number is 0-based.
|
||||
|
||||
```js
|
||||
consumer.allGeneratedpositionsfor({ line: 2, source: "foo.coffee" })
|
||||
// [ { line: 2,
|
||||
// column: 1 },
|
||||
// { line: 2,
|
||||
// column: 10 },
|
||||
// { line: 2,
|
||||
// column: 20 } ]
|
||||
```
|
||||
|
||||
#### SourceMapConsumer.prototype.hasContentsOfAllSources()
|
||||
|
||||
Return true if we have the embedded source content for every source listed in
|
||||
the source map, false otherwise.
|
||||
|
||||
In other words, if this method returns `true`, then
|
||||
`consumer.sourceContentFor(s)` will succeed for every source `s` in
|
||||
`consumer.sources`.
|
||||
|
||||
```js
|
||||
// ...
|
||||
if (consumer.hasContentsOfAllSources()) {
|
||||
consumerReadyCallback(consumer);
|
||||
} else {
|
||||
fetchSources(consumer, consumerReadyCallback);
|
||||
}
|
||||
// ...
|
||||
```
|
||||
|
||||
#### SourceMapConsumer.prototype.sourceContentFor(source[, returnNullOnMissing])
|
||||
|
||||
Returns the original source content for the source provided. The only
|
||||
argument is the URL of the original source file.
|
||||
|
||||
If the source content for the given source is not found, then an error is
|
||||
thrown. Optionally, pass `true` as the second param to have `null` returned
|
||||
instead.
|
||||
|
||||
```js
|
||||
consumer.sources
|
||||
// [ "my-cool-lib.clj" ]
|
||||
|
||||
consumer.sourceContentFor("my-cool-lib.clj")
|
||||
// "..."
|
||||
|
||||
consumer.sourceContentFor("this is not in the source map");
|
||||
// Error: "this is not in the source map" is not in the source map
|
||||
|
||||
consumer.sourceContentFor("this is not in the source map", true);
|
||||
// null
|
||||
```
|
||||
|
||||
#### SourceMapConsumer.prototype.eachMapping(callback, context, order)
|
||||
|
||||
Iterate over each mapping between an original source/line/column and a
|
||||
generated line/column in this source map.
|
||||
|
||||
* `callback`: The function that is called with each mapping. Mappings have the
|
||||
form `{ source, generatedLine, generatedColumn, originalLine, originalColumn,
|
||||
name }`
|
||||
|
||||
* `context`: Optional. If specified, this object will be the value of `this`
|
||||
every time that `callback` is called.
|
||||
|
||||
* `order`: Either `SourceMapConsumer.GENERATED_ORDER` or
|
||||
`SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to iterate over
|
||||
the mappings sorted by the generated file's line/column order or the
|
||||
original's source/line/column order, respectively. Defaults to
|
||||
`SourceMapConsumer.GENERATED_ORDER`.
|
||||
|
||||
```js
|
||||
consumer.eachMapping(function (m) { console.log(m); })
|
||||
// ...
|
||||
// { source: 'illmatic.js',
|
||||
// generatedLine: 1,
|
||||
// generatedColumn: 0,
|
||||
// originalLine: 1,
|
||||
// originalColumn: 0,
|
||||
// name: null }
|
||||
// { source: 'illmatic.js',
|
||||
// generatedLine: 2,
|
||||
// generatedColumn: 0,
|
||||
// originalLine: 2,
|
||||
// originalColumn: 0,
|
||||
// name: null }
|
||||
// ...
|
||||
```
|
||||
### SourceMapGenerator
|
||||
|
||||
An instance of the SourceMapGenerator represents a source map which is being
|
||||
built incrementally.
|
||||
|
||||
#### new SourceMapGenerator([startOfSourceMap])
|
||||
|
||||
You may pass an object with the following properties:
|
||||
|
||||
* `file`: The filename of the generated source that this source map is
|
||||
associated with.
|
||||
|
||||
* `sourceRoot`: A root for all relative URLs in this source map.
|
||||
|
||||
* `skipValidation`: Optional. When `true`, disables validation of mappings as
|
||||
they are added. This can improve performance but should be used with
|
||||
discretion, as a last resort. Even then, one should avoid using this flag when
|
||||
running tests, if possible.
|
||||
|
||||
```js
|
||||
var generator = new sourceMap.SourceMapGenerator({
|
||||
file: "my-generated-javascript-file.js",
|
||||
sourceRoot: "http://example.com/app/js/"
|
||||
});
|
||||
```
|
||||
|
||||
#### SourceMapGenerator.fromSourceMap(sourceMapConsumer)
|
||||
|
||||
Creates a new `SourceMapGenerator` from an existing `SourceMapConsumer` instance.
|
||||
|
||||
* `sourceMapConsumer` The SourceMap.
|
||||
|
||||
```js
|
||||
var generator = sourceMap.SourceMapGenerator.fromSourceMap(consumer);
|
||||
```
|
||||
|
||||
#### SourceMapGenerator.prototype.addMapping(mapping)
|
||||
|
||||
Add a single mapping from original source line and column to the generated
|
||||
source's line and column for this source map being created. The mapping object
|
||||
should have the following properties:
|
||||
|
||||
* `generated`: An object with the generated line and column positions.
|
||||
|
||||
* `original`: An object with the original line and column positions.
|
||||
|
||||
* `source`: The original source file (relative to the sourceRoot).
|
||||
|
||||
* `name`: An optional original token name for this mapping.
|
||||
|
||||
```js
|
||||
generator.addMapping({
|
||||
source: "module-one.scm",
|
||||
original: { line: 128, column: 0 },
|
||||
generated: { line: 3, column: 456 }
|
||||
})
|
||||
```
|
||||
|
||||
#### SourceMapGenerator.prototype.setSourceContent(sourceFile, sourceContent)
|
||||
|
||||
Set the source content for an original source file.
|
||||
|
||||
* `sourceFile` the URL of the original source file.
|
||||
|
||||
* `sourceContent` the content of the source file.
|
||||
|
||||
```js
|
||||
generator.setSourceContent("module-one.scm",
|
||||
fs.readFileSync("path/to/module-one.scm"))
|
||||
```
|
||||
|
||||
#### SourceMapGenerator.prototype.applySourceMap(sourceMapConsumer[, sourceFile[, sourceMapPath]])
|
||||
|
||||
Applies a SourceMap for a source file to the SourceMap.
|
||||
Each mapping to the supplied source file is rewritten using the
|
||||
supplied SourceMap. Note: The resolution for the resulting mappings
|
||||
is the minimum of this map and the supplied map.
|
||||
|
||||
* `sourceMapConsumer`: The SourceMap to be applied.
|
||||
|
||||
* `sourceFile`: Optional. The filename of the source file.
|
||||
If omitted, sourceMapConsumer.file will be used, if it exists.
|
||||
Otherwise an error will be thrown.
|
||||
|
||||
* `sourceMapPath`: Optional. The dirname of the path to the SourceMap
|
||||
to be applied. If relative, it is relative to the SourceMap.
|
||||
|
||||
This parameter is needed when the two SourceMaps aren't in the same
|
||||
directory, and the SourceMap to be applied contains relative source
|
||||
paths. If so, those relative source paths need to be rewritten
|
||||
relative to the SourceMap.
|
||||
|
||||
If omitted, it is assumed that both SourceMaps are in the same directory,
|
||||
thus not needing any rewriting. (Supplying `'.'` has the same effect.)
|
||||
|
||||
#### SourceMapGenerator.prototype.toString()
|
||||
|
||||
Renders the source map being generated to a string.
|
||||
|
||||
```js
|
||||
generator.toString()
|
||||
// '{"version":3,"sources":["module-one.scm"],"names":[],"mappings":"...snip...","file":"my-generated-javascript-file.js","sourceRoot":"http://example.com/app/js/"}'
|
||||
```
|
||||
|
||||
### SourceNode
|
||||
|
||||
SourceNodes provide a way to abstract over interpolating and/or concatenating
|
||||
snippets of generated JavaScript source code, while maintaining the line and
|
||||
column information associated between those snippets and the original source
|
||||
code. This is useful as the final intermediate representation a compiler might
|
||||
use before outputting the generated JS and source map.
|
||||
|
||||
#### new SourceNode([line, column, source[, chunk[, name]]])
|
||||
|
||||
* `line`: The original line number associated with this source node, or null if
|
||||
it isn't associated with an original line. The line number is 1-based.
|
||||
|
||||
* `column`: The original column number associated with this source node, or null
|
||||
if it isn't associated with an original column. The column number
|
||||
is 0-based.
|
||||
|
||||
* `source`: The original source's filename; null if no filename is provided.
|
||||
|
||||
* `chunk`: Optional. Is immediately passed to `SourceNode.prototype.add`, see
|
||||
below.
|
||||
|
||||
* `name`: Optional. The original identifier.
|
||||
|
||||
```js
|
||||
var node = new SourceNode(1, 2, "a.cpp", [
|
||||
new SourceNode(3, 4, "b.cpp", "extern int status;\n"),
|
||||
new SourceNode(5, 6, "c.cpp", "std::string* make_string(size_t n);\n"),
|
||||
new SourceNode(7, 8, "d.cpp", "int main(int argc, char** argv) {}\n"),
|
||||
]);
|
||||
```
|
||||
|
||||
#### SourceNode.fromStringWithSourceMap(code, sourceMapConsumer[, relativePath])
|
||||
|
||||
Creates a SourceNode from generated code and a SourceMapConsumer.
|
||||
|
||||
* `code`: The generated code
|
||||
|
||||
* `sourceMapConsumer` The SourceMap for the generated code
|
||||
|
||||
* `relativePath` The optional path that relative sources in `sourceMapConsumer`
|
||||
should be relative to.
|
||||
|
||||
```js
|
||||
var consumer = new SourceMapConsumer(fs.readFileSync("path/to/my-file.js.map", "utf8"));
|
||||
var node = SourceNode.fromStringWithSourceMap(fs.readFileSync("path/to/my-file.js"),
|
||||
consumer);
|
||||
```
|
||||
|
||||
#### SourceNode.prototype.add(chunk)
|
||||
|
||||
Add a chunk of generated JS to this source node.
|
||||
|
||||
* `chunk`: A string snippet of generated JS code, another instance of
|
||||
`SourceNode`, or an array where each member is one of those things.
|
||||
|
||||
```js
|
||||
node.add(" + ");
|
||||
node.add(otherNode);
|
||||
node.add([leftHandOperandNode, " + ", rightHandOperandNode]);
|
||||
```
|
||||
|
||||
#### SourceNode.prototype.prepend(chunk)
|
||||
|
||||
Prepend a chunk of generated JS to this source node.
|
||||
|
||||
* `chunk`: A string snippet of generated JS code, another instance of
|
||||
`SourceNode`, or an array where each member is one of those things.
|
||||
|
||||
```js
|
||||
node.prepend("/** Build Id: f783haef86324gf **/\n\n");
|
||||
```
|
||||
|
||||
#### SourceNode.prototype.setSourceContent(sourceFile, sourceContent)
|
||||
|
||||
Set the source content for a source file. This will be added to the
|
||||
`SourceMap` in the `sourcesContent` field.
|
||||
|
||||
* `sourceFile`: The filename of the source file
|
||||
|
||||
* `sourceContent`: The content of the source file
|
||||
|
||||
```js
|
||||
node.setSourceContent("module-one.scm",
|
||||
fs.readFileSync("path/to/module-one.scm"))
|
||||
```
|
||||
|
||||
#### SourceNode.prototype.walk(fn)
|
||||
|
||||
Walk over the tree of JS snippets in this node and its children. The walking
|
||||
function is called once for each snippet of JS and is passed that snippet and
|
||||
the its original associated source's line/column location.
|
||||
|
||||
* `fn`: The traversal function.
|
||||
|
||||
```js
|
||||
var node = new SourceNode(1, 2, "a.js", [
|
||||
new SourceNode(3, 4, "b.js", "uno"),
|
||||
"dos",
|
||||
[
|
||||
"tres",
|
||||
new SourceNode(5, 6, "c.js", "quatro")
|
||||
]
|
||||
]);
|
||||
|
||||
node.walk(function (code, loc) { console.log("WALK:", code, loc); })
|
||||
// WALK: uno { source: 'b.js', line: 3, column: 4, name: null }
|
||||
// WALK: dos { source: 'a.js', line: 1, column: 2, name: null }
|
||||
// WALK: tres { source: 'a.js', line: 1, column: 2, name: null }
|
||||
// WALK: quatro { source: 'c.js', line: 5, column: 6, name: null }
|
||||
```
|
||||
|
||||
#### SourceNode.prototype.walkSourceContents(fn)
|
||||
|
||||
Walk over the tree of SourceNodes. The walking function is called for each
|
||||
source file content and is passed the filename and source content.
|
||||
|
||||
* `fn`: The traversal function.
|
||||
|
||||
```js
|
||||
var a = new SourceNode(1, 2, "a.js", "generated from a");
|
||||
a.setSourceContent("a.js", "original a");
|
||||
var b = new SourceNode(1, 2, "b.js", "generated from b");
|
||||
b.setSourceContent("b.js", "original b");
|
||||
var c = new SourceNode(1, 2, "c.js", "generated from c");
|
||||
c.setSourceContent("c.js", "original c");
|
||||
|
||||
var node = new SourceNode(null, null, null, [a, b, c]);
|
||||
node.walkSourceContents(function (source, contents) { console.log("WALK:", source, ":", contents); })
|
||||
// WALK: a.js : original a
|
||||
// WALK: b.js : original b
|
||||
// WALK: c.js : original c
|
||||
```
|
||||
|
||||
#### SourceNode.prototype.join(sep)
|
||||
|
||||
Like `Array.prototype.join` except for SourceNodes. Inserts the separator
|
||||
between each of this source node's children.
|
||||
|
||||
* `sep`: The separator.
|
||||
|
||||
```js
|
||||
var lhs = new SourceNode(1, 2, "a.rs", "my_copy");
|
||||
var operand = new SourceNode(3, 4, "a.rs", "=");
|
||||
var rhs = new SourceNode(5, 6, "a.rs", "orig.clone()");
|
||||
|
||||
var node = new SourceNode(null, null, null, [ lhs, operand, rhs ]);
|
||||
var joinedNode = node.join(" ");
|
||||
```
|
||||
|
||||
#### SourceNode.prototype.replaceRight(pattern, replacement)
|
||||
|
||||
Call `String.prototype.replace` on the very right-most source snippet. Useful
|
||||
for trimming white space from the end of a source node, etc.
|
||||
|
||||
* `pattern`: The pattern to replace.
|
||||
|
||||
* `replacement`: The thing to replace the pattern with.
|
||||
|
||||
```js
|
||||
// Trim trailing white space.
|
||||
node.replaceRight(/\s*$/, "");
|
||||
```
|
||||
|
||||
#### SourceNode.prototype.toString()
|
||||
|
||||
Return the string representation of this source node. Walks over the tree and
|
||||
concatenates all the various snippets together to one string.
|
||||
|
||||
```js
|
||||
var node = new SourceNode(1, 2, "a.js", [
|
||||
new SourceNode(3, 4, "b.js", "uno"),
|
||||
"dos",
|
||||
[
|
||||
"tres",
|
||||
new SourceNode(5, 6, "c.js", "quatro")
|
||||
]
|
||||
]);
|
||||
|
||||
node.toString()
|
||||
// 'unodostresquatro'
|
||||
```
|
||||
|
||||
#### SourceNode.prototype.toStringWithSourceMap([startOfSourceMap])
|
||||
|
||||
Returns the string representation of this tree of source nodes, plus a
|
||||
SourceMapGenerator which contains all the mappings between the generated and
|
||||
original sources.
|
||||
|
||||
The arguments are the same as those to `new SourceMapGenerator`.
|
||||
|
||||
```js
|
||||
var node = new SourceNode(1, 2, "a.js", [
|
||||
new SourceNode(3, 4, "b.js", "uno"),
|
||||
"dos",
|
||||
[
|
||||
"tres",
|
||||
new SourceNode(5, 6, "c.js", "quatro")
|
||||
]
|
||||
]);
|
||||
|
||||
node.toStringWithSourceMap({ file: "my-output-file.js" })
|
||||
// { code: 'unodostresquatro',
|
||||
// map: [object SourceMapGenerator] }
|
||||
```
|
||||
Generated
Vendored
+3234
File diff suppressed because one or more lines are too long
Generated
Vendored
+3233
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+2
File diff suppressed because one or more lines are too long
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+121
@@ -0,0 +1,121 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
var util = require('./util');
|
||||
var has = Object.prototype.hasOwnProperty;
|
||||
var hasNativeMap = typeof Map !== "undefined";
|
||||
|
||||
/**
|
||||
* A data structure which is a combination of an array and a set. Adding a new
|
||||
* member is O(1), testing for membership is O(1), and finding the index of an
|
||||
* element is O(1). Removing elements from the set is not supported. Only
|
||||
* strings are supported for membership.
|
||||
*/
|
||||
function ArraySet() {
|
||||
this._array = [];
|
||||
this._set = hasNativeMap ? new Map() : Object.create(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method for creating ArraySet instances from an existing array.
|
||||
*/
|
||||
ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
|
||||
var set = new ArraySet();
|
||||
for (var i = 0, len = aArray.length; i < len; i++) {
|
||||
set.add(aArray[i], aAllowDuplicates);
|
||||
}
|
||||
return set;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return how many unique items are in this ArraySet. If duplicates have been
|
||||
* added, than those do not count towards the size.
|
||||
*
|
||||
* @returns Number
|
||||
*/
|
||||
ArraySet.prototype.size = function ArraySet_size() {
|
||||
return hasNativeMap ? this._set.size : Object.getOwnPropertyNames(this._set).length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the given string to this set.
|
||||
*
|
||||
* @param String aStr
|
||||
*/
|
||||
ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
|
||||
var sStr = hasNativeMap ? aStr : util.toSetString(aStr);
|
||||
var isDuplicate = hasNativeMap ? this.has(aStr) : has.call(this._set, sStr);
|
||||
var idx = this._array.length;
|
||||
if (!isDuplicate || aAllowDuplicates) {
|
||||
this._array.push(aStr);
|
||||
}
|
||||
if (!isDuplicate) {
|
||||
if (hasNativeMap) {
|
||||
this._set.set(aStr, idx);
|
||||
} else {
|
||||
this._set[sStr] = idx;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the given string a member of this set?
|
||||
*
|
||||
* @param String aStr
|
||||
*/
|
||||
ArraySet.prototype.has = function ArraySet_has(aStr) {
|
||||
if (hasNativeMap) {
|
||||
return this._set.has(aStr);
|
||||
} else {
|
||||
var sStr = util.toSetString(aStr);
|
||||
return has.call(this._set, sStr);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* What is the index of the given string in the array?
|
||||
*
|
||||
* @param String aStr
|
||||
*/
|
||||
ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
|
||||
if (hasNativeMap) {
|
||||
var idx = this._set.get(aStr);
|
||||
if (idx >= 0) {
|
||||
return idx;
|
||||
}
|
||||
} else {
|
||||
var sStr = util.toSetString(aStr);
|
||||
if (has.call(this._set, sStr)) {
|
||||
return this._set[sStr];
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('"' + aStr + '" is not in the set.');
|
||||
};
|
||||
|
||||
/**
|
||||
* What is the element at the given index?
|
||||
*
|
||||
* @param Number aIdx
|
||||
*/
|
||||
ArraySet.prototype.at = function ArraySet_at(aIdx) {
|
||||
if (aIdx >= 0 && aIdx < this._array.length) {
|
||||
return this._array[aIdx];
|
||||
}
|
||||
throw new Error('No element indexed by ' + aIdx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the array representation of this set (which has the proper indices
|
||||
* indicated by indexOf). Note that this is a copy of the internal array used
|
||||
* for storing the members so that no one can mess with internal state.
|
||||
*/
|
||||
ArraySet.prototype.toArray = function ArraySet_toArray() {
|
||||
return this._array.slice();
|
||||
};
|
||||
|
||||
exports.ArraySet = ArraySet;
|
||||
Generated
Vendored
+140
@@ -0,0 +1,140 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
* Based on the Base 64 VLQ implementation in Closure Compiler:
|
||||
* https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
|
||||
*
|
||||
* Copyright 2011 The Closure Compiler Authors. All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
var base64 = require('./base64');
|
||||
|
||||
// A single base 64 digit can contain 6 bits of data. For the base 64 variable
|
||||
// length quantities we use in the source map spec, the first bit is the sign,
|
||||
// the next four bits are the actual value, and the 6th bit is the
|
||||
// continuation bit. The continuation bit tells us whether there are more
|
||||
// digits in this value following this digit.
|
||||
//
|
||||
// Continuation
|
||||
// | Sign
|
||||
// | |
|
||||
// V V
|
||||
// 101011
|
||||
|
||||
var VLQ_BASE_SHIFT = 5;
|
||||
|
||||
// binary: 100000
|
||||
var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
|
||||
|
||||
// binary: 011111
|
||||
var VLQ_BASE_MASK = VLQ_BASE - 1;
|
||||
|
||||
// binary: 100000
|
||||
var VLQ_CONTINUATION_BIT = VLQ_BASE;
|
||||
|
||||
/**
|
||||
* Converts from a two-complement value to a value where the sign bit is
|
||||
* placed in the least significant bit. For example, as decimals:
|
||||
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
|
||||
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
|
||||
*/
|
||||
function toVLQSigned(aValue) {
|
||||
return aValue < 0
|
||||
? ((-aValue) << 1) + 1
|
||||
: (aValue << 1) + 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts to a two-complement value from a value where the sign bit is
|
||||
* placed in the least significant bit. For example, as decimals:
|
||||
* 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
|
||||
* 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
|
||||
*/
|
||||
function fromVLQSigned(aValue) {
|
||||
var isNegative = (aValue & 1) === 1;
|
||||
var shifted = aValue >> 1;
|
||||
return isNegative
|
||||
? -shifted
|
||||
: shifted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base 64 VLQ encoded value.
|
||||
*/
|
||||
exports.encode = function base64VLQ_encode(aValue) {
|
||||
var encoded = "";
|
||||
var digit;
|
||||
|
||||
var vlq = toVLQSigned(aValue);
|
||||
|
||||
do {
|
||||
digit = vlq & VLQ_BASE_MASK;
|
||||
vlq >>>= VLQ_BASE_SHIFT;
|
||||
if (vlq > 0) {
|
||||
// There are still more digits in this value, so we must make sure the
|
||||
// continuation bit is marked.
|
||||
digit |= VLQ_CONTINUATION_BIT;
|
||||
}
|
||||
encoded += base64.encode(digit);
|
||||
} while (vlq > 0);
|
||||
|
||||
return encoded;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes the next base 64 VLQ value from the given string and returns the
|
||||
* value and the rest of the string via the out parameter.
|
||||
*/
|
||||
exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) {
|
||||
var strLen = aStr.length;
|
||||
var result = 0;
|
||||
var shift = 0;
|
||||
var continuation, digit;
|
||||
|
||||
do {
|
||||
if (aIndex >= strLen) {
|
||||
throw new Error("Expected more digits in base 64 VLQ value.");
|
||||
}
|
||||
|
||||
digit = base64.decode(aStr.charCodeAt(aIndex++));
|
||||
if (digit === -1) {
|
||||
throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1));
|
||||
}
|
||||
|
||||
continuation = !!(digit & VLQ_CONTINUATION_BIT);
|
||||
digit &= VLQ_BASE_MASK;
|
||||
result = result + (digit << shift);
|
||||
shift += VLQ_BASE_SHIFT;
|
||||
} while (continuation);
|
||||
|
||||
aOutParam.value = fromVLQSigned(result);
|
||||
aOutParam.rest = aIndex;
|
||||
};
|
||||
Generated
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
|
||||
|
||||
/**
|
||||
* Encode an integer in the range of 0 to 63 to a single base 64 digit.
|
||||
*/
|
||||
exports.encode = function (number) {
|
||||
if (0 <= number && number < intToCharMap.length) {
|
||||
return intToCharMap[number];
|
||||
}
|
||||
throw new TypeError("Must be between 0 and 63: " + number);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode a single base 64 character code digit to an integer. Returns -1 on
|
||||
* failure.
|
||||
*/
|
||||
exports.decode = function (charCode) {
|
||||
var bigA = 65; // 'A'
|
||||
var bigZ = 90; // 'Z'
|
||||
|
||||
var littleA = 97; // 'a'
|
||||
var littleZ = 122; // 'z'
|
||||
|
||||
var zero = 48; // '0'
|
||||
var nine = 57; // '9'
|
||||
|
||||
var plus = 43; // '+'
|
||||
var slash = 47; // '/'
|
||||
|
||||
var littleOffset = 26;
|
||||
var numberOffset = 52;
|
||||
|
||||
// 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
if (bigA <= charCode && charCode <= bigZ) {
|
||||
return (charCode - bigA);
|
||||
}
|
||||
|
||||
// 26 - 51: abcdefghijklmnopqrstuvwxyz
|
||||
if (littleA <= charCode && charCode <= littleZ) {
|
||||
return (charCode - littleA + littleOffset);
|
||||
}
|
||||
|
||||
// 52 - 61: 0123456789
|
||||
if (zero <= charCode && charCode <= nine) {
|
||||
return (charCode - zero + numberOffset);
|
||||
}
|
||||
|
||||
// 62: +
|
||||
if (charCode == plus) {
|
||||
return 62;
|
||||
}
|
||||
|
||||
// 63: /
|
||||
if (charCode == slash) {
|
||||
return 63;
|
||||
}
|
||||
|
||||
// Invalid base64 digit.
|
||||
return -1;
|
||||
};
|
||||
Generated
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
exports.GREATEST_LOWER_BOUND = 1;
|
||||
exports.LEAST_UPPER_BOUND = 2;
|
||||
|
||||
/**
|
||||
* Recursive implementation of binary search.
|
||||
*
|
||||
* @param aLow Indices here and lower do not contain the needle.
|
||||
* @param aHigh Indices here and higher do not contain the needle.
|
||||
* @param aNeedle The element being searched for.
|
||||
* @param aHaystack The non-empty array being searched.
|
||||
* @param aCompare Function which takes two elements and returns -1, 0, or 1.
|
||||
* @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
|
||||
* 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
|
||||
* closest element that is smaller than or greater than the one we are
|
||||
* searching for, respectively, if the exact element cannot be found.
|
||||
*/
|
||||
function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) {
|
||||
// This function terminates when one of the following is true:
|
||||
//
|
||||
// 1. We find the exact element we are looking for.
|
||||
//
|
||||
// 2. We did not find the exact element, but we can return the index of
|
||||
// the next-closest element.
|
||||
//
|
||||
// 3. We did not find the exact element, and there is no next-closest
|
||||
// element than the one we are searching for, so we return -1.
|
||||
var mid = Math.floor((aHigh - aLow) / 2) + aLow;
|
||||
var cmp = aCompare(aNeedle, aHaystack[mid], true);
|
||||
if (cmp === 0) {
|
||||
// Found the element we are looking for.
|
||||
return mid;
|
||||
}
|
||||
else if (cmp > 0) {
|
||||
// Our needle is greater than aHaystack[mid].
|
||||
if (aHigh - mid > 1) {
|
||||
// The element is in the upper half.
|
||||
return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias);
|
||||
}
|
||||
|
||||
// The exact needle element was not found in this haystack. Determine if
|
||||
// we are in termination case (3) or (2) and return the appropriate thing.
|
||||
if (aBias == exports.LEAST_UPPER_BOUND) {
|
||||
return aHigh < aHaystack.length ? aHigh : -1;
|
||||
} else {
|
||||
return mid;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Our needle is less than aHaystack[mid].
|
||||
if (mid - aLow > 1) {
|
||||
// The element is in the lower half.
|
||||
return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias);
|
||||
}
|
||||
|
||||
// we are in termination case (3) or (2) and return the appropriate thing.
|
||||
if (aBias == exports.LEAST_UPPER_BOUND) {
|
||||
return mid;
|
||||
} else {
|
||||
return aLow < 0 ? -1 : aLow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an implementation of binary search which will always try and return
|
||||
* the index of the closest element if there is no exact hit. This is because
|
||||
* mappings between original and generated line/col pairs are single points,
|
||||
* and there is an implicit region between each of them, so a miss just means
|
||||
* that you aren't on the very start of a region.
|
||||
*
|
||||
* @param aNeedle The element you are looking for.
|
||||
* @param aHaystack The array that is being searched.
|
||||
* @param aCompare A function which takes the needle and an element in the
|
||||
* array and returns -1, 0, or 1 depending on whether the needle is less
|
||||
* than, equal to, or greater than the element, respectively.
|
||||
* @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
|
||||
* 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
|
||||
* closest element that is smaller than or greater than the one we are
|
||||
* searching for, respectively, if the exact element cannot be found.
|
||||
* Defaults to 'binarySearch.GREATEST_LOWER_BOUND'.
|
||||
*/
|
||||
exports.search = function search(aNeedle, aHaystack, aCompare, aBias) {
|
||||
if (aHaystack.length === 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack,
|
||||
aCompare, aBias || exports.GREATEST_LOWER_BOUND);
|
||||
if (index < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We have found either the exact element, or the next-closest element than
|
||||
// the one we are searching for. However, there may be more than one such
|
||||
// element. Make sure we always return the smallest of these.
|
||||
while (index - 1 >= 0) {
|
||||
if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) {
|
||||
break;
|
||||
}
|
||||
--index;
|
||||
}
|
||||
|
||||
return index;
|
||||
};
|
||||
Generated
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2014 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
var util = require('./util');
|
||||
|
||||
/**
|
||||
* Determine whether mappingB is after mappingA with respect to generated
|
||||
* position.
|
||||
*/
|
||||
function generatedPositionAfter(mappingA, mappingB) {
|
||||
// Optimized for most common case
|
||||
var lineA = mappingA.generatedLine;
|
||||
var lineB = mappingB.generatedLine;
|
||||
var columnA = mappingA.generatedColumn;
|
||||
var columnB = mappingB.generatedColumn;
|
||||
return lineB > lineA || lineB == lineA && columnB >= columnA ||
|
||||
util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* A data structure to provide a sorted view of accumulated mappings in a
|
||||
* performance conscious manner. It trades a neglibable overhead in general
|
||||
* case for a large speedup in case of mappings being added in order.
|
||||
*/
|
||||
function MappingList() {
|
||||
this._array = [];
|
||||
this._sorted = true;
|
||||
// Serves as infimum
|
||||
this._last = {generatedLine: -1, generatedColumn: 0};
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate through internal items. This method takes the same arguments that
|
||||
* `Array.prototype.forEach` takes.
|
||||
*
|
||||
* NOTE: The order of the mappings is NOT guaranteed.
|
||||
*/
|
||||
MappingList.prototype.unsortedForEach =
|
||||
function MappingList_forEach(aCallback, aThisArg) {
|
||||
this._array.forEach(aCallback, aThisArg);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the given source mapping.
|
||||
*
|
||||
* @param Object aMapping
|
||||
*/
|
||||
MappingList.prototype.add = function MappingList_add(aMapping) {
|
||||
if (generatedPositionAfter(this._last, aMapping)) {
|
||||
this._last = aMapping;
|
||||
this._array.push(aMapping);
|
||||
} else {
|
||||
this._sorted = false;
|
||||
this._array.push(aMapping);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the flat, sorted array of mappings. The mappings are sorted by
|
||||
* generated position.
|
||||
*
|
||||
* WARNING: This method returns internal data without copying, for
|
||||
* performance. The return value must NOT be mutated, and should be treated as
|
||||
* an immutable borrow. If you want to take ownership, you must make your own
|
||||
* copy.
|
||||
*/
|
||||
MappingList.prototype.toArray = function MappingList_toArray() {
|
||||
if (!this._sorted) {
|
||||
this._array.sort(util.compareByGeneratedPositionsInflated);
|
||||
this._sorted = true;
|
||||
}
|
||||
return this._array;
|
||||
};
|
||||
|
||||
exports.MappingList = MappingList;
|
||||
Generated
Vendored
+114
@@ -0,0 +1,114 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
// It turns out that some (most?) JavaScript engines don't self-host
|
||||
// `Array.prototype.sort`. This makes sense because C++ will likely remain
|
||||
// faster than JS when doing raw CPU-intensive sorting. However, when using a
|
||||
// custom comparator function, calling back and forth between the VM's C++ and
|
||||
// JIT'd JS is rather slow *and* loses JIT type information, resulting in
|
||||
// worse generated code for the comparator function than would be optimal. In
|
||||
// fact, when sorting with a comparator, these costs outweigh the benefits of
|
||||
// sorting in C++. By using our own JS-implemented Quick Sort (below), we get
|
||||
// a ~3500ms mean speed-up in `bench/bench.html`.
|
||||
|
||||
/**
|
||||
* Swap the elements indexed by `x` and `y` in the array `ary`.
|
||||
*
|
||||
* @param {Array} ary
|
||||
* The array.
|
||||
* @param {Number} x
|
||||
* The index of the first item.
|
||||
* @param {Number} y
|
||||
* The index of the second item.
|
||||
*/
|
||||
function swap(ary, x, y) {
|
||||
var temp = ary[x];
|
||||
ary[x] = ary[y];
|
||||
ary[y] = temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random integer within the range `low .. high` inclusive.
|
||||
*
|
||||
* @param {Number} low
|
||||
* The lower bound on the range.
|
||||
* @param {Number} high
|
||||
* The upper bound on the range.
|
||||
*/
|
||||
function randomIntInRange(low, high) {
|
||||
return Math.round(low + (Math.random() * (high - low)));
|
||||
}
|
||||
|
||||
/**
|
||||
* The Quick Sort algorithm.
|
||||
*
|
||||
* @param {Array} ary
|
||||
* An array to sort.
|
||||
* @param {function} comparator
|
||||
* Function to use to compare two items.
|
||||
* @param {Number} p
|
||||
* Start index of the array
|
||||
* @param {Number} r
|
||||
* End index of the array
|
||||
*/
|
||||
function doQuickSort(ary, comparator, p, r) {
|
||||
// If our lower bound is less than our upper bound, we (1) partition the
|
||||
// array into two pieces and (2) recurse on each half. If it is not, this is
|
||||
// the empty array and our base case.
|
||||
|
||||
if (p < r) {
|
||||
// (1) Partitioning.
|
||||
//
|
||||
// The partitioning chooses a pivot between `p` and `r` and moves all
|
||||
// elements that are less than or equal to the pivot to the before it, and
|
||||
// all the elements that are greater than it after it. The effect is that
|
||||
// once partition is done, the pivot is in the exact place it will be when
|
||||
// the array is put in sorted order, and it will not need to be moved
|
||||
// again. This runs in O(n) time.
|
||||
|
||||
// Always choose a random pivot so that an input array which is reverse
|
||||
// sorted does not cause O(n^2) running time.
|
||||
var pivotIndex = randomIntInRange(p, r);
|
||||
var i = p - 1;
|
||||
|
||||
swap(ary, pivotIndex, r);
|
||||
var pivot = ary[r];
|
||||
|
||||
// Immediately after `j` is incremented in this loop, the following hold
|
||||
// true:
|
||||
//
|
||||
// * Every element in `ary[p .. i]` is less than or equal to the pivot.
|
||||
//
|
||||
// * Every element in `ary[i+1 .. j-1]` is greater than the pivot.
|
||||
for (var j = p; j < r; j++) {
|
||||
if (comparator(ary[j], pivot) <= 0) {
|
||||
i += 1;
|
||||
swap(ary, i, j);
|
||||
}
|
||||
}
|
||||
|
||||
swap(ary, i + 1, j);
|
||||
var q = i + 1;
|
||||
|
||||
// (2) Recurse on each half.
|
||||
|
||||
doQuickSort(ary, comparator, p, q - 1);
|
||||
doQuickSort(ary, comparator, q + 1, r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the given array in-place with the given comparator function.
|
||||
*
|
||||
* @param {Array} ary
|
||||
* An array to sort.
|
||||
* @param {function} comparator
|
||||
* Function to use to compare two items.
|
||||
*/
|
||||
exports.quickSort = function (ary, comparator) {
|
||||
doQuickSort(ary, comparator, 0, ary.length - 1);
|
||||
};
|
||||
Generated
Vendored
+1145
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+425
@@ -0,0 +1,425 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
var base64VLQ = require('./base64-vlq');
|
||||
var util = require('./util');
|
||||
var ArraySet = require('./array-set').ArraySet;
|
||||
var MappingList = require('./mapping-list').MappingList;
|
||||
|
||||
/**
|
||||
* An instance of the SourceMapGenerator represents a source map which is
|
||||
* being built incrementally. You may pass an object with the following
|
||||
* properties:
|
||||
*
|
||||
* - file: The filename of the generated source.
|
||||
* - sourceRoot: A root for all relative URLs in this source map.
|
||||
*/
|
||||
function SourceMapGenerator(aArgs) {
|
||||
if (!aArgs) {
|
||||
aArgs = {};
|
||||
}
|
||||
this._file = util.getArg(aArgs, 'file', null);
|
||||
this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
|
||||
this._skipValidation = util.getArg(aArgs, 'skipValidation', false);
|
||||
this._sources = new ArraySet();
|
||||
this._names = new ArraySet();
|
||||
this._mappings = new MappingList();
|
||||
this._sourcesContents = null;
|
||||
}
|
||||
|
||||
SourceMapGenerator.prototype._version = 3;
|
||||
|
||||
/**
|
||||
* Creates a new SourceMapGenerator based on a SourceMapConsumer
|
||||
*
|
||||
* @param aSourceMapConsumer The SourceMap.
|
||||
*/
|
||||
SourceMapGenerator.fromSourceMap =
|
||||
function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {
|
||||
var sourceRoot = aSourceMapConsumer.sourceRoot;
|
||||
var generator = new SourceMapGenerator({
|
||||
file: aSourceMapConsumer.file,
|
||||
sourceRoot: sourceRoot
|
||||
});
|
||||
aSourceMapConsumer.eachMapping(function (mapping) {
|
||||
var newMapping = {
|
||||
generated: {
|
||||
line: mapping.generatedLine,
|
||||
column: mapping.generatedColumn
|
||||
}
|
||||
};
|
||||
|
||||
if (mapping.source != null) {
|
||||
newMapping.source = mapping.source;
|
||||
if (sourceRoot != null) {
|
||||
newMapping.source = util.relative(sourceRoot, newMapping.source);
|
||||
}
|
||||
|
||||
newMapping.original = {
|
||||
line: mapping.originalLine,
|
||||
column: mapping.originalColumn
|
||||
};
|
||||
|
||||
if (mapping.name != null) {
|
||||
newMapping.name = mapping.name;
|
||||
}
|
||||
}
|
||||
|
||||
generator.addMapping(newMapping);
|
||||
});
|
||||
aSourceMapConsumer.sources.forEach(function (sourceFile) {
|
||||
var sourceRelative = sourceFile;
|
||||
if (sourceRoot !== null) {
|
||||
sourceRelative = util.relative(sourceRoot, sourceFile);
|
||||
}
|
||||
|
||||
if (!generator._sources.has(sourceRelative)) {
|
||||
generator._sources.add(sourceRelative);
|
||||
}
|
||||
|
||||
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
|
||||
if (content != null) {
|
||||
generator.setSourceContent(sourceFile, content);
|
||||
}
|
||||
});
|
||||
return generator;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a single mapping from original source line and column to the generated
|
||||
* source's line and column for this source map being created. The mapping
|
||||
* object should have the following properties:
|
||||
*
|
||||
* - generated: An object with the generated line and column positions.
|
||||
* - original: An object with the original line and column positions.
|
||||
* - source: The original source file (relative to the sourceRoot).
|
||||
* - name: An optional original token name for this mapping.
|
||||
*/
|
||||
SourceMapGenerator.prototype.addMapping =
|
||||
function SourceMapGenerator_addMapping(aArgs) {
|
||||
var generated = util.getArg(aArgs, 'generated');
|
||||
var original = util.getArg(aArgs, 'original', null);
|
||||
var source = util.getArg(aArgs, 'source', null);
|
||||
var name = util.getArg(aArgs, 'name', null);
|
||||
|
||||
if (!this._skipValidation) {
|
||||
this._validateMapping(generated, original, source, name);
|
||||
}
|
||||
|
||||
if (source != null) {
|
||||
source = String(source);
|
||||
if (!this._sources.has(source)) {
|
||||
this._sources.add(source);
|
||||
}
|
||||
}
|
||||
|
||||
if (name != null) {
|
||||
name = String(name);
|
||||
if (!this._names.has(name)) {
|
||||
this._names.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
this._mappings.add({
|
||||
generatedLine: generated.line,
|
||||
generatedColumn: generated.column,
|
||||
originalLine: original != null && original.line,
|
||||
originalColumn: original != null && original.column,
|
||||
source: source,
|
||||
name: name
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the source content for a source file.
|
||||
*/
|
||||
SourceMapGenerator.prototype.setSourceContent =
|
||||
function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
|
||||
var source = aSourceFile;
|
||||
if (this._sourceRoot != null) {
|
||||
source = util.relative(this._sourceRoot, source);
|
||||
}
|
||||
|
||||
if (aSourceContent != null) {
|
||||
// Add the source content to the _sourcesContents map.
|
||||
// Create a new _sourcesContents map if the property is null.
|
||||
if (!this._sourcesContents) {
|
||||
this._sourcesContents = Object.create(null);
|
||||
}
|
||||
this._sourcesContents[util.toSetString(source)] = aSourceContent;
|
||||
} else if (this._sourcesContents) {
|
||||
// Remove the source file from the _sourcesContents map.
|
||||
// If the _sourcesContents map is empty, set the property to null.
|
||||
delete this._sourcesContents[util.toSetString(source)];
|
||||
if (Object.keys(this._sourcesContents).length === 0) {
|
||||
this._sourcesContents = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Applies the mappings of a sub-source-map for a specific source file to the
|
||||
* source map being generated. Each mapping to the supplied source file is
|
||||
* rewritten using the supplied source map. Note: The resolution for the
|
||||
* resulting mappings is the minimium of this map and the supplied map.
|
||||
*
|
||||
* @param aSourceMapConsumer The source map to be applied.
|
||||
* @param aSourceFile Optional. The filename of the source file.
|
||||
* If omitted, SourceMapConsumer's file property will be used.
|
||||
* @param aSourceMapPath Optional. The dirname of the path to the source map
|
||||
* to be applied. If relative, it is relative to the SourceMapConsumer.
|
||||
* This parameter is needed when the two source maps aren't in the same
|
||||
* directory, and the source map to be applied contains relative source
|
||||
* paths. If so, those relative source paths need to be rewritten
|
||||
* relative to the SourceMapGenerator.
|
||||
*/
|
||||
SourceMapGenerator.prototype.applySourceMap =
|
||||
function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {
|
||||
var sourceFile = aSourceFile;
|
||||
// If aSourceFile is omitted, we will use the file property of the SourceMap
|
||||
if (aSourceFile == null) {
|
||||
if (aSourceMapConsumer.file == null) {
|
||||
throw new Error(
|
||||
'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' +
|
||||
'or the source map\'s "file" property. Both were omitted.'
|
||||
);
|
||||
}
|
||||
sourceFile = aSourceMapConsumer.file;
|
||||
}
|
||||
var sourceRoot = this._sourceRoot;
|
||||
// Make "sourceFile" relative if an absolute Url is passed.
|
||||
if (sourceRoot != null) {
|
||||
sourceFile = util.relative(sourceRoot, sourceFile);
|
||||
}
|
||||
// Applying the SourceMap can add and remove items from the sources and
|
||||
// the names array.
|
||||
var newSources = new ArraySet();
|
||||
var newNames = new ArraySet();
|
||||
|
||||
// Find mappings for the "sourceFile"
|
||||
this._mappings.unsortedForEach(function (mapping) {
|
||||
if (mapping.source === sourceFile && mapping.originalLine != null) {
|
||||
// Check if it can be mapped by the source map, then update the mapping.
|
||||
var original = aSourceMapConsumer.originalPositionFor({
|
||||
line: mapping.originalLine,
|
||||
column: mapping.originalColumn
|
||||
});
|
||||
if (original.source != null) {
|
||||
// Copy mapping
|
||||
mapping.source = original.source;
|
||||
if (aSourceMapPath != null) {
|
||||
mapping.source = util.join(aSourceMapPath, mapping.source)
|
||||
}
|
||||
if (sourceRoot != null) {
|
||||
mapping.source = util.relative(sourceRoot, mapping.source);
|
||||
}
|
||||
mapping.originalLine = original.line;
|
||||
mapping.originalColumn = original.column;
|
||||
if (original.name != null) {
|
||||
mapping.name = original.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var source = mapping.source;
|
||||
if (source != null && !newSources.has(source)) {
|
||||
newSources.add(source);
|
||||
}
|
||||
|
||||
var name = mapping.name;
|
||||
if (name != null && !newNames.has(name)) {
|
||||
newNames.add(name);
|
||||
}
|
||||
|
||||
}, this);
|
||||
this._sources = newSources;
|
||||
this._names = newNames;
|
||||
|
||||
// Copy sourcesContents of applied map.
|
||||
aSourceMapConsumer.sources.forEach(function (sourceFile) {
|
||||
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
|
||||
if (content != null) {
|
||||
if (aSourceMapPath != null) {
|
||||
sourceFile = util.join(aSourceMapPath, sourceFile);
|
||||
}
|
||||
if (sourceRoot != null) {
|
||||
sourceFile = util.relative(sourceRoot, sourceFile);
|
||||
}
|
||||
this.setSourceContent(sourceFile, content);
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* A mapping can have one of the three levels of data:
|
||||
*
|
||||
* 1. Just the generated position.
|
||||
* 2. The Generated position, original position, and original source.
|
||||
* 3. Generated and original position, original source, as well as a name
|
||||
* token.
|
||||
*
|
||||
* To maintain consistency, we validate that any new mapping being added falls
|
||||
* in to one of these categories.
|
||||
*/
|
||||
SourceMapGenerator.prototype._validateMapping =
|
||||
function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
|
||||
aName) {
|
||||
// When aOriginal is truthy but has empty values for .line and .column,
|
||||
// it is most likely a programmer error. In this case we throw a very
|
||||
// specific error message to try to guide them the right way.
|
||||
// For example: https://github.com/Polymer/polymer-bundler/pull/519
|
||||
if (aOriginal && typeof aOriginal.line !== 'number' && typeof aOriginal.column !== 'number') {
|
||||
throw new Error(
|
||||
'original.line and original.column are not numbers -- you probably meant to omit ' +
|
||||
'the original mapping entirely and only map the generated position. If so, pass ' +
|
||||
'null for the original mapping instead of an object with empty or null values.'
|
||||
);
|
||||
}
|
||||
|
||||
if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
|
||||
&& aGenerated.line > 0 && aGenerated.column >= 0
|
||||
&& !aOriginal && !aSource && !aName) {
|
||||
// Case 1.
|
||||
return;
|
||||
}
|
||||
else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
|
||||
&& aOriginal && 'line' in aOriginal && 'column' in aOriginal
|
||||
&& aGenerated.line > 0 && aGenerated.column >= 0
|
||||
&& aOriginal.line > 0 && aOriginal.column >= 0
|
||||
&& aSource) {
|
||||
// Cases 2 and 3.
|
||||
return;
|
||||
}
|
||||
else {
|
||||
throw new Error('Invalid mapping: ' + JSON.stringify({
|
||||
generated: aGenerated,
|
||||
source: aSource,
|
||||
original: aOriginal,
|
||||
name: aName
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize the accumulated mappings in to the stream of base 64 VLQs
|
||||
* specified by the source map format.
|
||||
*/
|
||||
SourceMapGenerator.prototype._serializeMappings =
|
||||
function SourceMapGenerator_serializeMappings() {
|
||||
var previousGeneratedColumn = 0;
|
||||
var previousGeneratedLine = 1;
|
||||
var previousOriginalColumn = 0;
|
||||
var previousOriginalLine = 0;
|
||||
var previousName = 0;
|
||||
var previousSource = 0;
|
||||
var result = '';
|
||||
var next;
|
||||
var mapping;
|
||||
var nameIdx;
|
||||
var sourceIdx;
|
||||
|
||||
var mappings = this._mappings.toArray();
|
||||
for (var i = 0, len = mappings.length; i < len; i++) {
|
||||
mapping = mappings[i];
|
||||
next = ''
|
||||
|
||||
if (mapping.generatedLine !== previousGeneratedLine) {
|
||||
previousGeneratedColumn = 0;
|
||||
while (mapping.generatedLine !== previousGeneratedLine) {
|
||||
next += ';';
|
||||
previousGeneratedLine++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (i > 0) {
|
||||
if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) {
|
||||
continue;
|
||||
}
|
||||
next += ',';
|
||||
}
|
||||
}
|
||||
|
||||
next += base64VLQ.encode(mapping.generatedColumn
|
||||
- previousGeneratedColumn);
|
||||
previousGeneratedColumn = mapping.generatedColumn;
|
||||
|
||||
if (mapping.source != null) {
|
||||
sourceIdx = this._sources.indexOf(mapping.source);
|
||||
next += base64VLQ.encode(sourceIdx - previousSource);
|
||||
previousSource = sourceIdx;
|
||||
|
||||
// lines are stored 0-based in SourceMap spec version 3
|
||||
next += base64VLQ.encode(mapping.originalLine - 1
|
||||
- previousOriginalLine);
|
||||
previousOriginalLine = mapping.originalLine - 1;
|
||||
|
||||
next += base64VLQ.encode(mapping.originalColumn
|
||||
- previousOriginalColumn);
|
||||
previousOriginalColumn = mapping.originalColumn;
|
||||
|
||||
if (mapping.name != null) {
|
||||
nameIdx = this._names.indexOf(mapping.name);
|
||||
next += base64VLQ.encode(nameIdx - previousName);
|
||||
previousName = nameIdx;
|
||||
}
|
||||
}
|
||||
|
||||
result += next;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
SourceMapGenerator.prototype._generateSourcesContent =
|
||||
function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
|
||||
return aSources.map(function (source) {
|
||||
if (!this._sourcesContents) {
|
||||
return null;
|
||||
}
|
||||
if (aSourceRoot != null) {
|
||||
source = util.relative(aSourceRoot, source);
|
||||
}
|
||||
var key = util.toSetString(source);
|
||||
return Object.prototype.hasOwnProperty.call(this._sourcesContents, key)
|
||||
? this._sourcesContents[key]
|
||||
: null;
|
||||
}, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Externalize the source map.
|
||||
*/
|
||||
SourceMapGenerator.prototype.toJSON =
|
||||
function SourceMapGenerator_toJSON() {
|
||||
var map = {
|
||||
version: this._version,
|
||||
sources: this._sources.toArray(),
|
||||
names: this._names.toArray(),
|
||||
mappings: this._serializeMappings()
|
||||
};
|
||||
if (this._file != null) {
|
||||
map.file = this._file;
|
||||
}
|
||||
if (this._sourceRoot != null) {
|
||||
map.sourceRoot = this._sourceRoot;
|
||||
}
|
||||
if (this._sourcesContents) {
|
||||
map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
|
||||
}
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the source map being generated to a string.
|
||||
*/
|
||||
SourceMapGenerator.prototype.toString =
|
||||
function SourceMapGenerator_toString() {
|
||||
return JSON.stringify(this.toJSON());
|
||||
};
|
||||
|
||||
exports.SourceMapGenerator = SourceMapGenerator;
|
||||
Generated
Vendored
+413
@@ -0,0 +1,413 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
var SourceMapGenerator = require('./source-map-generator').SourceMapGenerator;
|
||||
var util = require('./util');
|
||||
|
||||
// Matches a Windows-style `\r\n` newline or a `\n` newline used by all other
|
||||
// operating systems these days (capturing the result).
|
||||
var REGEX_NEWLINE = /(\r?\n)/;
|
||||
|
||||
// Newline character code for charCodeAt() comparisons
|
||||
var NEWLINE_CODE = 10;
|
||||
|
||||
// Private symbol for identifying `SourceNode`s when multiple versions of
|
||||
// the source-map library are loaded. This MUST NOT CHANGE across
|
||||
// versions!
|
||||
var isSourceNode = "$$$isSourceNode$$$";
|
||||
|
||||
/**
|
||||
* SourceNodes provide a way to abstract over interpolating/concatenating
|
||||
* snippets of generated JavaScript source code while maintaining the line and
|
||||
* column information associated with the original source code.
|
||||
*
|
||||
* @param aLine The original line number.
|
||||
* @param aColumn The original column number.
|
||||
* @param aSource The original source's filename.
|
||||
* @param aChunks Optional. An array of strings which are snippets of
|
||||
* generated JS, or other SourceNodes.
|
||||
* @param aName The original identifier.
|
||||
*/
|
||||
function SourceNode(aLine, aColumn, aSource, aChunks, aName) {
|
||||
this.children = [];
|
||||
this.sourceContents = {};
|
||||
this.line = aLine == null ? null : aLine;
|
||||
this.column = aColumn == null ? null : aColumn;
|
||||
this.source = aSource == null ? null : aSource;
|
||||
this.name = aName == null ? null : aName;
|
||||
this[isSourceNode] = true;
|
||||
if (aChunks != null) this.add(aChunks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SourceNode from generated code and a SourceMapConsumer.
|
||||
*
|
||||
* @param aGeneratedCode The generated code
|
||||
* @param aSourceMapConsumer The SourceMap for the generated code
|
||||
* @param aRelativePath Optional. The path that relative sources in the
|
||||
* SourceMapConsumer should be relative to.
|
||||
*/
|
||||
SourceNode.fromStringWithSourceMap =
|
||||
function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) {
|
||||
// The SourceNode we want to fill with the generated code
|
||||
// and the SourceMap
|
||||
var node = new SourceNode();
|
||||
|
||||
// All even indices of this array are one line of the generated code,
|
||||
// while all odd indices are the newlines between two adjacent lines
|
||||
// (since `REGEX_NEWLINE` captures its match).
|
||||
// Processed fragments are accessed by calling `shiftNextLine`.
|
||||
var remainingLines = aGeneratedCode.split(REGEX_NEWLINE);
|
||||
var remainingLinesIndex = 0;
|
||||
var shiftNextLine = function() {
|
||||
var lineContents = getNextLine();
|
||||
// The last line of a file might not have a newline.
|
||||
var newLine = getNextLine() || "";
|
||||
return lineContents + newLine;
|
||||
|
||||
function getNextLine() {
|
||||
return remainingLinesIndex < remainingLines.length ?
|
||||
remainingLines[remainingLinesIndex++] : undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// We need to remember the position of "remainingLines"
|
||||
var lastGeneratedLine = 1, lastGeneratedColumn = 0;
|
||||
|
||||
// The generate SourceNodes we need a code range.
|
||||
// To extract it current and last mapping is used.
|
||||
// Here we store the last mapping.
|
||||
var lastMapping = null;
|
||||
|
||||
aSourceMapConsumer.eachMapping(function (mapping) {
|
||||
if (lastMapping !== null) {
|
||||
// We add the code from "lastMapping" to "mapping":
|
||||
// First check if there is a new line in between.
|
||||
if (lastGeneratedLine < mapping.generatedLine) {
|
||||
// Associate first line with "lastMapping"
|
||||
addMappingWithCode(lastMapping, shiftNextLine());
|
||||
lastGeneratedLine++;
|
||||
lastGeneratedColumn = 0;
|
||||
// The remaining code is added without mapping
|
||||
} else {
|
||||
// There is no new line in between.
|
||||
// Associate the code between "lastGeneratedColumn" and
|
||||
// "mapping.generatedColumn" with "lastMapping"
|
||||
var nextLine = remainingLines[remainingLinesIndex] || '';
|
||||
var code = nextLine.substr(0, mapping.generatedColumn -
|
||||
lastGeneratedColumn);
|
||||
remainingLines[remainingLinesIndex] = nextLine.substr(mapping.generatedColumn -
|
||||
lastGeneratedColumn);
|
||||
lastGeneratedColumn = mapping.generatedColumn;
|
||||
addMappingWithCode(lastMapping, code);
|
||||
// No more remaining code, continue
|
||||
lastMapping = mapping;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// We add the generated code until the first mapping
|
||||
// to the SourceNode without any mapping.
|
||||
// Each line is added as separate string.
|
||||
while (lastGeneratedLine < mapping.generatedLine) {
|
||||
node.add(shiftNextLine());
|
||||
lastGeneratedLine++;
|
||||
}
|
||||
if (lastGeneratedColumn < mapping.generatedColumn) {
|
||||
var nextLine = remainingLines[remainingLinesIndex] || '';
|
||||
node.add(nextLine.substr(0, mapping.generatedColumn));
|
||||
remainingLines[remainingLinesIndex] = nextLine.substr(mapping.generatedColumn);
|
||||
lastGeneratedColumn = mapping.generatedColumn;
|
||||
}
|
||||
lastMapping = mapping;
|
||||
}, this);
|
||||
// We have processed all mappings.
|
||||
if (remainingLinesIndex < remainingLines.length) {
|
||||
if (lastMapping) {
|
||||
// Associate the remaining code in the current line with "lastMapping"
|
||||
addMappingWithCode(lastMapping, shiftNextLine());
|
||||
}
|
||||
// and add the remaining lines without any mapping
|
||||
node.add(remainingLines.splice(remainingLinesIndex).join(""));
|
||||
}
|
||||
|
||||
// Copy sourcesContent into SourceNode
|
||||
aSourceMapConsumer.sources.forEach(function (sourceFile) {
|
||||
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
|
||||
if (content != null) {
|
||||
if (aRelativePath != null) {
|
||||
sourceFile = util.join(aRelativePath, sourceFile);
|
||||
}
|
||||
node.setSourceContent(sourceFile, content);
|
||||
}
|
||||
});
|
||||
|
||||
return node;
|
||||
|
||||
function addMappingWithCode(mapping, code) {
|
||||
if (mapping === null || mapping.source === undefined) {
|
||||
node.add(code);
|
||||
} else {
|
||||
var source = aRelativePath
|
||||
? util.join(aRelativePath, mapping.source)
|
||||
: mapping.source;
|
||||
node.add(new SourceNode(mapping.originalLine,
|
||||
mapping.originalColumn,
|
||||
source,
|
||||
code,
|
||||
mapping.name));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a chunk of generated JS to this source node.
|
||||
*
|
||||
* @param aChunk A string snippet of generated JS code, another instance of
|
||||
* SourceNode, or an array where each member is one of those things.
|
||||
*/
|
||||
SourceNode.prototype.add = function SourceNode_add(aChunk) {
|
||||
if (Array.isArray(aChunk)) {
|
||||
aChunk.forEach(function (chunk) {
|
||||
this.add(chunk);
|
||||
}, this);
|
||||
}
|
||||
else if (aChunk[isSourceNode] || typeof aChunk === "string") {
|
||||
if (aChunk) {
|
||||
this.children.push(aChunk);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new TypeError(
|
||||
"Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
|
||||
);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a chunk of generated JS to the beginning of this source node.
|
||||
*
|
||||
* @param aChunk A string snippet of generated JS code, another instance of
|
||||
* SourceNode, or an array where each member is one of those things.
|
||||
*/
|
||||
SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) {
|
||||
if (Array.isArray(aChunk)) {
|
||||
for (var i = aChunk.length-1; i >= 0; i--) {
|
||||
this.prepend(aChunk[i]);
|
||||
}
|
||||
}
|
||||
else if (aChunk[isSourceNode] || typeof aChunk === "string") {
|
||||
this.children.unshift(aChunk);
|
||||
}
|
||||
else {
|
||||
throw new TypeError(
|
||||
"Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
|
||||
);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk over the tree of JS snippets in this node and its children. The
|
||||
* walking function is called once for each snippet of JS and is passed that
|
||||
* snippet and the its original associated source's line/column location.
|
||||
*
|
||||
* @param aFn The traversal function.
|
||||
*/
|
||||
SourceNode.prototype.walk = function SourceNode_walk(aFn) {
|
||||
var chunk;
|
||||
for (var i = 0, len = this.children.length; i < len; i++) {
|
||||
chunk = this.children[i];
|
||||
if (chunk[isSourceNode]) {
|
||||
chunk.walk(aFn);
|
||||
}
|
||||
else {
|
||||
if (chunk !== '') {
|
||||
aFn(chunk, { source: this.source,
|
||||
line: this.line,
|
||||
column: this.column,
|
||||
name: this.name });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between
|
||||
* each of `this.children`.
|
||||
*
|
||||
* @param aSep The separator.
|
||||
*/
|
||||
SourceNode.prototype.join = function SourceNode_join(aSep) {
|
||||
var newChildren;
|
||||
var i;
|
||||
var len = this.children.length;
|
||||
if (len > 0) {
|
||||
newChildren = [];
|
||||
for (i = 0; i < len-1; i++) {
|
||||
newChildren.push(this.children[i]);
|
||||
newChildren.push(aSep);
|
||||
}
|
||||
newChildren.push(this.children[i]);
|
||||
this.children = newChildren;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Call String.prototype.replace on the very right-most source snippet. Useful
|
||||
* for trimming whitespace from the end of a source node, etc.
|
||||
*
|
||||
* @param aPattern The pattern to replace.
|
||||
* @param aReplacement The thing to replace the pattern with.
|
||||
*/
|
||||
SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) {
|
||||
var lastChild = this.children[this.children.length - 1];
|
||||
if (lastChild[isSourceNode]) {
|
||||
lastChild.replaceRight(aPattern, aReplacement);
|
||||
}
|
||||
else if (typeof lastChild === 'string') {
|
||||
this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement);
|
||||
}
|
||||
else {
|
||||
this.children.push(''.replace(aPattern, aReplacement));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the source content for a source file. This will be added to the SourceMapGenerator
|
||||
* in the sourcesContent field.
|
||||
*
|
||||
* @param aSourceFile The filename of the source file
|
||||
* @param aSourceContent The content of the source file
|
||||
*/
|
||||
SourceNode.prototype.setSourceContent =
|
||||
function SourceNode_setSourceContent(aSourceFile, aSourceContent) {
|
||||
this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk over the tree of SourceNodes. The walking function is called for each
|
||||
* source file content and is passed the filename and source content.
|
||||
*
|
||||
* @param aFn The traversal function.
|
||||
*/
|
||||
SourceNode.prototype.walkSourceContents =
|
||||
function SourceNode_walkSourceContents(aFn) {
|
||||
for (var i = 0, len = this.children.length; i < len; i++) {
|
||||
if (this.children[i][isSourceNode]) {
|
||||
this.children[i].walkSourceContents(aFn);
|
||||
}
|
||||
}
|
||||
|
||||
var sources = Object.keys(this.sourceContents);
|
||||
for (var i = 0, len = sources.length; i < len; i++) {
|
||||
aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the string representation of this source node. Walks over the tree
|
||||
* and concatenates all the various snippets together to one string.
|
||||
*/
|
||||
SourceNode.prototype.toString = function SourceNode_toString() {
|
||||
var str = "";
|
||||
this.walk(function (chunk) {
|
||||
str += chunk;
|
||||
});
|
||||
return str;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the string representation of this source node along with a source
|
||||
* map.
|
||||
*/
|
||||
SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) {
|
||||
var generated = {
|
||||
code: "",
|
||||
line: 1,
|
||||
column: 0
|
||||
};
|
||||
var map = new SourceMapGenerator(aArgs);
|
||||
var sourceMappingActive = false;
|
||||
var lastOriginalSource = null;
|
||||
var lastOriginalLine = null;
|
||||
var lastOriginalColumn = null;
|
||||
var lastOriginalName = null;
|
||||
this.walk(function (chunk, original) {
|
||||
generated.code += chunk;
|
||||
if (original.source !== null
|
||||
&& original.line !== null
|
||||
&& original.column !== null) {
|
||||
if(lastOriginalSource !== original.source
|
||||
|| lastOriginalLine !== original.line
|
||||
|| lastOriginalColumn !== original.column
|
||||
|| lastOriginalName !== original.name) {
|
||||
map.addMapping({
|
||||
source: original.source,
|
||||
original: {
|
||||
line: original.line,
|
||||
column: original.column
|
||||
},
|
||||
generated: {
|
||||
line: generated.line,
|
||||
column: generated.column
|
||||
},
|
||||
name: original.name
|
||||
});
|
||||
}
|
||||
lastOriginalSource = original.source;
|
||||
lastOriginalLine = original.line;
|
||||
lastOriginalColumn = original.column;
|
||||
lastOriginalName = original.name;
|
||||
sourceMappingActive = true;
|
||||
} else if (sourceMappingActive) {
|
||||
map.addMapping({
|
||||
generated: {
|
||||
line: generated.line,
|
||||
column: generated.column
|
||||
}
|
||||
});
|
||||
lastOriginalSource = null;
|
||||
sourceMappingActive = false;
|
||||
}
|
||||
for (var idx = 0, length = chunk.length; idx < length; idx++) {
|
||||
if (chunk.charCodeAt(idx) === NEWLINE_CODE) {
|
||||
generated.line++;
|
||||
generated.column = 0;
|
||||
// Mappings end at eol
|
||||
if (idx + 1 === length) {
|
||||
lastOriginalSource = null;
|
||||
sourceMappingActive = false;
|
||||
} else if (sourceMappingActive) {
|
||||
map.addMapping({
|
||||
source: original.source,
|
||||
original: {
|
||||
line: original.line,
|
||||
column: original.column
|
||||
},
|
||||
generated: {
|
||||
line: generated.line,
|
||||
column: generated.column
|
||||
},
|
||||
name: original.name
|
||||
});
|
||||
}
|
||||
} else {
|
||||
generated.column++;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.walkSourceContents(function (sourceFile, sourceContent) {
|
||||
map.setSourceContent(sourceFile, sourceContent);
|
||||
});
|
||||
|
||||
return { code: generated.code, map: map };
|
||||
};
|
||||
|
||||
exports.SourceNode = SourceNode;
|
||||
+488
@@ -0,0 +1,488 @@
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
/*
|
||||
* Copyright 2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a helper function for getting values from parameter/options
|
||||
* objects.
|
||||
*
|
||||
* @param args The object we are extracting values from
|
||||
* @param name The name of the property we are getting.
|
||||
* @param defaultValue An optional value to return if the property is missing
|
||||
* from the object. If this is not specified and the property is missing, an
|
||||
* error will be thrown.
|
||||
*/
|
||||
function getArg(aArgs, aName, aDefaultValue) {
|
||||
if (aName in aArgs) {
|
||||
return aArgs[aName];
|
||||
} else if (arguments.length === 3) {
|
||||
return aDefaultValue;
|
||||
} else {
|
||||
throw new Error('"' + aName + '" is a required argument.');
|
||||
}
|
||||
}
|
||||
exports.getArg = getArg;
|
||||
|
||||
var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.-]*)(?::(\d+))?(.*)$/;
|
||||
var dataUrlRegexp = /^data:.+\,.+$/;
|
||||
|
||||
function urlParse(aUrl) {
|
||||
var match = aUrl.match(urlRegexp);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
scheme: match[1],
|
||||
auth: match[2],
|
||||
host: match[3],
|
||||
port: match[4],
|
||||
path: match[5]
|
||||
};
|
||||
}
|
||||
exports.urlParse = urlParse;
|
||||
|
||||
function urlGenerate(aParsedUrl) {
|
||||
var url = '';
|
||||
if (aParsedUrl.scheme) {
|
||||
url += aParsedUrl.scheme + ':';
|
||||
}
|
||||
url += '//';
|
||||
if (aParsedUrl.auth) {
|
||||
url += aParsedUrl.auth + '@';
|
||||
}
|
||||
if (aParsedUrl.host) {
|
||||
url += aParsedUrl.host;
|
||||
}
|
||||
if (aParsedUrl.port) {
|
||||
url += ":" + aParsedUrl.port
|
||||
}
|
||||
if (aParsedUrl.path) {
|
||||
url += aParsedUrl.path;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
exports.urlGenerate = urlGenerate;
|
||||
|
||||
/**
|
||||
* Normalizes a path, or the path portion of a URL:
|
||||
*
|
||||
* - Replaces consecutive slashes with one slash.
|
||||
* - Removes unnecessary '.' parts.
|
||||
* - Removes unnecessary '<dir>/..' parts.
|
||||
*
|
||||
* Based on code in the Node.js 'path' core module.
|
||||
*
|
||||
* @param aPath The path or url to normalize.
|
||||
*/
|
||||
function normalize(aPath) {
|
||||
var path = aPath;
|
||||
var url = urlParse(aPath);
|
||||
if (url) {
|
||||
if (!url.path) {
|
||||
return aPath;
|
||||
}
|
||||
path = url.path;
|
||||
}
|
||||
var isAbsolute = exports.isAbsolute(path);
|
||||
|
||||
var parts = path.split(/\/+/);
|
||||
for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
|
||||
part = parts[i];
|
||||
if (part === '.') {
|
||||
parts.splice(i, 1);
|
||||
} else if (part === '..') {
|
||||
up++;
|
||||
} else if (up > 0) {
|
||||
if (part === '') {
|
||||
// The first part is blank if the path is absolute. Trying to go
|
||||
// above the root is a no-op. Therefore we can remove all '..' parts
|
||||
// directly after the root.
|
||||
parts.splice(i + 1, up);
|
||||
up = 0;
|
||||
} else {
|
||||
parts.splice(i, 2);
|
||||
up--;
|
||||
}
|
||||
}
|
||||
}
|
||||
path = parts.join('/');
|
||||
|
||||
if (path === '') {
|
||||
path = isAbsolute ? '/' : '.';
|
||||
}
|
||||
|
||||
if (url) {
|
||||
url.path = path;
|
||||
return urlGenerate(url);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
exports.normalize = normalize;
|
||||
|
||||
/**
|
||||
* Joins two paths/URLs.
|
||||
*
|
||||
* @param aRoot The root path or URL.
|
||||
* @param aPath The path or URL to be joined with the root.
|
||||
*
|
||||
* - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
|
||||
* scheme-relative URL: Then the scheme of aRoot, if any, is prepended
|
||||
* first.
|
||||
* - Otherwise aPath is a path. If aRoot is a URL, then its path portion
|
||||
* is updated with the result and aRoot is returned. Otherwise the result
|
||||
* is returned.
|
||||
* - If aPath is absolute, the result is aPath.
|
||||
* - Otherwise the two paths are joined with a slash.
|
||||
* - Joining for example 'http://' and 'www.example.com' is also supported.
|
||||
*/
|
||||
function join(aRoot, aPath) {
|
||||
if (aRoot === "") {
|
||||
aRoot = ".";
|
||||
}
|
||||
if (aPath === "") {
|
||||
aPath = ".";
|
||||
}
|
||||
var aPathUrl = urlParse(aPath);
|
||||
var aRootUrl = urlParse(aRoot);
|
||||
if (aRootUrl) {
|
||||
aRoot = aRootUrl.path || '/';
|
||||
}
|
||||
|
||||
// `join(foo, '//www.example.org')`
|
||||
if (aPathUrl && !aPathUrl.scheme) {
|
||||
if (aRootUrl) {
|
||||
aPathUrl.scheme = aRootUrl.scheme;
|
||||
}
|
||||
return urlGenerate(aPathUrl);
|
||||
}
|
||||
|
||||
if (aPathUrl || aPath.match(dataUrlRegexp)) {
|
||||
return aPath;
|
||||
}
|
||||
|
||||
// `join('http://', 'www.example.com')`
|
||||
if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
|
||||
aRootUrl.host = aPath;
|
||||
return urlGenerate(aRootUrl);
|
||||
}
|
||||
|
||||
var joined = aPath.charAt(0) === '/'
|
||||
? aPath
|
||||
: normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
|
||||
|
||||
if (aRootUrl) {
|
||||
aRootUrl.path = joined;
|
||||
return urlGenerate(aRootUrl);
|
||||
}
|
||||
return joined;
|
||||
}
|
||||
exports.join = join;
|
||||
|
||||
exports.isAbsolute = function (aPath) {
|
||||
return aPath.charAt(0) === '/' || urlRegexp.test(aPath);
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a path relative to a URL or another path.
|
||||
*
|
||||
* @param aRoot The root path or URL.
|
||||
* @param aPath The path or URL to be made relative to aRoot.
|
||||
*/
|
||||
function relative(aRoot, aPath) {
|
||||
if (aRoot === "") {
|
||||
aRoot = ".";
|
||||
}
|
||||
|
||||
aRoot = aRoot.replace(/\/$/, '');
|
||||
|
||||
// It is possible for the path to be above the root. In this case, simply
|
||||
// checking whether the root is a prefix of the path won't work. Instead, we
|
||||
// need to remove components from the root one by one, until either we find
|
||||
// a prefix that fits, or we run out of components to remove.
|
||||
var level = 0;
|
||||
while (aPath.indexOf(aRoot + '/') !== 0) {
|
||||
var index = aRoot.lastIndexOf("/");
|
||||
if (index < 0) {
|
||||
return aPath;
|
||||
}
|
||||
|
||||
// If the only part of the root that is left is the scheme (i.e. http://,
|
||||
// file:///, etc.), one or more slashes (/), or simply nothing at all, we
|
||||
// have exhausted all components, so the path is not relative to the root.
|
||||
aRoot = aRoot.slice(0, index);
|
||||
if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
|
||||
return aPath;
|
||||
}
|
||||
|
||||
++level;
|
||||
}
|
||||
|
||||
// Make sure we add a "../" for each component we removed from the root.
|
||||
return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
|
||||
}
|
||||
exports.relative = relative;
|
||||
|
||||
var supportsNullProto = (function () {
|
||||
var obj = Object.create(null);
|
||||
return !('__proto__' in obj);
|
||||
}());
|
||||
|
||||
function identity (s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Because behavior goes wacky when you set `__proto__` on objects, we
|
||||
* have to prefix all the strings in our set with an arbitrary character.
|
||||
*
|
||||
* See https://github.com/mozilla/source-map/pull/31 and
|
||||
* https://github.com/mozilla/source-map/issues/30
|
||||
*
|
||||
* @param String aStr
|
||||
*/
|
||||
function toSetString(aStr) {
|
||||
if (isProtoString(aStr)) {
|
||||
return '$' + aStr;
|
||||
}
|
||||
|
||||
return aStr;
|
||||
}
|
||||
exports.toSetString = supportsNullProto ? identity : toSetString;
|
||||
|
||||
function fromSetString(aStr) {
|
||||
if (isProtoString(aStr)) {
|
||||
return aStr.slice(1);
|
||||
}
|
||||
|
||||
return aStr;
|
||||
}
|
||||
exports.fromSetString = supportsNullProto ? identity : fromSetString;
|
||||
|
||||
function isProtoString(s) {
|
||||
if (!s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var length = s.length;
|
||||
|
||||
if (length < 9 /* "__proto__".length */) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (s.charCodeAt(length - 1) !== 95 /* '_' */ ||
|
||||
s.charCodeAt(length - 2) !== 95 /* '_' */ ||
|
||||
s.charCodeAt(length - 3) !== 111 /* 'o' */ ||
|
||||
s.charCodeAt(length - 4) !== 116 /* 't' */ ||
|
||||
s.charCodeAt(length - 5) !== 111 /* 'o' */ ||
|
||||
s.charCodeAt(length - 6) !== 114 /* 'r' */ ||
|
||||
s.charCodeAt(length - 7) !== 112 /* 'p' */ ||
|
||||
s.charCodeAt(length - 8) !== 95 /* '_' */ ||
|
||||
s.charCodeAt(length - 9) !== 95 /* '_' */) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = length - 10; i >= 0; i--) {
|
||||
if (s.charCodeAt(i) !== 36 /* '$' */) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator between two mappings where the original positions are compared.
|
||||
*
|
||||
* Optionally pass in `true` as `onlyCompareGenerated` to consider two
|
||||
* mappings with the same original source/line/column, but different generated
|
||||
* line and column the same. Useful when searching for a mapping with a
|
||||
* stubbed out mapping.
|
||||
*/
|
||||
function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
|
||||
var cmp = strcmp(mappingA.source, mappingB.source);
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalLine - mappingB.originalLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalColumn - mappingB.originalColumn;
|
||||
if (cmp !== 0 || onlyCompareOriginal) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.generatedLine - mappingB.generatedLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
return strcmp(mappingA.name, mappingB.name);
|
||||
}
|
||||
exports.compareByOriginalPositions = compareByOriginalPositions;
|
||||
|
||||
/**
|
||||
* Comparator between two mappings with deflated source and name indices where
|
||||
* the generated positions are compared.
|
||||
*
|
||||
* Optionally pass in `true` as `onlyCompareGenerated` to consider two
|
||||
* mappings with the same generated line and column, but different
|
||||
* source/name/original line and column the same. Useful when searching for a
|
||||
* mapping with a stubbed out mapping.
|
||||
*/
|
||||
function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
|
||||
var cmp = mappingA.generatedLine - mappingB.generatedLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
|
||||
if (cmp !== 0 || onlyCompareGenerated) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = strcmp(mappingA.source, mappingB.source);
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalLine - mappingB.originalLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalColumn - mappingB.originalColumn;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
return strcmp(mappingA.name, mappingB.name);
|
||||
}
|
||||
exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
|
||||
|
||||
function strcmp(aStr1, aStr2) {
|
||||
if (aStr1 === aStr2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (aStr1 === null) {
|
||||
return 1; // aStr2 !== null
|
||||
}
|
||||
|
||||
if (aStr2 === null) {
|
||||
return -1; // aStr1 !== null
|
||||
}
|
||||
|
||||
if (aStr1 > aStr2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator between two mappings with inflated source and name strings where
|
||||
* the generated positions are compared.
|
||||
*/
|
||||
function compareByGeneratedPositionsInflated(mappingA, mappingB) {
|
||||
var cmp = mappingA.generatedLine - mappingB.generatedLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = strcmp(mappingA.source, mappingB.source);
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalLine - mappingB.originalLine;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
cmp = mappingA.originalColumn - mappingB.originalColumn;
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
return strcmp(mappingA.name, mappingB.name);
|
||||
}
|
||||
exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
|
||||
|
||||
/**
|
||||
* Strip any JSON XSSI avoidance prefix from the string (as documented
|
||||
* in the source maps specification), and then parse the string as
|
||||
* JSON.
|
||||
*/
|
||||
function parseSourceMapInput(str) {
|
||||
return JSON.parse(str.replace(/^\)]}'[^\n]*\n/, ''));
|
||||
}
|
||||
exports.parseSourceMapInput = parseSourceMapInput;
|
||||
|
||||
/**
|
||||
* Compute the URL of a source given the the source root, the source's
|
||||
* URL, and the source map's URL.
|
||||
*/
|
||||
function computeSourceURL(sourceRoot, sourceURL, sourceMapURL) {
|
||||
sourceURL = sourceURL || '';
|
||||
|
||||
if (sourceRoot) {
|
||||
// This follows what Chrome does.
|
||||
if (sourceRoot[sourceRoot.length - 1] !== '/' && sourceURL[0] !== '/') {
|
||||
sourceRoot += '/';
|
||||
}
|
||||
// The spec says:
|
||||
// Line 4: An optional source root, useful for relocating source
|
||||
// files on a server or removing repeated values in the
|
||||
// “sources” entry. This value is prepended to the individual
|
||||
// entries in the “source” field.
|
||||
sourceURL = sourceRoot + sourceURL;
|
||||
}
|
||||
|
||||
// Historically, SourceMapConsumer did not take the sourceMapURL as
|
||||
// a parameter. This mode is still somewhat supported, which is why
|
||||
// this code block is conditional. However, it's preferable to pass
|
||||
// the source map URL to SourceMapConsumer, so that this function
|
||||
// can implement the source URL resolution algorithm as outlined in
|
||||
// the spec. This block is basically the equivalent of:
|
||||
// new URL(sourceURL, sourceMapURL).toString()
|
||||
// ... except it avoids using URL, which wasn't available in the
|
||||
// older releases of node still supported by this library.
|
||||
//
|
||||
// The spec says:
|
||||
// If the sources are not absolute URLs after prepending of the
|
||||
// “sourceRoot”, the sources are resolved relative to the
|
||||
// SourceMap (like resolving script src in a html document).
|
||||
if (sourceMapURL) {
|
||||
var parsed = urlParse(sourceMapURL);
|
||||
if (!parsed) {
|
||||
throw new Error("sourceMapURL could not be parsed");
|
||||
}
|
||||
if (parsed.path) {
|
||||
// Strip the last path component, but keep the "/".
|
||||
var index = parsed.path.lastIndexOf('/');
|
||||
if (index >= 0) {
|
||||
parsed.path = parsed.path.substring(0, index + 1);
|
||||
}
|
||||
}
|
||||
sourceURL = join(urlGenerate(parsed), sourceURL);
|
||||
}
|
||||
|
||||
return normalize(sourceURL);
|
||||
}
|
||||
exports.computeSourceURL = computeSourceURL;
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "source-map",
|
||||
"description": "Generates and consumes source maps",
|
||||
"version": "0.6.1",
|
||||
"homepage": "https://github.com/mozilla/source-map",
|
||||
"author": "Nick Fitzgerald <nfitzgerald@mozilla.com>",
|
||||
"contributors": [
|
||||
"Tobias Koppers <tobias.koppers@googlemail.com>",
|
||||
"Duncan Beevers <duncan@dweebd.com>",
|
||||
"Stephen Crane <scrane@mozilla.com>",
|
||||
"Ryan Seddon <seddon.ryan@gmail.com>",
|
||||
"Miles Elam <miles.elam@deem.com>",
|
||||
"Mihai Bazon <mihai.bazon@gmail.com>",
|
||||
"Michael Ficarra <github.public.email@michael.ficarra.me>",
|
||||
"Todd Wolfson <todd@twolfson.com>",
|
||||
"Alexander Solovyov <alexander@solovyov.net>",
|
||||
"Felix Gnass <fgnass@gmail.com>",
|
||||
"Conrad Irwin <conrad.irwin@gmail.com>",
|
||||
"usrbincc <usrbincc@yahoo.com>",
|
||||
"David Glasser <glasser@davidglasser.net>",
|
||||
"Chase Douglas <chase@newrelic.com>",
|
||||
"Evan Wallace <evan.exe@gmail.com>",
|
||||
"Heather Arthur <fayearthur@gmail.com>",
|
||||
"Hugh Kennedy <hughskennedy@gmail.com>",
|
||||
"David Glasser <glasser@davidglasser.net>",
|
||||
"Simon Lydell <simon.lydell@gmail.com>",
|
||||
"Jmeas Smith <jellyes2@gmail.com>",
|
||||
"Michael Z Goddard <mzgoddard@gmail.com>",
|
||||
"azu <azu@users.noreply.github.com>",
|
||||
"John Gozde <john@gozde.ca>",
|
||||
"Adam Kirkton <akirkton@truefitinnovation.com>",
|
||||
"Chris Montgomery <christopher.montgomery@dowjones.com>",
|
||||
"J. Ryan Stinnett <jryans@gmail.com>",
|
||||
"Jack Herrington <jherrington@walmartlabs.com>",
|
||||
"Chris Truter <jeffpalentine@gmail.com>",
|
||||
"Daniel Espeset <daniel@danielespeset.com>",
|
||||
"Jamie Wong <jamie.lf.wong@gmail.com>",
|
||||
"Eddy Bruël <ejpbruel@mozilla.com>",
|
||||
"Hawken Rives <hawkrives@gmail.com>",
|
||||
"Gilad Peleg <giladp007@gmail.com>",
|
||||
"djchie <djchie.dev@gmail.com>",
|
||||
"Gary Ye <garysye@gmail.com>",
|
||||
"Nicolas Lalevée <nicolas.lalevee@hibnet.org>"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/mozilla/source-map.git"
|
||||
},
|
||||
"main": "./source-map.js",
|
||||
"files": [
|
||||
"source-map.js",
|
||||
"source-map.d.ts",
|
||||
"lib/",
|
||||
"dist/source-map.debug.js",
|
||||
"dist/source-map.js",
|
||||
"dist/source-map.min.js",
|
||||
"dist/source-map.min.js.map"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"license": "BSD-3-Clause",
|
||||
"scripts": {
|
||||
"test": "npm run build && node test/run-tests.js",
|
||||
"build": "webpack --color",
|
||||
"toc": "doctoc --title '## Table of Contents' README.md && doctoc --title '## Table of Contents' CONTRIBUTING.md"
|
||||
},
|
||||
"devDependencies": {
|
||||
"doctoc": "^0.15.0",
|
||||
"webpack": "^1.12.0"
|
||||
},
|
||||
"typings": "source-map"
|
||||
}
|
||||
Generated
Vendored
+98
@@ -0,0 +1,98 @@
|
||||
export interface StartOfSourceMap {
|
||||
file?: string;
|
||||
sourceRoot?: string;
|
||||
}
|
||||
|
||||
export interface RawSourceMap extends StartOfSourceMap {
|
||||
version: string;
|
||||
sources: string[];
|
||||
names: string[];
|
||||
sourcesContent?: string[];
|
||||
mappings: string;
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
line: number;
|
||||
column: number;
|
||||
}
|
||||
|
||||
export interface LineRange extends Position {
|
||||
lastColumn: number;
|
||||
}
|
||||
|
||||
export interface FindPosition extends Position {
|
||||
// SourceMapConsumer.GREATEST_LOWER_BOUND or SourceMapConsumer.LEAST_UPPER_BOUND
|
||||
bias?: number;
|
||||
}
|
||||
|
||||
export interface SourceFindPosition extends FindPosition {
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface MappedPosition extends Position {
|
||||
source: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface MappingItem {
|
||||
source: string;
|
||||
generatedLine: number;
|
||||
generatedColumn: number;
|
||||
originalLine: number;
|
||||
originalColumn: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class SourceMapConsumer {
|
||||
static GENERATED_ORDER: number;
|
||||
static ORIGINAL_ORDER: number;
|
||||
|
||||
static GREATEST_LOWER_BOUND: number;
|
||||
static LEAST_UPPER_BOUND: number;
|
||||
|
||||
constructor(rawSourceMap: RawSourceMap);
|
||||
computeColumnSpans(): void;
|
||||
originalPositionFor(generatedPosition: FindPosition): MappedPosition;
|
||||
generatedPositionFor(originalPosition: SourceFindPosition): LineRange;
|
||||
allGeneratedPositionsFor(originalPosition: MappedPosition): Position[];
|
||||
hasContentsOfAllSources(): boolean;
|
||||
sourceContentFor(source: string, returnNullOnMissing?: boolean): string;
|
||||
eachMapping(callback: (mapping: MappingItem) => void, context?: any, order?: number): void;
|
||||
}
|
||||
|
||||
export interface Mapping {
|
||||
generated: Position;
|
||||
original: Position;
|
||||
source: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export class SourceMapGenerator {
|
||||
constructor(startOfSourceMap?: StartOfSourceMap);
|
||||
static fromSourceMap(sourceMapConsumer: SourceMapConsumer): SourceMapGenerator;
|
||||
addMapping(mapping: Mapping): void;
|
||||
setSourceContent(sourceFile: string, sourceContent: string): void;
|
||||
applySourceMap(sourceMapConsumer: SourceMapConsumer, sourceFile?: string, sourceMapPath?: string): void;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
export interface CodeWithSourceMap {
|
||||
code: string;
|
||||
map: SourceMapGenerator;
|
||||
}
|
||||
|
||||
export class SourceNode {
|
||||
constructor();
|
||||
constructor(line: number, column: number, source: string);
|
||||
constructor(line: number, column: number, source: string, chunk?: string, name?: string);
|
||||
static fromStringWithSourceMap(code: string, sourceMapConsumer: SourceMapConsumer, relativePath?: string): SourceNode;
|
||||
add(chunk: string): void;
|
||||
prepend(chunk: string): void;
|
||||
setSourceContent(sourceFile: string, sourceContent: string): void;
|
||||
walk(fn: (chunk: string, mapping: MappedPosition) => void): void;
|
||||
walkSourceContents(fn: (file: string, content: string) => void): void;
|
||||
join(sep: string): SourceNode;
|
||||
replaceRight(pattern: string, replacement: string): SourceNode;
|
||||
toString(): string;
|
||||
toStringWithSourceMap(startOfSourceMap?: StartOfSourceMap): CodeWithSourceMap;
|
||||
}
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright 2009-2011 Mozilla Foundation and contributors
|
||||
* Licensed under the New BSD license. See LICENSE.txt or:
|
||||
* http://opensource.org/licenses/BSD-3-Clause
|
||||
*/
|
||||
exports.SourceMapGenerator = require('./lib/source-map-generator').SourceMapGenerator;
|
||||
exports.SourceMapConsumer = require('./lib/source-map-consumer').SourceMapConsumer;
|
||||
exports.SourceNode = require('./lib/source-node').SourceNode;
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"name": "terser-webpack-plugin",
|
||||
"version": "5.3.1",
|
||||
"description": "Terser plugin for webpack",
|
||||
"license": "MIT",
|
||||
"repository": "webpack-contrib/terser-webpack-plugin",
|
||||
"author": "webpack Contrib Team",
|
||||
"homepage": "https://github.com/webpack-contrib/terser-webpack-plugin",
|
||||
"bugs": "https://github.com/webpack-contrib/terser-webpack-plugin/issues",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "del-cli dist types",
|
||||
"prebuild": "npm run clean",
|
||||
"build:types": "tsc --declaration --emitDeclarationOnly --outDir types && prettier \"types/**/*.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",
|
||||
"lint:prettier": "prettier --list-different .",
|
||||
"lint:js": "eslint --cache .",
|
||||
"lint:types": "tsc --pretty --noEmit",
|
||||
"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 -- --collectCoverageFrom=\"src/**/*.js\" --coverage",
|
||||
"pretest": "npm run lint",
|
||||
"test": "npm run test:coverage",
|
||||
"prepare": "husky install && npm run build",
|
||||
"release": "standard-version"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"types"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"webpack": "^5.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/core": {
|
||||
"optional": true
|
||||
},
|
||||
"uglify-js": {
|
||||
"optional": true
|
||||
},
|
||||
"esbuild": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"jest-worker": "^27.4.5",
|
||||
"schema-utils": "^3.1.1",
|
||||
"serialize-javascript": "^6.0.0",
|
||||
"source-map": "^0.6.1",
|
||||
"terser": "^5.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.16.7",
|
||||
"@babel/core": "^7.16.7",
|
||||
"@babel/preset-env": "^7.16.7",
|
||||
"@commitlint/cli": "^15.0.0",
|
||||
"@commitlint/config-conventional": "^15.0.0",
|
||||
"@swc/core": "^1.2.126",
|
||||
"@types/serialize-javascript": "^5.0.2",
|
||||
"@types/uglify-js": "^3.13.1",
|
||||
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
|
||||
"babel-jest": "^27.4.5",
|
||||
"copy-webpack-plugin": "^9.0.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"del": "^6.0.0",
|
||||
"del-cli": "^3.0.1",
|
||||
"esbuild": "^0.14.10",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"file-loader": "^6.2.0",
|
||||
"husky": "^7.0.2",
|
||||
"jest": "^27.4.5",
|
||||
"lint-staged": "^11.0.1",
|
||||
"memfs": "^3.4.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.3.2",
|
||||
"standard-version": "^9.3.1",
|
||||
"typescript": "^4.5.4",
|
||||
"uglify-js": "^3.14.5",
|
||||
"webpack": "^5.48.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"worker-loader": "^3.0.8"
|
||||
},
|
||||
"keywords": [
|
||||
"uglify",
|
||||
"uglify-js",
|
||||
"uglify-es",
|
||||
"terser",
|
||||
"webpack",
|
||||
"webpack-plugin",
|
||||
"minification",
|
||||
"compress",
|
||||
"compressor",
|
||||
"min",
|
||||
"minification",
|
||||
"minifier",
|
||||
"minify",
|
||||
"optimize",
|
||||
"optimizer"
|
||||
]
|
||||
}
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
export = TerserPlugin;
|
||||
/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
|
||||
/** @typedef {import("webpack").Compiler} Compiler */
|
||||
/** @typedef {import("webpack").Compilation} Compilation */
|
||||
/** @typedef {import("webpack").WebpackError} WebpackError */
|
||||
/** @typedef {import("webpack").Asset} Asset */
|
||||
/** @typedef {import("./utils.js").TerserECMA} TerserECMA */
|
||||
/** @typedef {import("./utils.js").TerserOptions} TerserOptions */
|
||||
/** @typedef {import("jest-worker").Worker} JestWorker */
|
||||
/** @typedef {import("source-map").RawSourceMap} RawSourceMap */
|
||||
/** @typedef {RegExp | string} Rule */
|
||||
/** @typedef {Rule[] | Rule} Rules */
|
||||
/**
|
||||
* @callback ExtractCommentsFunction
|
||||
* @param {any} astNode
|
||||
* @param {{ value: string, type: 'comment1' | 'comment2' | 'comment3' | 'comment4', pos: number, line: number, col: number }} comment
|
||||
* @returns {boolean}
|
||||
*/
|
||||
/**
|
||||
* @typedef {boolean | 'all' | 'some' | RegExp | ExtractCommentsFunction} ExtractCommentsCondition
|
||||
*/
|
||||
/**
|
||||
* @typedef {string | ((fileData: any) => string)} ExtractCommentsFilename
|
||||
*/
|
||||
/**
|
||||
* @typedef {boolean | string | ((commentsFile: string) => string)} ExtractCommentsBanner
|
||||
*/
|
||||
/**
|
||||
* @typedef {Object} ExtractCommentsObject
|
||||
* @property {ExtractCommentsCondition} [condition]
|
||||
* @property {ExtractCommentsFilename} [filename]
|
||||
* @property {ExtractCommentsBanner} [banner]
|
||||
*/
|
||||
/**
|
||||
* @typedef {ExtractCommentsCondition | ExtractCommentsObject} ExtractCommentsOptions
|
||||
*/
|
||||
/**
|
||||
* @typedef {Object} MinimizedResult
|
||||
* @property {string} code
|
||||
* @property {RawSourceMap} [map]
|
||||
* @property {Array<Error | string>} [errors]
|
||||
* @property {Array<Error | string>} [warnings]
|
||||
* @property {Array<string>} [extractedComments]
|
||||
*/
|
||||
/**
|
||||
* @typedef {{ [file: string]: string }} Input
|
||||
*/
|
||||
/**
|
||||
* @typedef {{ [key: string]: any }} CustomOptions
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {T extends infer U ? U : CustomOptions} InferDefaultType
|
||||
*/
|
||||
/**
|
||||
* @typedef {Object} PredefinedOptions
|
||||
* @property {boolean} [module]
|
||||
* @property {TerserECMA} [ecma]
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {PredefinedOptions & InferDefaultType<T>} MinimizerOptions
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @callback BasicMinimizerImplementation
|
||||
* @param {Input} input
|
||||
* @param {RawSourceMap | undefined} sourceMap
|
||||
* @param {MinimizerOptions<T>} minifyOptions
|
||||
* @param {ExtractCommentsOptions | undefined} extractComments
|
||||
* @returns {Promise<MinimizedResult>}
|
||||
*/
|
||||
/**
|
||||
* @typedef {object} MinimizeFunctionHelpers
|
||||
* @property {() => string | undefined} [getMinimizerVersion]
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {BasicMinimizerImplementation<T> & MinimizeFunctionHelpers} MinimizerImplementation
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Object} InternalOptions
|
||||
* @property {string} name
|
||||
* @property {string} input
|
||||
* @property {RawSourceMap | undefined} inputSourceMap
|
||||
* @property {ExtractCommentsOptions | undefined} extractComments
|
||||
* @property {{ implementation: MinimizerImplementation<T>, options: MinimizerOptions<T> }} minimizer
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {JestWorker & { transform: (options: string) => MinimizedResult, minify: (options: InternalOptions<T>) => MinimizedResult }} MinimizerWorker
|
||||
*/
|
||||
/**
|
||||
* @typedef {undefined | boolean | number} Parallel
|
||||
*/
|
||||
/**
|
||||
* @typedef {Object} BasePluginOptions
|
||||
* @property {Rules} [test]
|
||||
* @property {Rules} [include]
|
||||
* @property {Rules} [exclude]
|
||||
* @property {ExtractCommentsOptions} [extractComments]
|
||||
* @property {Parallel} [parallel]
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {T extends TerserOptions ? { minify?: MinimizerImplementation<T> | undefined, terserOptions?: MinimizerOptions<T> | undefined } : { minify: MinimizerImplementation<T>, terserOptions?: MinimizerOptions<T> | undefined }} DefinedDefaultMinimizerAndOptions
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {BasePluginOptions & { minimizer: { implementation: MinimizerImplementation<T>, options: MinimizerOptions<T> } }} InternalPluginOptions
|
||||
*/
|
||||
/**
|
||||
* @template [T=TerserOptions]
|
||||
*/
|
||||
declare class TerserPlugin<T = import("terser").MinifyOptions> {
|
||||
/**
|
||||
* @private
|
||||
* @param {any} input
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private static isSourceMap;
|
||||
/**
|
||||
* @private
|
||||
* @param {unknown} warning
|
||||
* @param {string} file
|
||||
* @returns {Error}
|
||||
*/
|
||||
private static buildWarning;
|
||||
/**
|
||||
* @private
|
||||
* @param {any} error
|
||||
* @param {string} file
|
||||
* @param {SourceMapConsumer} [sourceMap]
|
||||
* @param {Compilation["requestShortener"]} [requestShortener]
|
||||
* @returns {Error}
|
||||
*/
|
||||
private static buildError;
|
||||
/**
|
||||
* @private
|
||||
* @param {Parallel} parallel
|
||||
* @returns {number}
|
||||
*/
|
||||
private static getAvailableNumberOfCores;
|
||||
/**
|
||||
* @private
|
||||
* @param {any} environment
|
||||
* @returns {TerserECMA}
|
||||
*/
|
||||
private static getEcmaVersion;
|
||||
/**
|
||||
* @param {BasePluginOptions & DefinedDefaultMinimizerAndOptions<T>} [options]
|
||||
*/
|
||||
constructor(
|
||||
options?:
|
||||
| (BasePluginOptions & DefinedDefaultMinimizerAndOptions<T>)
|
||||
| undefined
|
||||
);
|
||||
/**
|
||||
* @private
|
||||
* @type {InternalPluginOptions<T>}
|
||||
*/
|
||||
private options;
|
||||
/**
|
||||
* @private
|
||||
* @param {Compiler} compiler
|
||||
* @param {Compilation} compilation
|
||||
* @param {Record<string, import("webpack").sources.Source>} assets
|
||||
* @param {{availableNumberOfCores: number}} optimizeOptions
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private optimize;
|
||||
/**
|
||||
* @param {Compiler} compiler
|
||||
* @returns {void}
|
||||
*/
|
||||
apply(compiler: Compiler): void;
|
||||
}
|
||||
declare namespace TerserPlugin {
|
||||
export {
|
||||
terserMinify,
|
||||
uglifyJsMinify,
|
||||
swcMinify,
|
||||
esbuildMinify,
|
||||
Schema,
|
||||
Compiler,
|
||||
Compilation,
|
||||
WebpackError,
|
||||
Asset,
|
||||
TerserECMA,
|
||||
TerserOptions,
|
||||
JestWorker,
|
||||
RawSourceMap,
|
||||
Rule,
|
||||
Rules,
|
||||
ExtractCommentsFunction,
|
||||
ExtractCommentsCondition,
|
||||
ExtractCommentsFilename,
|
||||
ExtractCommentsBanner,
|
||||
ExtractCommentsObject,
|
||||
ExtractCommentsOptions,
|
||||
MinimizedResult,
|
||||
Input,
|
||||
CustomOptions,
|
||||
InferDefaultType,
|
||||
PredefinedOptions,
|
||||
MinimizerOptions,
|
||||
BasicMinimizerImplementation,
|
||||
MinimizeFunctionHelpers,
|
||||
MinimizerImplementation,
|
||||
InternalOptions,
|
||||
MinimizerWorker,
|
||||
Parallel,
|
||||
BasePluginOptions,
|
||||
DefinedDefaultMinimizerAndOptions,
|
||||
InternalPluginOptions,
|
||||
};
|
||||
}
|
||||
type Compiler = import("webpack").Compiler;
|
||||
type BasePluginOptions = {
|
||||
test?: Rules | undefined;
|
||||
include?: Rules | undefined;
|
||||
exclude?: Rules | undefined;
|
||||
extractComments?: ExtractCommentsOptions | undefined;
|
||||
parallel?: Parallel;
|
||||
};
|
||||
type DefinedDefaultMinimizerAndOptions<T> = T extends TerserOptions
|
||||
? {
|
||||
minify?: MinimizerImplementation<T> | undefined;
|
||||
terserOptions?: MinimizerOptions<T> | undefined;
|
||||
}
|
||||
: {
|
||||
minify: MinimizerImplementation<T>;
|
||||
terserOptions?: MinimizerOptions<T> | undefined;
|
||||
};
|
||||
import { terserMinify } from "./utils";
|
||||
import { uglifyJsMinify } from "./utils";
|
||||
import { swcMinify } from "./utils";
|
||||
import { esbuildMinify } from "./utils";
|
||||
type Schema = import("schema-utils/declarations/validate").Schema;
|
||||
type Compilation = import("webpack").Compilation;
|
||||
type WebpackError = import("webpack").WebpackError;
|
||||
type Asset = import("webpack").Asset;
|
||||
type TerserECMA = import("./utils.js").TerserECMA;
|
||||
type TerserOptions = import("./utils.js").TerserOptions;
|
||||
type JestWorker = import("jest-worker").Worker;
|
||||
type RawSourceMap = import("source-map").RawSourceMap;
|
||||
type Rule = RegExp | string;
|
||||
type Rules = Rule[] | Rule;
|
||||
type ExtractCommentsFunction = (
|
||||
astNode: any,
|
||||
comment: {
|
||||
value: string;
|
||||
type: "comment1" | "comment2" | "comment3" | "comment4";
|
||||
pos: number;
|
||||
line: number;
|
||||
col: number;
|
||||
}
|
||||
) => boolean;
|
||||
type ExtractCommentsCondition =
|
||||
| boolean
|
||||
| "all"
|
||||
| "some"
|
||||
| RegExp
|
||||
| ExtractCommentsFunction;
|
||||
type ExtractCommentsFilename = string | ((fileData: any) => string);
|
||||
type ExtractCommentsBanner =
|
||||
| string
|
||||
| boolean
|
||||
| ((commentsFile: string) => string);
|
||||
type ExtractCommentsObject = {
|
||||
condition?: ExtractCommentsCondition | undefined;
|
||||
filename?: ExtractCommentsFilename | undefined;
|
||||
banner?: ExtractCommentsBanner | undefined;
|
||||
};
|
||||
type ExtractCommentsOptions = ExtractCommentsCondition | ExtractCommentsObject;
|
||||
type MinimizedResult = {
|
||||
code: string;
|
||||
map?: import("source-map").RawSourceMap | undefined;
|
||||
errors?: (string | Error)[] | undefined;
|
||||
warnings?: (string | Error)[] | undefined;
|
||||
extractedComments?: string[] | undefined;
|
||||
};
|
||||
type Input = {
|
||||
[file: string]: string;
|
||||
};
|
||||
type CustomOptions = {
|
||||
[key: string]: any;
|
||||
};
|
||||
type InferDefaultType<T> = T extends infer U ? U : CustomOptions;
|
||||
type PredefinedOptions = {
|
||||
module?: boolean | undefined;
|
||||
ecma?: import("terser").ECMA | undefined;
|
||||
};
|
||||
type MinimizerOptions<T> = PredefinedOptions & InferDefaultType<T>;
|
||||
type BasicMinimizerImplementation<T> = (
|
||||
input: Input,
|
||||
sourceMap: RawSourceMap | undefined,
|
||||
minifyOptions: MinimizerOptions<T>,
|
||||
extractComments: ExtractCommentsOptions | undefined
|
||||
) => Promise<MinimizedResult>;
|
||||
type MinimizeFunctionHelpers = {
|
||||
getMinimizerVersion?: (() => string | undefined) | undefined;
|
||||
};
|
||||
type MinimizerImplementation<T> = BasicMinimizerImplementation<T> &
|
||||
MinimizeFunctionHelpers;
|
||||
type InternalOptions<T> = {
|
||||
name: string;
|
||||
input: string;
|
||||
inputSourceMap: RawSourceMap | undefined;
|
||||
extractComments: ExtractCommentsOptions | undefined;
|
||||
minimizer: {
|
||||
implementation: MinimizerImplementation<T>;
|
||||
options: MinimizerOptions<T>;
|
||||
};
|
||||
};
|
||||
type MinimizerWorker<T> = Worker & {
|
||||
transform: (options: string) => MinimizedResult;
|
||||
minify: (options: InternalOptions<T>) => MinimizedResult;
|
||||
};
|
||||
type Parallel = undefined | boolean | number;
|
||||
type InternalPluginOptions<T> = BasePluginOptions & {
|
||||
minimizer: {
|
||||
implementation: MinimizerImplementation<T>;
|
||||
options: MinimizerOptions<T>;
|
||||
};
|
||||
};
|
||||
import { minify } from "./minify";
|
||||
import { Worker } from "jest-worker";
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
export type MinimizedResult = import("./index.js").MinimizedResult;
|
||||
export type CustomOptions = import("./index.js").CustomOptions;
|
||||
/** @typedef {import("./index.js").MinimizedResult} MinimizedResult */
|
||||
/** @typedef {import("./index.js").CustomOptions} CustomOptions */
|
||||
/**
|
||||
* @template T
|
||||
* @param {import("./index.js").InternalOptions<T>} options
|
||||
* @returns {Promise<MinimizedResult>}
|
||||
*/
|
||||
export function minify<T>(
|
||||
options: import("./index.js").InternalOptions<T>
|
||||
): Promise<MinimizedResult>;
|
||||
/**
|
||||
* @param {string} options
|
||||
* @returns {Promise<MinimizedResult>}
|
||||
*/
|
||||
export function transform(options: string): Promise<MinimizedResult>;
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
export type Task<T> = () => Promise<T>;
|
||||
export type RawSourceMap = import("source-map").RawSourceMap;
|
||||
export type TerserFormatOptions = import("terser").FormatOptions;
|
||||
export type TerserOptions = import("terser").MinifyOptions;
|
||||
export type TerserECMA = import("terser").ECMA;
|
||||
export type ExtractCommentsOptions =
|
||||
import("./index.js").ExtractCommentsOptions;
|
||||
export type ExtractCommentsFunction =
|
||||
import("./index.js").ExtractCommentsFunction;
|
||||
export type ExtractCommentsCondition =
|
||||
import("./index.js").ExtractCommentsCondition;
|
||||
export type Input = import("./index.js").Input;
|
||||
export type MinimizedResult = import("./index.js").MinimizedResult;
|
||||
export type PredefinedOptions = import("./index.js").PredefinedOptions;
|
||||
export type CustomOptions = import("./index.js").CustomOptions;
|
||||
export type ExtractedComments = Array<string>;
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {() => Promise<T>} Task
|
||||
*/
|
||||
/**
|
||||
* Run tasks with limited concurency.
|
||||
* @template T
|
||||
* @param {number} limit - Limit of tasks that run at once.
|
||||
* @param {Task<T>[]} tasks - List of tasks to run.
|
||||
* @returns {Promise<T[]>} A promise that fulfills to an array of the results
|
||||
*/
|
||||
export function throttleAll<T>(limit: number, tasks: Task<T>[]): Promise<T[]>;
|
||||
/**
|
||||
* @param {Input} input
|
||||
* @param {RawSourceMap | undefined} sourceMap
|
||||
* @param {PredefinedOptions & CustomOptions} minimizerOptions
|
||||
* @param {ExtractCommentsOptions | undefined} extractComments
|
||||
* @return {Promise<MinimizedResult>}
|
||||
*/
|
||||
export function terserMinify(
|
||||
input: Input,
|
||||
sourceMap: RawSourceMap | undefined,
|
||||
minimizerOptions: PredefinedOptions & CustomOptions,
|
||||
extractComments: ExtractCommentsOptions | undefined
|
||||
): Promise<MinimizedResult>;
|
||||
export namespace terserMinify {
|
||||
/**
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
function getMinimizerVersion(): string | undefined;
|
||||
}
|
||||
/**
|
||||
* @param {Input} input
|
||||
* @param {RawSourceMap | undefined} sourceMap
|
||||
* @param {PredefinedOptions & CustomOptions} minimizerOptions
|
||||
* @param {ExtractCommentsOptions | undefined} extractComments
|
||||
* @return {Promise<MinimizedResult>}
|
||||
*/
|
||||
export function uglifyJsMinify(
|
||||
input: Input,
|
||||
sourceMap: RawSourceMap | undefined,
|
||||
minimizerOptions: PredefinedOptions & CustomOptions,
|
||||
extractComments: ExtractCommentsOptions | undefined
|
||||
): Promise<MinimizedResult>;
|
||||
export namespace uglifyJsMinify {
|
||||
/**
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
function getMinimizerVersion(): string | undefined;
|
||||
}
|
||||
/**
|
||||
* @param {Input} input
|
||||
* @param {RawSourceMap | undefined} sourceMap
|
||||
* @param {PredefinedOptions & CustomOptions} minimizerOptions
|
||||
* @return {Promise<MinimizedResult>}
|
||||
*/
|
||||
export function swcMinify(
|
||||
input: Input,
|
||||
sourceMap: RawSourceMap | undefined,
|
||||
minimizerOptions: PredefinedOptions & CustomOptions
|
||||
): Promise<MinimizedResult>;
|
||||
export namespace swcMinify {
|
||||
/**
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
function getMinimizerVersion(): string | undefined;
|
||||
}
|
||||
/**
|
||||
* @param {Input} input
|
||||
* @param {RawSourceMap | undefined} sourceMap
|
||||
* @param {PredefinedOptions & CustomOptions} minimizerOptions
|
||||
* @return {Promise<MinimizedResult>}
|
||||
*/
|
||||
export function esbuildMinify(
|
||||
input: Input,
|
||||
sourceMap: RawSourceMap | undefined,
|
||||
minimizerOptions: PredefinedOptions & CustomOptions
|
||||
): Promise<MinimizedResult>;
|
||||
export namespace esbuildMinify {
|
||||
/**
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
function getMinimizerVersion(): string | undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user