Version 1.0.2 mit node_modules Verzeichnis

This commit is contained in:
ISA
2024-10-02 07:58:24 +02:00
parent f353a06b1b
commit 62b6e55a0a
68228 changed files with 4548477 additions and 651 deletions

20
node_modules/terser-webpack-plugin/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,20 @@
Copyright JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

934
node_modules/terser-webpack-plugin/README.md generated vendored Normal file
View File

@@ -0,0 +1,934 @@
<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]
[![tests][tests]][tests-url]
[![cover][cover]][cover-url]
[![discussion][discussion]][discussion-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
```
or
```console
yarn add -D terser-webpack-plugin
```
or
```console
pnpm add -D terser-webpack-plugin
```
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
- **[`test`](#test)**
- **[`include`](#include)**
- **[`exclude`](#exclude)**
- **[`parallel`](#parallel)**
- **[`minify`](#minify)**
- **[`terserOptions`](#terseroptions)**
- **[`extractComments`](#extractcomments)**
### `test`
Type:
```ts
type test = 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:
```ts
type include = 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:
```ts
type exclude = 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:
```ts
type parallel = boolean | number;
```
Default: `true`
Use multi-process parallel running to improve the build speed.
Default number of concurrent runs: `os.cpus().length - 1`.
> **Note**
>
> Parallelization can speedup your build significantly and is therefore **highly recommended**.
> **Warning**
>
> 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:
```ts
type minify = (
input: {
[file: string]: string;
},
sourceMap: import("@jridgewell/trace-mapping").SourceMapInput | undefined,
minifyOptions: {
module?: boolean | undefined;
ecma?: import("terser").ECMA | undefined;
},
extractComments:
| boolean
| "all"
| "some"
| RegExp
| ((
astNode: any,
comment: {
value: string;
type: "comment1" | "comment2" | "comment3" | "comment4";
pos: number;
line: number;
col: number;
}
) => boolean)
| {
condition?:
| boolean
| "all"
| "some"
| RegExp
| ((
astNode: any,
comment: {
value: string;
type: "comment1" | "comment2" | "comment3" | "comment4";
pos: number;
line: number;
col: number;
}
) => boolean)
| undefined;
filename?: string | ((fileData: any) => string) | undefined;
banner?:
| string
| boolean
| ((commentsFile: string) => string)
| undefined;
}
| undefined
) => Promise<{
code: string;
map?: import("@jridgewell/trace-mapping").SourceMapInput | undefined;
errors?: (string | Error)[] | undefined;
warnings?: (string | Error)[] | undefined;
extractedComments?: string[] | undefined;
}>;
```
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.
> **Warning**
>
> **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:
```ts
type terserOptions = {
compress?: boolean | CompressOptions;
ecma?: ECMA;
enclose?: boolean | string;
ie8?: boolean;
keep_classnames?: boolean | RegExp;
keep_fnames?: boolean | RegExp;
mangle?: boolean | MangleOptions;
module?: boolean;
nameCache?: object;
format?: FormatOptions;
/** @deprecated */
output?: FormatOptions;
parse?: ParseOptions;
safari10?: boolean;
sourceMap?: boolean | SourceMapOptions;
toplevel?: boolean;
};
```
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:
```ts
type extractComments =
| boolean
| string
| RegExp
| ((
astNode: any,
comment: {
value: string;
type: "comment1" | "comment2" | "comment3" | "comment4";
pos: number;
line: number;
col: number;
}
) => boolean)
| {
condition?:
| boolean
| "all"
| "some"
| RegExp
| ((
astNode: any,
comment: {
value: string;
type: "comment1" | "comment2" | "comment3" | "comment4";
pos: number;
line: number;
col: number;
}
) => boolean)
| undefined;
filename?: string | ((fileData: any) => string) | undefined;
banner?:
| string
| boolean
| ((commentsFile: string) => string)
| undefined;
};
```
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`
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:
```ts
type condition =
| boolean
| "all"
| "some"
| RegExp
| ((
astNode: any,
comment: {
value: string;
type: "comment1" | "comment2" | "comment3" | "comment4";
pos: number;
line: number;
col: number;
}
) => boolean)
| undefined;
```
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:
```ts
type filename = string | ((fileData: any) => string) | undefined;
```
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.
> **Warning**
>
> 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:
```ts
type banner = string | boolean | ((commentsFile: string) => string) | undefined;
```
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.
> **Warning**
>
> 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.
> **Warning**
>
> 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
[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
[discussion]: https://img.shields.io/github/discussions/webpack/webpack
[discussion-url]: https://github.com/webpack/webpack/discussions
[size]: https://packagephobia.now.sh/badge?p=terser-webpack-plugin
[size-url]: https://packagephobia.now.sh/result?p=terser-webpack-plugin

692
node_modules/terser-webpack-plugin/dist/index.js generated vendored Normal file
View File

@@ -0,0 +1,692 @@
"use strict";
const path = require("path");
const os = require("os");
const {
validate
} = require("schema-utils");
const {
throttleAll,
memoize,
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("@jridgewell/trace-mapping").SourceMapInput} SourceMapInput */
/** @typedef {import("@jridgewell/trace-mapping").TraceMap} TraceMap */
/** @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 {SourceMapInput} [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 {SourceMapInput | 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 {SourceMapInput | 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
*/
const getTraceMapping = memoize(() =>
// eslint-disable-next-line global-require
require("@jridgewell/trace-mapping"));
const getSerializeJavascript = memoize(() =>
// eslint-disable-next-line global-require
require("serialize-javascript"));
/**
* @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 TraceMap(...options)`
// https://github.com/jridgewell/trace-mapping#usage
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 {TraceMap} [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 && getTraceMapping().originalPositionFor(sourceMap, {
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;
}
// eslint-disable-next-line global-require
const {
Worker
} = require("jest-worker");
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 {SourceMapInput | 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 {SourceMapInput} */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(getSerializeJavascript()(options)) : minify(options));
} catch (error) {
const hasSourceMap = inputSourceMap && TerserPlugin.isSourceMap(inputSourceMap);
compilation.errors.push( /** @type {WebpackError} */
TerserPlugin.buildError(error, name, hasSourceMap ? new (getTraceMapping().TraceMap)( /** @type {SourceMapInput} */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 (getTraceMapping().TraceMap)( /** @type {SourceMapInput} */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 {SourceMapInput} */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.slice(querySplit);
filename = filename.slice(0, querySplit);
}
const lastSlashIndex = filename.lastIndexOf("/");
const basename = lastSlashIndex === -1 ? filename : filename.slice(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 = getSerializeJavascript()({
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;

48
node_modules/terser-webpack-plugin/dist/minify.js generated vendored Normal file
View File

@@ -0,0 +1,48 @@
"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
node_modules/terser-webpack-plugin/dist/options.json generated vendored Normal file
View File

@@ -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"
}
}
}

637
node_modules/terser-webpack-plugin/dist/utils.js generated vendored Normal file
View File

@@ -0,0 +1,637 @@
"use strict";
/** @typedef {import("@jridgewell/trace-mapping").SourceMapInput} SourceMapInput */
/** @typedef {import("terser").FormatOptions} TerserFormatOptions */
/** @typedef {import("terser").MinifyOptions} TerserOptions */
/** @typedef {import("terser").CompressOptions} TerserCompressOptions */
/** @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 concurrency.
* @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 {SourceMapInput | 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 } & { compress: TerserCompressOptions } & ({ 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 ? {} : false : {
...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);
}
if (terserOptions.compress) {
// More optimizations
if (typeof terserOptions.compress.ecma === "undefined") {
terserOptions.compress.ecma = terserOptions.ecma;
}
// https://github.com/webpack/webpack/issues/16135
if (terserOptions.ecma === 5 && typeof terserOptions.compress.arrows === "undefined") {
terserOptions.compress.arrows = false;
}
}
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 {SourceMapInput} **/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 {SourceMapInput | 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 {SourceMapInput | 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 } & { compress: import("@swc/core").TerserCompressOptions }}
*/
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 ? {} : false : {
...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;
}
if (swcOptions.compress) {
// More optimizations
if (typeof swcOptions.compress.ecma === "undefined") {
swcOptions.compress.ecma = swcOptions.ecma;
}
// https://github.com/webpack/webpack/issues/16135
if (swcOptions.ecma === 5 && typeof swcOptions.compress.arrows === "undefined") {
swcOptions.compress.arrows = false;
}
}
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 {SourceMapInput | 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 => {
const plugin = item.pluginName ? `\nPlugin Name: ${item.pluginName}` : "";
const location = item.location ? `\n\n${item.location.file}:${item.location.line}:${item.location.column}:\n ${item.location.line} | ${item.location.lineText}\n\nSuggestion: ${item.location.suggestion}` : "";
const notes = 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")}` : "";
return `${item.text} [${item.id}]${plugin}${location}${item.detail ? `\nDetails:\n${item.detail}` : ""}${notes}`;
}) : []
};
}
/**
* @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;
};
/**
* @template T
* @param fn {(function(): any) | undefined}
* @returns {function(): T}
*/
function memoize(fn) {
let cache = false;
/** @type {T} */
let result;
return () => {
if (cache) {
return result;
}
result = /** @type {function(): any} */fn();
cache = true;
// Allow to clean up memory for fn
// and all dependent resources
// eslint-disable-next-line no-undefined, no-param-reassign
fn = undefined;
return result;
};
}
module.exports = {
throttleAll,
memoize,
terserMinify,
uglifyJsMinify,
swcMinify,
esbuildMinify
};

View File

@@ -0,0 +1,39 @@
/**
Check if [`argv`](https://nodejs.org/docs/latest/api/process.html#process_process_argv) has a specific flag.
@param flag - CLI flag to look for. The `--` prefix is optional.
@param argv - CLI arguments. Default: `process.argv`.
@returns Whether the flag exists.
@example
```
// $ ts-node foo.ts -f --unicorn --foo=bar -- --rainbow
// foo.ts
import hasFlag = require('has-flag');
hasFlag('unicorn');
//=> true
hasFlag('--unicorn');
//=> true
hasFlag('f');
//=> true
hasFlag('-f');
//=> true
hasFlag('foo=bar');
//=> true
hasFlag('foo');
//=> false
hasFlag('rainbow');
//=> false
```
*/
declare function hasFlag(flag: string, argv?: string[]): boolean;
export = hasFlag;

View File

@@ -0,0 +1,8 @@
'use strict';
module.exports = (flag, argv = process.argv) => {
const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--');
const position = argv.indexOf(prefix + flag);
const terminatorPosition = argv.indexOf('--');
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
};

View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
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.

View File

@@ -0,0 +1,46 @@
{
"name": "has-flag",
"version": "4.0.0",
"description": "Check if argv has a specific flag",
"license": "MIT",
"repository": "sindresorhus/has-flag",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"engines": {
"node": ">=8"
},
"scripts": {
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"has",
"check",
"detect",
"contains",
"find",
"flag",
"cli",
"command-line",
"argv",
"process",
"arg",
"args",
"argument",
"arguments",
"getopt",
"minimist",
"optimist"
],
"devDependencies": {
"ava": "^1.4.1",
"tsd": "^0.7.2",
"xo": "^0.24.0"
}
}

View File

@@ -0,0 +1,89 @@
# has-flag [![Build Status](https://travis-ci.org/sindresorhus/has-flag.svg?branch=master)](https://travis-ci.org/sindresorhus/has-flag)
> Check if [`argv`](https://nodejs.org/docs/latest/api/process.html#process_process_argv) has a specific flag
Correctly stops looking after an `--` argument terminator.
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-has-flag?utm_source=npm-has-flag&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>
---
## Install
```
$ npm install has-flag
```
## Usage
```js
// foo.js
const hasFlag = require('has-flag');
hasFlag('unicorn');
//=> true
hasFlag('--unicorn');
//=> true
hasFlag('f');
//=> true
hasFlag('-f');
//=> true
hasFlag('foo=bar');
//=> true
hasFlag('foo');
//=> false
hasFlag('rainbow');
//=> false
```
```
$ node foo.js -f --unicorn --foo=bar -- --rainbow
```
## API
### hasFlag(flag, [argv])
Returns a boolean for whether the flag exists.
#### flag
Type: `string`
CLI flag to look for. The `--` prefix is optional.
#### argv
Type: `string[]`<br>
Default: `process.argv`
CLI arguments.
## Security
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
## License
MIT © [Sindre Sorhus](https://sindresorhus.com)

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) Facebook, Inc. and its affiliates.
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.

View File

@@ -0,0 +1,247 @@
# jest-worker
Module for executing heavy tasks under forked processes in parallel, by providing a `Promise` based interface, minimum overhead, and bound workers.
The module works by providing an absolute path of the module to be loaded in all forked processes. Files relative to a node module are also accepted. All methods are exposed on the parent process as promises, so they can be `await`'ed. Child (worker) methods can either be synchronous or asynchronous.
The module also implements support for bound workers. Binding a worker means that, based on certain parameters, the same task will always be executed by the same worker. The way bound workers work is by using the returned string of the `computeWorkerKey` method. If the string was used before for a task, the call will be queued to the related worker that processed the task earlier; if not, it will be executed by the first available worker, then sticked to the worker that executed it; so the next time it will be processed by the same worker. If you have no preference on the worker executing the task, but you have defined a `computeWorkerKey` method because you want _some_ of the tasks to be sticked, you can return `null` from it.
The list of exposed methods can be explicitly provided via the `exposedMethods` option. If it is not provided, it will be obtained by requiring the child module into the main process, and analyzed via reflection. Check the "minimal example" section for a valid one.
## Install
```sh
$ yarn add jest-worker
```
## Example
This example covers the minimal usage:
### File `parent.js`
```javascript
import {Worker as JestWorker} from 'jest-worker';
async function main() {
const worker = new JestWorker(require.resolve('./Worker'));
const result = await worker.hello('Alice'); // "Hello, Alice"
}
main();
```
### File `worker.js`
```javascript
export function hello(param) {
return 'Hello, ' + param;
}
```
## Experimental worker
Node 10 shipped with [worker-threads](https://nodejs.org/api/worker_threads.html), a "threading API" that uses SharedArrayBuffers to communicate between the main process and its child threads. This experimental Node feature can significantly improve the communication time between parent and child processes in `jest-worker`.
Since `worker_threads` are considered experimental in Node, you have to opt-in to this behavior by passing `enableWorkerThreads: true` when instantiating the worker. While the feature was unflagged in Node 11.7.0, you'll need to run the Node process with the `--experimental-worker` flag for Node 10.
## API
The `Worker` export is a constructor that is initialized by passing the worker path, plus an options object.
### `workerPath: string` (required)
Node module name or absolute path of the file to be loaded in the child processes. Use `require.resolve` to transform a relative path into an absolute one.
### `options: Object` (optional)
#### `exposedMethods: $ReadOnlyArray<string>` (optional)
List of method names that can be called on the child processes from the parent process. You cannot expose any method named like a public `Worker` method, or starting with `_`. If you use method auto-discovery, then these methods will not be exposed, even if they exist.
#### `numWorkers: number` (optional)
Amount of workers to spawn. Defaults to the number of CPUs minus 1.
#### `maxRetries: number` (optional)
Maximum amount of times that a dead child can be re-spawned, per call. Defaults to `3`, pass `Infinity` to allow endless retries.
#### `forkOptions: Object` (optional)
Allow customizing all options passed to `childProcess.fork`. By default, some values are set (`cwd`, `env` and `execArgv`), but you can override them and customize the rest. For a list of valid values, check [the Node documentation](https://nodejs.org/api/child_process.html#child_process_child_process_fork_modulepath_args_options).
#### `computeWorkerKey: (method: string, ...args: Array<any>) => ?string` (optional)
Every time a method exposed via the API is called, `computeWorkerKey` is also called in order to bound the call to a worker. This is useful for workers that are able to cache the result or part of it. You bound calls to a worker by making `computeWorkerKey` return the same identifier for all different calls. If you do not want to bind the call to any worker, return `null`.
The callback you provide is called with the method name, plus all the rest of the arguments of the call. Thus, you have full control to decide what to return. Check a practical example on bound workers under the "bound worker usage" section.
By default, no process is bound to any worker.
#### `setupArgs: Array<mixed>` (optional)
The arguments that will be passed to the `setup` method during initialization.
#### `WorkerPool: (workerPath: string, options?: WorkerPoolOptions) => WorkerPoolInterface` (optional)
Provide a custom worker pool to be used for spawning child processes. By default, Jest will use a node thread pool if available and fall back to child process threads.
#### `enableWorkerThreads: boolean` (optional)
`jest-worker` will automatically detect if `worker_threads` are available, but will not use them unless passed `enableWorkerThreads: true`.
### `workerSchedulingPolicy: 'round-robin' | 'in-order'` (optional)
Specifies the policy how tasks are assigned to workers if multiple workers are _idle_:
- `round-robin` (default): The task will be sequentially distributed onto the workers. The first task is assigned to the worker 1, the second to the worker 2, to ensure that the work is distributed across workers.
- `in-order`: The task will be assigned to the first free worker starting with worker 1 and only assign the work to worker 2 if the worker 1 is busy.
Tasks are always assigned to the first free worker as soon as tasks start to queue up. The scheduling policy does not define the task scheduling which is always first-in, first-out.
### `taskQueue`: TaskQueue` (optional)
The task queue defines in which order tasks (method calls) are processed by the workers. `jest-worker` ships with a `FifoQueue` and `PriorityQueue`:
- `FifoQueue` (default): Processes the method calls (tasks) in the call order.
- `PriorityQueue`: Processes the method calls by a computed priority in natural ordering (lower priorities first). Tasks with the same priority are processed in any order (FIFO not guaranteed). The constructor accepts a single argument, the function that is passed the name of the called function and the arguments and returns a numerical value for the priority: `new require('jest-worker').PriorityQueue((method, filename) => filename.length)`.
## JestWorker
### Methods
The returned `JestWorker` instance has all the exposed methods, plus some additional ones to interact with the workers itself:
#### `getStdout(): Readable`
Returns a `ReadableStream` where the standard output of all workers is piped. Note that the `silent` option of the child workers must be set to `true` to make it work. This is the default set by `jest-worker`, but keep it in mind when overriding options through `forkOptions`.
#### `getStderr(): Readable`
Returns a `ReadableStream` where the standard error of all workers is piped. Note that the `silent` option of the child workers must be set to `true` to make it work. This is the default set by `jest-worker`, but keep it in mind when overriding options through `forkOptions`.
#### `end()`
Finishes the workers by killing all workers. No further calls can be done to the `Worker` instance.
Returns a Promise that resolves with `{ forceExited: boolean }` once all workers are dead. If `forceExited` is `true`, at least one of the workers did not exit gracefully, which likely happened because it executed a leaky task that left handles open. This should be avoided, force exiting workers is a last resort to prevent creating lots of orphans.
**Note:**
`await`ing the `end()` Promise immediately after the workers are no longer needed before proceeding to do other useful things in your program may not be a good idea. If workers have to be force exited, `jest-worker` may go through multiple stages of force exiting (e.g. SIGTERM, later SIGKILL) and give the worker overall around 1 second time to exit on its own. During this time, your program will wait, even though it may not be necessary that all workers are dead before continuing execution.
Consider deliberately leaving this Promise floating (unhandled resolution). After your program has done the rest of its work and is about to exit, the Node process will wait for the Promise to resolve after all workers are dead as the last event loop task. That way you parallelized computation time of your program and waiting time and you didn't delay the outputs of your program unnecessarily.
### Worker IDs
Each worker has a unique id (index that starts with `1`), which is available inside the worker as `process.env.JEST_WORKER_ID`.
## Setting up and tearing down the child process
The child process can define two special methods (both of them can be asynchronous):
- `setup()`: If defined, it's executed before the first call to any method in the child.
- `teardown()`: If defined, it's executed when the farm ends.
# More examples
## Standard usage
This example covers the standard usage:
### File `parent.js`
```javascript
import {Worker as JestWorker} from 'jest-worker';
async function main() {
const myWorker = new JestWorker(require.resolve('./Worker'), {
exposedMethods: ['foo', 'bar', 'getWorkerId'],
numWorkers: 4,
});
console.log(await myWorker.foo('Alice')); // "Hello from foo: Alice"
console.log(await myWorker.bar('Bob')); // "Hello from bar: Bob"
console.log(await myWorker.getWorkerId()); // "3" -> this message has sent from the 3rd worker
const {forceExited} = await myWorker.end();
if (forceExited) {
console.error('Workers failed to exit gracefully');
}
}
main();
```
### File `worker.js`
```javascript
export function foo(param) {
return 'Hello from foo: ' + param;
}
export function bar(param) {
return 'Hello from bar: ' + param;
}
export function getWorkerId() {
return process.env.JEST_WORKER_ID;
}
```
## Bound worker usage:
This example covers the usage with a `computeWorkerKey` method:
### File `parent.js`
```javascript
import {Worker as JestWorker} from 'jest-worker';
async function main() {
const myWorker = new JestWorker(require.resolve('./Worker'), {
computeWorkerKey: (method, filename) => filename,
});
// Transform the given file, within the first available worker.
console.log(await myWorker.transform('/tmp/foo.js'));
// Wait a bit.
await sleep(10000);
// Transform the same file again. Will immediately return because the
// transformed file is cached in the worker, and `computeWorkerKey` ensures
// the same worker that processed the file the first time will process it now.
console.log(await myWorker.transform('/tmp/foo.js'));
const {forceExited} = await myWorker.end();
if (forceExited) {
console.error('Workers failed to exit gracefully');
}
}
main();
```
### File `worker.js`
```javascript
import babel from '@babel/core';
const cache = Object.create(null);
export function transform(filename) {
if (cache[filename]) {
return cache[filename];
}
// jest-worker can handle both immediate results and thenables. If a
// thenable is returned, it will be await'ed until it resolves.
return babel.transformFileAsync(filename).then(result => {
cache[filename] = result;
return result;
});
}
```

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { FarmOptions, PromiseWithCustomMessage, TaskQueue } from './types';
export default class Farm {
private _numOfWorkers;
private _callback;
private readonly _computeWorkerKey;
private readonly _workerSchedulingPolicy;
private readonly _cacheKeys;
private readonly _locks;
private _offset;
private readonly _taskQueue;
constructor(_numOfWorkers: number, _callback: Function, options?: {
computeWorkerKey?: FarmOptions['computeWorkerKey'];
workerSchedulingPolicy?: FarmOptions['workerSchedulingPolicy'];
taskQueue?: TaskQueue;
});
doWork(method: string, ...args: Array<unknown>): PromiseWithCustomMessage<unknown>;
private _process;
private _push;
private _getNextWorkerOffset;
private _lock;
private _unlock;
private _isLocked;
}

View File

@@ -0,0 +1,206 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
exports.default = void 0;
var _FifoQueue = _interopRequireDefault(require('./FifoQueue'));
var _types = require('./types');
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {default: obj};
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
class Farm {
constructor(_numOfWorkers, _callback, options = {}) {
var _options$workerSchedu, _options$taskQueue;
_defineProperty(this, '_computeWorkerKey', void 0);
_defineProperty(this, '_workerSchedulingPolicy', void 0);
_defineProperty(this, '_cacheKeys', Object.create(null));
_defineProperty(this, '_locks', []);
_defineProperty(this, '_offset', 0);
_defineProperty(this, '_taskQueue', void 0);
this._numOfWorkers = _numOfWorkers;
this._callback = _callback;
this._computeWorkerKey = options.computeWorkerKey;
this._workerSchedulingPolicy =
(_options$workerSchedu = options.workerSchedulingPolicy) !== null &&
_options$workerSchedu !== void 0
? _options$workerSchedu
: 'round-robin';
this._taskQueue =
(_options$taskQueue = options.taskQueue) !== null &&
_options$taskQueue !== void 0
? _options$taskQueue
: new _FifoQueue.default();
}
doWork(method, ...args) {
const customMessageListeners = new Set();
const addCustomMessageListener = listener => {
customMessageListeners.add(listener);
return () => {
customMessageListeners.delete(listener);
};
};
const onCustomMessage = message => {
customMessageListeners.forEach(listener => listener(message));
};
const promise = new Promise( // Bind args to this function so it won't reference to the parent scope.
// This prevents a memory leak in v8, because otherwise the function will
// retaine args for the closure.
((args, resolve, reject) => {
const computeWorkerKey = this._computeWorkerKey;
const request = [_types.CHILD_MESSAGE_CALL, false, method, args];
let worker = null;
let hash = null;
if (computeWorkerKey) {
hash = computeWorkerKey.call(this, method, ...args);
worker = hash == null ? null : this._cacheKeys[hash];
}
const onStart = worker => {
if (hash != null) {
this._cacheKeys[hash] = worker;
}
};
const onEnd = (error, result) => {
customMessageListeners.clear();
if (error) {
reject(error);
} else {
resolve(result);
}
};
const task = {
onCustomMessage,
onEnd,
onStart,
request
};
if (worker) {
this._taskQueue.enqueue(task, worker.getWorkerId());
this._process(worker.getWorkerId());
} else {
this._push(task);
}
}).bind(null, args)
);
promise.UNSTABLE_onCustomMessage = addCustomMessageListener;
return promise;
}
_process(workerId) {
if (this._isLocked(workerId)) {
return this;
}
const task = this._taskQueue.dequeue(workerId);
if (!task) {
return this;
}
if (task.request[1]) {
throw new Error('Queue implementation returned processed task');
} // Reference the task object outside so it won't be retained by onEnd,
// and other properties of the task object, such as task.request can be
// garbage collected.
const taskOnEnd = task.onEnd;
const onEnd = (error, result) => {
taskOnEnd(error, result);
this._unlock(workerId);
this._process(workerId);
};
task.request[1] = true;
this._lock(workerId);
this._callback(
workerId,
task.request,
task.onStart,
onEnd,
task.onCustomMessage
);
return this;
}
_push(task) {
this._taskQueue.enqueue(task);
const offset = this._getNextWorkerOffset();
for (let i = 0; i < this._numOfWorkers; i++) {
this._process((offset + i) % this._numOfWorkers);
if (task.request[1]) {
break;
}
}
return this;
}
_getNextWorkerOffset() {
switch (this._workerSchedulingPolicy) {
case 'in-order':
return 0;
case 'round-robin':
return this._offset++;
}
}
_lock(workerId) {
this._locks[workerId] = true;
}
_unlock(workerId) {
this._locks[workerId] = false;
}
_isLocked(workerId) {
return this._locks[workerId];
}
}
exports.default = Farm;

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { QueueChildMessage, TaskQueue } from './types';
/**
* First-in, First-out task queue that manages a dedicated pool
* for each worker as well as a shared queue. The FIFO ordering is guaranteed
* across the worker specific and shared queue.
*/
export default class FifoQueue implements TaskQueue {
private _workerQueues;
private _sharedQueue;
enqueue(task: QueueChildMessage, workerId?: number): void;
dequeue(workerId: number): QueueChildMessage | null;
}

View File

@@ -0,0 +1,171 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
exports.default = void 0;
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* First-in, First-out task queue that manages a dedicated pool
* for each worker as well as a shared queue. The FIFO ordering is guaranteed
* across the worker specific and shared queue.
*/
class FifoQueue {
constructor() {
_defineProperty(this, '_workerQueues', []);
_defineProperty(this, '_sharedQueue', new InternalQueue());
}
enqueue(task, workerId) {
if (workerId == null) {
this._sharedQueue.enqueue(task);
return;
}
let workerQueue = this._workerQueues[workerId];
if (workerQueue == null) {
workerQueue = this._workerQueues[workerId] = new InternalQueue();
}
const sharedTop = this._sharedQueue.peekLast();
const item = {
previousSharedTask: sharedTop,
task
};
workerQueue.enqueue(item);
}
dequeue(workerId) {
var _this$_workerQueues$w, _workerTop$previousSh, _workerTop$previousSh2;
const workerTop =
(_this$_workerQueues$w = this._workerQueues[workerId]) === null ||
_this$_workerQueues$w === void 0
? void 0
: _this$_workerQueues$w.peek();
const sharedTaskIsProcessed =
(_workerTop$previousSh =
workerTop === null || workerTop === void 0
? void 0
: (_workerTop$previousSh2 = workerTop.previousSharedTask) === null ||
_workerTop$previousSh2 === void 0
? void 0
: _workerTop$previousSh2.request[1]) !== null &&
_workerTop$previousSh !== void 0
? _workerTop$previousSh
: true; // Process the top task from the shared queue if
// - there's no task in the worker specific queue or
// - if the non-worker-specific task after which this worker specifif task
// hasn been queued wasn't processed yet
if (workerTop != null && sharedTaskIsProcessed) {
var _this$_workerQueues$w2,
_this$_workerQueues$w3,
_this$_workerQueues$w4;
return (_this$_workerQueues$w2 =
(_this$_workerQueues$w3 = this._workerQueues[workerId]) === null ||
_this$_workerQueues$w3 === void 0
? void 0
: (_this$_workerQueues$w4 = _this$_workerQueues$w3.dequeue()) ===
null || _this$_workerQueues$w4 === void 0
? void 0
: _this$_workerQueues$w4.task) !== null &&
_this$_workerQueues$w2 !== void 0
? _this$_workerQueues$w2
: null;
}
return this._sharedQueue.dequeue();
}
}
exports.default = FifoQueue;
/**
* FIFO queue for a single worker / shared queue.
*/
class InternalQueue {
constructor() {
_defineProperty(this, '_head', null);
_defineProperty(this, '_last', null);
}
enqueue(value) {
const item = {
next: null,
value
};
if (this._last == null) {
this._head = item;
} else {
this._last.next = item;
}
this._last = item;
}
dequeue() {
if (this._head == null) {
return null;
}
const item = this._head;
this._head = item.next;
if (this._head == null) {
this._last = null;
}
return item.value;
}
peek() {
var _this$_head$value, _this$_head;
return (_this$_head$value =
(_this$_head = this._head) === null || _this$_head === void 0
? void 0
: _this$_head.value) !== null && _this$_head$value !== void 0
? _this$_head$value
: null;
}
peekLast() {
var _this$_last$value, _this$_last;
return (_this$_last$value =
(_this$_last = this._last) === null || _this$_last === void 0
? void 0
: _this$_last.value) !== null && _this$_last$value !== void 0
? _this$_last$value
: null;
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { QueueChildMessage, TaskQueue } from './types';
export declare type ComputeTaskPriorityCallback = (method: string, ...args: Array<unknown>) => number;
declare type QueueItem = {
task: QueueChildMessage;
priority: number;
};
/**
* Priority queue that processes tasks in natural ordering (lower priority first)
* accoridng to the priority computed by the function passed in the constructor.
*
* FIFO ordering isn't guaranteed for tasks with the same priority.
*
* Worker specific tasks with the same priority as a non-worker specific task
* are always processed first.
*/
export default class PriorityQueue implements TaskQueue {
private _computePriority;
private _queue;
private _sharedQueue;
constructor(_computePriority: ComputeTaskPriorityCallback);
enqueue(task: QueueChildMessage, workerId?: number): void;
_enqueue(task: QueueChildMessage, queue: MinHeap<QueueItem>): void;
dequeue(workerId: number): QueueChildMessage | null;
_getWorkerQueue(workerId: number): MinHeap<QueueItem>;
}
declare type HeapItem = {
priority: number;
};
declare class MinHeap<TItem extends HeapItem> {
private _heap;
peek(): TItem | null;
add(item: TItem): void;
poll(): TItem | null;
}
export {};

View File

@@ -0,0 +1,188 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
exports.default = void 0;
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* Priority queue that processes tasks in natural ordering (lower priority first)
* accoridng to the priority computed by the function passed in the constructor.
*
* FIFO ordering isn't guaranteed for tasks with the same priority.
*
* Worker specific tasks with the same priority as a non-worker specific task
* are always processed first.
*/
class PriorityQueue {
constructor(_computePriority) {
_defineProperty(this, '_queue', []);
_defineProperty(this, '_sharedQueue', new MinHeap());
this._computePriority = _computePriority;
}
enqueue(task, workerId) {
if (workerId == null) {
this._enqueue(task, this._sharedQueue);
} else {
const queue = this._getWorkerQueue(workerId);
this._enqueue(task, queue);
}
}
_enqueue(task, queue) {
const item = {
priority: this._computePriority(task.request[2], ...task.request[3]),
task
};
queue.add(item);
}
dequeue(workerId) {
const workerQueue = this._getWorkerQueue(workerId);
const workerTop = workerQueue.peek();
const sharedTop = this._sharedQueue.peek(); // use the task from the worker queue if there's no task in the shared queue
// or if the priority of the worker queue is smaller or equal to the
// priority of the top task in the shared queue. The tasks of the
// worker specific queue are preferred because no other worker can pick this
// specific task up.
if (
sharedTop == null ||
(workerTop != null && workerTop.priority <= sharedTop.priority)
) {
var _workerQueue$poll$tas, _workerQueue$poll;
return (_workerQueue$poll$tas =
(_workerQueue$poll = workerQueue.poll()) === null ||
_workerQueue$poll === void 0
? void 0
: _workerQueue$poll.task) !== null && _workerQueue$poll$tas !== void 0
? _workerQueue$poll$tas
: null;
}
return this._sharedQueue.poll().task;
}
_getWorkerQueue(workerId) {
let queue = this._queue[workerId];
if (queue == null) {
queue = this._queue[workerId] = new MinHeap();
}
return queue;
}
}
exports.default = PriorityQueue;
class MinHeap {
constructor() {
_defineProperty(this, '_heap', []);
}
peek() {
var _this$_heap$;
return (_this$_heap$ = this._heap[0]) !== null && _this$_heap$ !== void 0
? _this$_heap$
: null;
}
add(item) {
const nodes = this._heap;
nodes.push(item);
if (nodes.length === 1) {
return;
}
let currentIndex = nodes.length - 1; // Bubble up the added node as long as the parent is bigger
while (currentIndex > 0) {
const parentIndex = Math.floor((currentIndex + 1) / 2) - 1;
const parent = nodes[parentIndex];
if (parent.priority <= item.priority) {
break;
}
nodes[currentIndex] = parent;
nodes[parentIndex] = item;
currentIndex = parentIndex;
}
}
poll() {
const nodes = this._heap;
const result = nodes[0];
const lastElement = nodes.pop(); // heap was empty or removed the last element
if (result == null || nodes.length === 0) {
return result !== null && result !== void 0 ? result : null;
}
let index = 0;
nodes[0] =
lastElement !== null && lastElement !== void 0 ? lastElement : null;
const element = nodes[0];
while (true) {
let swapIndex = null;
const rightChildIndex = (index + 1) * 2;
const leftChildIndex = rightChildIndex - 1;
const rightChild = nodes[rightChildIndex];
const leftChild = nodes[leftChildIndex]; // if the left child is smaller, swap with the left
if (leftChild != null && leftChild.priority < element.priority) {
swapIndex = leftChildIndex;
} // If the right child is smaller or the right child is smaller than the left
// then swap with the right child
if (
rightChild != null &&
rightChild.priority < (swapIndex == null ? element : leftChild).priority
) {
swapIndex = rightChildIndex;
}
if (swapIndex == null) {
break;
}
nodes[index] = nodes[swapIndex];
nodes[swapIndex] = element;
index = swapIndex;
}
return result;
}
}

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import BaseWorkerPool from './base/BaseWorkerPool';
import type { ChildMessage, OnCustomMessage, OnEnd, OnStart, WorkerInterface, WorkerOptions, WorkerPoolInterface } from './types';
declare class WorkerPool extends BaseWorkerPool implements WorkerPoolInterface {
send(workerId: number, request: ChildMessage, onStart: OnStart, onEnd: OnEnd, onCustomMessage: OnCustomMessage): void;
createWorker(workerOptions: WorkerOptions): WorkerInterface;
}
export default WorkerPool;

View File

@@ -0,0 +1,49 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
exports.default = void 0;
var _BaseWorkerPool = _interopRequireDefault(require('./base/BaseWorkerPool'));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {default: obj};
}
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const canUseWorkerThreads = () => {
try {
require('worker_threads');
return true;
} catch {
return false;
}
};
class WorkerPool extends _BaseWorkerPool.default {
send(workerId, request, onStart, onEnd, onCustomMessage) {
this.getWorkerById(workerId).send(request, onStart, onEnd, onCustomMessage);
}
createWorker(workerOptions) {
let Worker;
if (this._options.enableWorkerThreads && canUseWorkerThreads()) {
Worker = require('./workers/NodeThreadsWorker').default;
} else {
Worker = require('./workers/ChildProcessWorker').default;
}
return new Worker(workerOptions);
}
}
var _default = WorkerPool;
exports.default = _default;

View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/// <reference types="node" />
import { PoolExitResult, WorkerInterface, WorkerOptions, WorkerPoolOptions } from '../types';
export default class BaseWorkerPool {
private readonly _stderr;
private readonly _stdout;
protected readonly _options: WorkerPoolOptions;
private readonly _workers;
constructor(workerPath: string, options: WorkerPoolOptions);
getStderr(): NodeJS.ReadableStream;
getStdout(): NodeJS.ReadableStream;
getWorkers(): Array<WorkerInterface>;
getWorkerById(workerId: number): WorkerInterface;
createWorker(_workerOptions: WorkerOptions): WorkerInterface;
end(): Promise<PoolExitResult>;
}

View File

@@ -0,0 +1,201 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
exports.default = void 0;
function path() {
const data = _interopRequireWildcard(require('path'));
path = function () {
return data;
};
return data;
}
function _mergeStream() {
const data = _interopRequireDefault(require('merge-stream'));
_mergeStream = function () {
return data;
};
return data;
}
var _types = require('../types');
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {default: obj};
}
function _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== 'function') return null;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache = function (nodeInterop) {
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
})(nodeInterop);
}
function _interopRequireWildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
return {default: obj};
}
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {};
var hasPropertyDescriptor =
Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var key in obj) {
if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = hasPropertyDescriptor
? Object.getOwnPropertyDescriptor(obj, key)
: null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
// How long to wait for the child process to terminate
// after CHILD_MESSAGE_END before sending force exiting.
const FORCE_EXIT_DELAY = 500;
/* istanbul ignore next */
const emptyMethod = () => {};
class BaseWorkerPool {
constructor(workerPath, options) {
_defineProperty(this, '_stderr', void 0);
_defineProperty(this, '_stdout', void 0);
_defineProperty(this, '_options', void 0);
_defineProperty(this, '_workers', void 0);
this._options = options;
this._workers = new Array(options.numWorkers);
if (!path().isAbsolute(workerPath)) {
workerPath = require.resolve(workerPath);
}
const stdout = (0, _mergeStream().default)();
const stderr = (0, _mergeStream().default)();
const {forkOptions, maxRetries, resourceLimits, setupArgs} = options;
for (let i = 0; i < options.numWorkers; i++) {
const workerOptions = {
forkOptions,
maxRetries,
resourceLimits,
setupArgs,
workerId: i,
workerPath
};
const worker = this.createWorker(workerOptions);
const workerStdout = worker.getStdout();
const workerStderr = worker.getStderr();
if (workerStdout) {
stdout.add(workerStdout);
}
if (workerStderr) {
stderr.add(workerStderr);
}
this._workers[i] = worker;
}
this._stdout = stdout;
this._stderr = stderr;
}
getStderr() {
return this._stderr;
}
getStdout() {
return this._stdout;
}
getWorkers() {
return this._workers;
}
getWorkerById(workerId) {
return this._workers[workerId];
}
createWorker(_workerOptions) {
throw Error('Missing method createWorker in WorkerPool');
}
async end() {
// We do not cache the request object here. If so, it would only be only
// processed by one of the workers, and we want them all to close.
const workerExitPromises = this._workers.map(async worker => {
worker.send(
[_types.CHILD_MESSAGE_END, false],
emptyMethod,
emptyMethod,
emptyMethod
); // Schedule a force exit in case worker fails to exit gracefully so
// await worker.waitForExit() never takes longer than FORCE_EXIT_DELAY
let forceExited = false;
const forceExitTimeout = setTimeout(() => {
worker.forceExit();
forceExited = true;
}, FORCE_EXIT_DELAY);
await worker.waitForExit(); // Worker ideally exited gracefully, don't send force exit then
clearTimeout(forceExitTimeout);
return forceExited;
});
const workerExits = await Promise.all(workerExitPromises);
return workerExits.reduce(
(result, forceExited) => ({
forceExited: result.forceExited || forceExited
}),
{
forceExited: false
}
);
}
}
exports.default = BaseWorkerPool;

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/// <reference types="node" />
import type { FarmOptions, PoolExitResult, PromiseWithCustomMessage, TaskQueue } from './types';
export { default as PriorityQueue } from './PriorityQueue';
export { default as FifoQueue } from './FifoQueue';
export { default as messageParent } from './workers/messageParent';
/**
* The Jest farm (publicly called "Worker") is a class that allows you to queue
* methods across multiple child processes, in order to parallelize work. This
* is done by providing an absolute path to a module that will be loaded on each
* of the child processes, and bridged to the main process.
*
* Bridged methods are specified by using the "exposedMethods" property of the
* "options" object. This is an array of strings, where each of them corresponds
* to the exported name in the loaded module.
*
* You can also control the amount of workers by using the "numWorkers" property
* of the "options" object, and the settings passed to fork the process through
* the "forkOptions" property. The amount of workers defaults to the amount of
* CPUS minus one.
*
* Queueing calls can be done in two ways:
* - Standard method: calls will be redirected to the first available worker,
* so they will get executed as soon as they can.
*
* - Sticky method: if a "computeWorkerKey" method is provided within the
* config, the resulting string of this method will be used as a key.
* Every time this key is returned, it is guaranteed that your job will be
* processed by the same worker. This is specially useful if your workers
* are caching results.
*/
export declare class Worker {
private _ending;
private _farm;
private _options;
private _workerPool;
constructor(workerPath: string, options?: FarmOptions);
private _bindExposedWorkerMethods;
private _callFunctionWithArgs;
getStderr(): NodeJS.ReadableStream;
getStdout(): NodeJS.ReadableStream;
end(): Promise<PoolExitResult>;
}
export type { PromiseWithCustomMessage, TaskQueue };

View File

@@ -0,0 +1,223 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
Object.defineProperty(exports, 'FifoQueue', {
enumerable: true,
get: function () {
return _FifoQueue.default;
}
});
Object.defineProperty(exports, 'PriorityQueue', {
enumerable: true,
get: function () {
return _PriorityQueue.default;
}
});
exports.Worker = void 0;
Object.defineProperty(exports, 'messageParent', {
enumerable: true,
get: function () {
return _messageParent.default;
}
});
function _os() {
const data = require('os');
_os = function () {
return data;
};
return data;
}
var _Farm = _interopRequireDefault(require('./Farm'));
var _WorkerPool = _interopRequireDefault(require('./WorkerPool'));
var _PriorityQueue = _interopRequireDefault(require('./PriorityQueue'));
var _FifoQueue = _interopRequireDefault(require('./FifoQueue'));
var _messageParent = _interopRequireDefault(require('./workers/messageParent'));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {default: obj};
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function getExposedMethods(workerPath, options) {
let exposedMethods = options.exposedMethods; // If no methods list is given, try getting it by auto-requiring the module.
if (!exposedMethods) {
const module = require(workerPath);
exposedMethods = Object.keys(module).filter(
// @ts-expect-error: no index
name => typeof module[name] === 'function'
);
if (typeof module === 'function') {
exposedMethods = [...exposedMethods, 'default'];
}
}
return exposedMethods;
}
/**
* The Jest farm (publicly called "Worker") is a class that allows you to queue
* methods across multiple child processes, in order to parallelize work. This
* is done by providing an absolute path to a module that will be loaded on each
* of the child processes, and bridged to the main process.
*
* Bridged methods are specified by using the "exposedMethods" property of the
* "options" object. This is an array of strings, where each of them corresponds
* to the exported name in the loaded module.
*
* You can also control the amount of workers by using the "numWorkers" property
* of the "options" object, and the settings passed to fork the process through
* the "forkOptions" property. The amount of workers defaults to the amount of
* CPUS minus one.
*
* Queueing calls can be done in two ways:
* - Standard method: calls will be redirected to the first available worker,
* so they will get executed as soon as they can.
*
* - Sticky method: if a "computeWorkerKey" method is provided within the
* config, the resulting string of this method will be used as a key.
* Every time this key is returned, it is guaranteed that your job will be
* processed by the same worker. This is specially useful if your workers
* are caching results.
*/
class Worker {
constructor(workerPath, options) {
var _this$_options$enable,
_this$_options$forkOp,
_this$_options$maxRet,
_this$_options$numWor,
_this$_options$resour,
_this$_options$setupA;
_defineProperty(this, '_ending', void 0);
_defineProperty(this, '_farm', void 0);
_defineProperty(this, '_options', void 0);
_defineProperty(this, '_workerPool', void 0);
this._options = {...options};
this._ending = false;
const workerPoolOptions = {
enableWorkerThreads:
(_this$_options$enable = this._options.enableWorkerThreads) !== null &&
_this$_options$enable !== void 0
? _this$_options$enable
: false,
forkOptions:
(_this$_options$forkOp = this._options.forkOptions) !== null &&
_this$_options$forkOp !== void 0
? _this$_options$forkOp
: {},
maxRetries:
(_this$_options$maxRet = this._options.maxRetries) !== null &&
_this$_options$maxRet !== void 0
? _this$_options$maxRet
: 3,
numWorkers:
(_this$_options$numWor = this._options.numWorkers) !== null &&
_this$_options$numWor !== void 0
? _this$_options$numWor
: Math.max((0, _os().cpus)().length - 1, 1),
resourceLimits:
(_this$_options$resour = this._options.resourceLimits) !== null &&
_this$_options$resour !== void 0
? _this$_options$resour
: {},
setupArgs:
(_this$_options$setupA = this._options.setupArgs) !== null &&
_this$_options$setupA !== void 0
? _this$_options$setupA
: []
};
if (this._options.WorkerPool) {
// @ts-expect-error: constructor target any?
this._workerPool = new this._options.WorkerPool(
workerPath,
workerPoolOptions
);
} else {
this._workerPool = new _WorkerPool.default(workerPath, workerPoolOptions);
}
this._farm = new _Farm.default(
workerPoolOptions.numWorkers,
this._workerPool.send.bind(this._workerPool),
{
computeWorkerKey: this._options.computeWorkerKey,
taskQueue: this._options.taskQueue,
workerSchedulingPolicy: this._options.workerSchedulingPolicy
}
);
this._bindExposedWorkerMethods(workerPath, this._options);
}
_bindExposedWorkerMethods(workerPath, options) {
getExposedMethods(workerPath, options).forEach(name => {
if (name.startsWith('_')) {
return;
}
if (this.constructor.prototype.hasOwnProperty(name)) {
throw new TypeError('Cannot define a method called ' + name);
} // @ts-expect-error: dynamic extension of the class instance is expected.
this[name] = this._callFunctionWithArgs.bind(this, name);
});
}
_callFunctionWithArgs(method, ...args) {
if (this._ending) {
throw new Error('Farm is ended, no more calls can be done to it');
}
return this._farm.doWork(method, ...args);
}
getStderr() {
return this._workerPool.getStderr();
}
getStdout() {
return this._workerPool.getStdout();
}
async end() {
if (this._ending) {
throw new Error('Farm is ended, no more calls can be done to it');
}
this._ending = true;
return this._workerPool.end();
}
}
exports.Worker = Worker;

View File

@@ -0,0 +1,143 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/// <reference types="node" />
import type { ForkOptions } from 'child_process';
import type { EventEmitter } from 'events';
export interface ResourceLimits {
maxYoungGenerationSizeMb?: number;
maxOldGenerationSizeMb?: number;
codeRangeSizeMb?: number;
stackSizeMb?: number;
}
export declare const CHILD_MESSAGE_INITIALIZE: 0;
export declare const CHILD_MESSAGE_CALL: 1;
export declare const CHILD_MESSAGE_END: 2;
export declare const PARENT_MESSAGE_OK: 0;
export declare const PARENT_MESSAGE_CLIENT_ERROR: 1;
export declare const PARENT_MESSAGE_SETUP_ERROR: 2;
export declare const PARENT_MESSAGE_CUSTOM: 3;
export declare type PARENT_MESSAGE_ERROR = typeof PARENT_MESSAGE_CLIENT_ERROR | typeof PARENT_MESSAGE_SETUP_ERROR;
export interface WorkerPoolInterface {
getStderr(): NodeJS.ReadableStream;
getStdout(): NodeJS.ReadableStream;
getWorkers(): Array<WorkerInterface>;
createWorker(options: WorkerOptions): WorkerInterface;
send(workerId: number, request: ChildMessage, onStart: OnStart, onEnd: OnEnd, onCustomMessage: OnCustomMessage): void;
end(): Promise<PoolExitResult>;
}
export interface WorkerInterface {
send(request: ChildMessage, onProcessStart: OnStart, onProcessEnd: OnEnd, onCustomMessage: OnCustomMessage): void;
waitForExit(): Promise<void>;
forceExit(): void;
getWorkerId(): number;
getStderr(): NodeJS.ReadableStream | null;
getStdout(): NodeJS.ReadableStream | null;
}
export declare type PoolExitResult = {
forceExited: boolean;
};
export interface PromiseWithCustomMessage<T> extends Promise<T> {
UNSTABLE_onCustomMessage?: (listener: OnCustomMessage) => () => void;
}
export type { ForkOptions };
export interface TaskQueue {
/**
* Enqueues the task in the queue for the specified worker or adds it to the
* queue shared by all workers
* @param task the task to queue
* @param workerId the id of the worker that should process this task or undefined
* if there's no preference.
*/
enqueue(task: QueueChildMessage, workerId?: number): void;
/**
* Dequeues the next item from the queue for the speified worker
* @param workerId the id of the worker for which the next task should be retrieved
*/
dequeue(workerId: number): QueueChildMessage | null;
}
export declare type FarmOptions = {
computeWorkerKey?: (method: string, ...args: Array<unknown>) => string | null;
exposedMethods?: ReadonlyArray<string>;
forkOptions?: ForkOptions;
workerSchedulingPolicy?: 'round-robin' | 'in-order';
resourceLimits?: ResourceLimits;
setupArgs?: Array<unknown>;
maxRetries?: number;
numWorkers?: number;
taskQueue?: TaskQueue;
WorkerPool?: (workerPath: string, options?: WorkerPoolOptions) => WorkerPoolInterface;
enableWorkerThreads?: boolean;
};
export declare type WorkerPoolOptions = {
setupArgs: Array<unknown>;
forkOptions: ForkOptions;
resourceLimits: ResourceLimits;
maxRetries: number;
numWorkers: number;
enableWorkerThreads: boolean;
};
export declare type WorkerOptions = {
forkOptions: ForkOptions;
resourceLimits: ResourceLimits;
setupArgs: Array<unknown>;
maxRetries: number;
workerId: number;
workerData?: unknown;
workerPath: string;
};
export declare type MessagePort = typeof EventEmitter & {
postMessage(message: unknown): void;
};
export declare type MessageChannel = {
port1: MessagePort;
port2: MessagePort;
};
export declare type ChildMessageInitialize = [
typeof CHILD_MESSAGE_INITIALIZE,
boolean,
string,
// file
Array<unknown> | undefined,
// setupArgs
MessagePort | undefined
];
export declare type ChildMessageCall = [
typeof CHILD_MESSAGE_CALL,
boolean,
string,
Array<unknown>
];
export declare type ChildMessageEnd = [
typeof CHILD_MESSAGE_END,
boolean
];
export declare type ChildMessage = ChildMessageInitialize | ChildMessageCall | ChildMessageEnd;
export declare type ParentMessageCustom = [
typeof PARENT_MESSAGE_CUSTOM,
unknown
];
export declare type ParentMessageOk = [
typeof PARENT_MESSAGE_OK,
unknown
];
export declare type ParentMessageError = [
PARENT_MESSAGE_ERROR,
string,
string,
string,
unknown
];
export declare type ParentMessage = ParentMessageOk | ParentMessageError | ParentMessageCustom;
export declare type OnStart = (worker: WorkerInterface) => void;
export declare type OnEnd = (err: Error | null, result: unknown) => void;
export declare type OnCustomMessage = (message: Array<unknown> | unknown) => void;
export declare type QueueChildMessage = {
request: ChildMessageCall;
onStart: OnStart;
onEnd: OnEnd;
onCustomMessage: OnCustomMessage;
};

View File

@@ -0,0 +1,39 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
exports.PARENT_MESSAGE_SETUP_ERROR =
exports.PARENT_MESSAGE_OK =
exports.PARENT_MESSAGE_CUSTOM =
exports.PARENT_MESSAGE_CLIENT_ERROR =
exports.CHILD_MESSAGE_INITIALIZE =
exports.CHILD_MESSAGE_END =
exports.CHILD_MESSAGE_CALL =
void 0;
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// import type {ResourceLimits} from 'worker_threads';
// This is not present in the Node 12 typings
// Because of the dynamic nature of a worker communication process, all messages
// coming from any of the other processes cannot be typed. Thus, many types
// include "unknown" as a TS type, which is (unfortunately) correct here.
const CHILD_MESSAGE_INITIALIZE = 0;
exports.CHILD_MESSAGE_INITIALIZE = CHILD_MESSAGE_INITIALIZE;
const CHILD_MESSAGE_CALL = 1;
exports.CHILD_MESSAGE_CALL = CHILD_MESSAGE_CALL;
const CHILD_MESSAGE_END = 2;
exports.CHILD_MESSAGE_END = CHILD_MESSAGE_END;
const PARENT_MESSAGE_OK = 0;
exports.PARENT_MESSAGE_OK = PARENT_MESSAGE_OK;
const PARENT_MESSAGE_CLIENT_ERROR = 1;
exports.PARENT_MESSAGE_CLIENT_ERROR = PARENT_MESSAGE_CLIENT_ERROR;
const PARENT_MESSAGE_SETUP_ERROR = 2;
exports.PARENT_MESSAGE_SETUP_ERROR = PARENT_MESSAGE_SETUP_ERROR;
const PARENT_MESSAGE_CUSTOM = 3;
exports.PARENT_MESSAGE_CUSTOM = PARENT_MESSAGE_CUSTOM;

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/// <reference types="node" />
import { ChildMessage, OnCustomMessage, OnEnd, OnStart, WorkerInterface, WorkerOptions } from '../types';
/**
* This class wraps the child process and provides a nice interface to
* communicate with. It takes care of:
*
* - Re-spawning the process if it dies.
* - Queues calls while the worker is busy.
* - Re-sends the requests if the worker blew up.
*
* The reason for queueing them here (since childProcess.send also has an
* internal queue) is because the worker could be doing asynchronous work, and
* this would lead to the child process to read its receiving buffer and start a
* second call. By queueing calls here, we don't send the next call to the
* children until we receive the result of the previous one.
*
* As soon as a request starts to be processed by a worker, its "processed"
* field is changed to "true", so that other workers which might encounter the
* same call skip it.
*/
export default class ChildProcessWorker implements WorkerInterface {
private _child;
private _options;
private _request;
private _retries;
private _onProcessEnd;
private _onCustomMessage;
private _fakeStream;
private _stdout;
private _stderr;
private _exitPromise;
private _resolveExitPromise;
constructor(options: WorkerOptions);
initialize(): void;
private _shutdown;
private _onMessage;
private _onExit;
send(request: ChildMessage, onProcessStart: OnStart, onProcessEnd: OnEnd, onCustomMessage: OnCustomMessage): void;
waitForExit(): Promise<void>;
forceExit(): void;
getWorkerId(): number;
getStdout(): NodeJS.ReadableStream | null;
getStderr(): NodeJS.ReadableStream | null;
private _getFakeStream;
}

View File

@@ -0,0 +1,333 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
exports.default = void 0;
function _child_process() {
const data = require('child_process');
_child_process = function () {
return data;
};
return data;
}
function _stream() {
const data = require('stream');
_stream = function () {
return data;
};
return data;
}
function _mergeStream() {
const data = _interopRequireDefault(require('merge-stream'));
_mergeStream = function () {
return data;
};
return data;
}
function _supportsColor() {
const data = require('supports-color');
_supportsColor = function () {
return data;
};
return data;
}
var _types = require('../types');
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {default: obj};
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
const SIGNAL_BASE_EXIT_CODE = 128;
const SIGKILL_EXIT_CODE = SIGNAL_BASE_EXIT_CODE + 9;
const SIGTERM_EXIT_CODE = SIGNAL_BASE_EXIT_CODE + 15; // How long to wait after SIGTERM before sending SIGKILL
const SIGKILL_DELAY = 500;
/**
* This class wraps the child process and provides a nice interface to
* communicate with. It takes care of:
*
* - Re-spawning the process if it dies.
* - Queues calls while the worker is busy.
* - Re-sends the requests if the worker blew up.
*
* The reason for queueing them here (since childProcess.send also has an
* internal queue) is because the worker could be doing asynchronous work, and
* this would lead to the child process to read its receiving buffer and start a
* second call. By queueing calls here, we don't send the next call to the
* children until we receive the result of the previous one.
*
* As soon as a request starts to be processed by a worker, its "processed"
* field is changed to "true", so that other workers which might encounter the
* same call skip it.
*/
class ChildProcessWorker {
constructor(options) {
_defineProperty(this, '_child', void 0);
_defineProperty(this, '_options', void 0);
_defineProperty(this, '_request', void 0);
_defineProperty(this, '_retries', void 0);
_defineProperty(this, '_onProcessEnd', void 0);
_defineProperty(this, '_onCustomMessage', void 0);
_defineProperty(this, '_fakeStream', void 0);
_defineProperty(this, '_stdout', void 0);
_defineProperty(this, '_stderr', void 0);
_defineProperty(this, '_exitPromise', void 0);
_defineProperty(this, '_resolveExitPromise', void 0);
this._options = options;
this._request = null;
this._fakeStream = null;
this._stdout = null;
this._stderr = null;
this._exitPromise = new Promise(resolve => {
this._resolveExitPromise = resolve;
});
this.initialize();
}
initialize() {
const forceColor = _supportsColor().stdout
? {
FORCE_COLOR: '1'
}
: {};
const child = (0, _child_process().fork)(
require.resolve('./processChild'),
[],
{
cwd: process.cwd(),
env: {
...process.env,
JEST_WORKER_ID: String(this._options.workerId + 1),
// 0-indexed workerId, 1-indexed JEST_WORKER_ID
...forceColor
},
// Suppress --debug / --inspect flags while preserving others (like --harmony).
execArgv: process.execArgv.filter(v => !/^--(debug|inspect)/.test(v)),
silent: true,
...this._options.forkOptions
}
);
if (child.stdout) {
if (!this._stdout) {
// We need to add a permanent stream to the merged stream to prevent it
// from ending when the subprocess stream ends
this._stdout = (0, _mergeStream().default)(this._getFakeStream());
}
this._stdout.add(child.stdout);
}
if (child.stderr) {
if (!this._stderr) {
// We need to add a permanent stream to the merged stream to prevent it
// from ending when the subprocess stream ends
this._stderr = (0, _mergeStream().default)(this._getFakeStream());
}
this._stderr.add(child.stderr);
}
child.on('message', this._onMessage.bind(this));
child.on('exit', this._onExit.bind(this));
child.send([
_types.CHILD_MESSAGE_INITIALIZE,
false,
this._options.workerPath,
this._options.setupArgs
]);
this._child = child;
this._retries++; // If we exceeded the amount of retries, we will emulate an error reply
// coming from the child. This avoids code duplication related with cleaning
// the queue, and scheduling the next call.
if (this._retries > this._options.maxRetries) {
const error = new Error(
`Jest worker encountered ${this._retries} child process exceptions, exceeding retry limit`
);
this._onMessage([
_types.PARENT_MESSAGE_CLIENT_ERROR,
error.name,
error.message,
error.stack,
{
type: 'WorkerError'
}
]);
}
}
_shutdown() {
// End the temporary streams so the merged streams end too
if (this._fakeStream) {
this._fakeStream.end();
this._fakeStream = null;
}
this._resolveExitPromise();
}
_onMessage(response) {
// TODO: Add appropriate type check
let error;
switch (response[0]) {
case _types.PARENT_MESSAGE_OK:
this._onProcessEnd(null, response[1]);
break;
case _types.PARENT_MESSAGE_CLIENT_ERROR:
error = response[4];
if (error != null && typeof error === 'object') {
const extra = error; // @ts-expect-error: no index
const NativeCtor = global[response[1]];
const Ctor = typeof NativeCtor === 'function' ? NativeCtor : Error;
error = new Ctor(response[2]);
error.type = response[1];
error.stack = response[3];
for (const key in extra) {
error[key] = extra[key];
}
}
this._onProcessEnd(error, null);
break;
case _types.PARENT_MESSAGE_SETUP_ERROR:
error = new Error('Error when calling setup: ' + response[2]);
error.type = response[1];
error.stack = response[3];
this._onProcessEnd(error, null);
break;
case _types.PARENT_MESSAGE_CUSTOM:
this._onCustomMessage(response[1]);
break;
default:
throw new TypeError('Unexpected response from worker: ' + response[0]);
}
}
_onExit(exitCode) {
if (
exitCode !== 0 &&
exitCode !== null &&
exitCode !== SIGTERM_EXIT_CODE &&
exitCode !== SIGKILL_EXIT_CODE
) {
this.initialize();
if (this._request) {
this._child.send(this._request);
}
} else {
this._shutdown();
}
}
send(request, onProcessStart, onProcessEnd, onCustomMessage) {
onProcessStart(this);
this._onProcessEnd = (...args) => {
// Clean the request to avoid sending past requests to workers that fail
// while waiting for a new request (timers, unhandled rejections...)
this._request = null;
return onProcessEnd(...args);
};
this._onCustomMessage = (...arg) => onCustomMessage(...arg);
this._request = request;
this._retries = 0;
this._child.send(request, () => {});
}
waitForExit() {
return this._exitPromise;
}
forceExit() {
this._child.kill('SIGTERM');
const sigkillTimeout = setTimeout(
() => this._child.kill('SIGKILL'),
SIGKILL_DELAY
);
this._exitPromise.then(() => clearTimeout(sigkillTimeout));
}
getWorkerId() {
return this._options.workerId;
}
getStdout() {
return this._stdout;
}
getStderr() {
return this._stderr;
}
_getFakeStream() {
if (!this._fakeStream) {
this._fakeStream = new (_stream().PassThrough)();
}
return this._fakeStream;
}
}
exports.default = ChildProcessWorker;

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/// <reference types="node" />
import { ChildMessage, OnCustomMessage, OnEnd, OnStart, WorkerInterface, WorkerOptions } from '../types';
export default class ExperimentalWorker implements WorkerInterface {
private _worker;
private _options;
private _request;
private _retries;
private _onProcessEnd;
private _onCustomMessage;
private _fakeStream;
private _stdout;
private _stderr;
private _exitPromise;
private _resolveExitPromise;
private _forceExited;
constructor(options: WorkerOptions);
initialize(): void;
private _shutdown;
private _onMessage;
private _onExit;
waitForExit(): Promise<void>;
forceExit(): void;
send(request: ChildMessage, onProcessStart: OnStart, onProcessEnd: OnEnd | null, onCustomMessage: OnCustomMessage): void;
getWorkerId(): number;
getStdout(): NodeJS.ReadableStream | null;
getStderr(): NodeJS.ReadableStream | null;
private _getFakeStream;
}

View File

@@ -0,0 +1,344 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
exports.default = void 0;
function path() {
const data = _interopRequireWildcard(require('path'));
path = function () {
return data;
};
return data;
}
function _stream() {
const data = require('stream');
_stream = function () {
return data;
};
return data;
}
function _worker_threads() {
const data = require('worker_threads');
_worker_threads = function () {
return data;
};
return data;
}
function _mergeStream() {
const data = _interopRequireDefault(require('merge-stream'));
_mergeStream = function () {
return data;
};
return data;
}
var _types = require('../types');
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {default: obj};
}
function _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== 'function') return null;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache = function (nodeInterop) {
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
})(nodeInterop);
}
function _interopRequireWildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
return {default: obj};
}
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {};
var hasPropertyDescriptor =
Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var key in obj) {
if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = hasPropertyDescriptor
? Object.getOwnPropertyDescriptor(obj, key)
: null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
class ExperimentalWorker {
constructor(options) {
_defineProperty(this, '_worker', void 0);
_defineProperty(this, '_options', void 0);
_defineProperty(this, '_request', void 0);
_defineProperty(this, '_retries', void 0);
_defineProperty(this, '_onProcessEnd', void 0);
_defineProperty(this, '_onCustomMessage', void 0);
_defineProperty(this, '_fakeStream', void 0);
_defineProperty(this, '_stdout', void 0);
_defineProperty(this, '_stderr', void 0);
_defineProperty(this, '_exitPromise', void 0);
_defineProperty(this, '_resolveExitPromise', void 0);
_defineProperty(this, '_forceExited', void 0);
this._options = options;
this._request = null;
this._fakeStream = null;
this._stdout = null;
this._stderr = null;
this._exitPromise = new Promise(resolve => {
this._resolveExitPromise = resolve;
});
this._forceExited = false;
this.initialize();
}
initialize() {
this._worker = new (_worker_threads().Worker)(
path().resolve(__dirname, './threadChild.js'),
{
eval: false,
// @ts-expect-error: added in newer versions
resourceLimits: this._options.resourceLimits,
stderr: true,
stdout: true,
workerData: this._options.workerData,
...this._options.forkOptions
}
);
if (this._worker.stdout) {
if (!this._stdout) {
// We need to add a permanent stream to the merged stream to prevent it
// from ending when the subprocess stream ends
this._stdout = (0, _mergeStream().default)(this._getFakeStream());
}
this._stdout.add(this._worker.stdout);
}
if (this._worker.stderr) {
if (!this._stderr) {
// We need to add a permanent stream to the merged stream to prevent it
// from ending when the subprocess stream ends
this._stderr = (0, _mergeStream().default)(this._getFakeStream());
}
this._stderr.add(this._worker.stderr);
}
this._worker.on('message', this._onMessage.bind(this));
this._worker.on('exit', this._onExit.bind(this));
this._worker.postMessage([
_types.CHILD_MESSAGE_INITIALIZE,
false,
this._options.workerPath,
this._options.setupArgs,
String(this._options.workerId + 1) // 0-indexed workerId, 1-indexed JEST_WORKER_ID
]);
this._retries++; // If we exceeded the amount of retries, we will emulate an error reply
// coming from the child. This avoids code duplication related with cleaning
// the queue, and scheduling the next call.
if (this._retries > this._options.maxRetries) {
const error = new Error('Call retries were exceeded');
this._onMessage([
_types.PARENT_MESSAGE_CLIENT_ERROR,
error.name,
error.message,
error.stack,
{
type: 'WorkerError'
}
]);
}
}
_shutdown() {
// End the permanent stream so the merged stream end too
if (this._fakeStream) {
this._fakeStream.end();
this._fakeStream = null;
}
this._resolveExitPromise();
}
_onMessage(response) {
let error;
switch (response[0]) {
case _types.PARENT_MESSAGE_OK:
this._onProcessEnd(null, response[1]);
break;
case _types.PARENT_MESSAGE_CLIENT_ERROR:
error = response[4];
if (error != null && typeof error === 'object') {
const extra = error; // @ts-expect-error: no index
const NativeCtor = global[response[1]];
const Ctor = typeof NativeCtor === 'function' ? NativeCtor : Error;
error = new Ctor(response[2]);
error.type = response[1];
error.stack = response[3];
for (const key in extra) {
// @ts-expect-error: no index
error[key] = extra[key];
}
}
this._onProcessEnd(error, null);
break;
case _types.PARENT_MESSAGE_SETUP_ERROR:
error = new Error('Error when calling setup: ' + response[2]); // @ts-expect-error: adding custom properties to errors.
error.type = response[1];
error.stack = response[3];
this._onProcessEnd(error, null);
break;
case _types.PARENT_MESSAGE_CUSTOM:
this._onCustomMessage(response[1]);
break;
default:
throw new TypeError('Unexpected response from worker: ' + response[0]);
}
}
_onExit(exitCode) {
if (exitCode !== 0 && !this._forceExited) {
this.initialize();
if (this._request) {
this._worker.postMessage(this._request);
}
} else {
this._shutdown();
}
}
waitForExit() {
return this._exitPromise;
}
forceExit() {
this._forceExited = true;
this._worker.terminate();
}
send(request, onProcessStart, onProcessEnd, onCustomMessage) {
onProcessStart(this);
this._onProcessEnd = (...args) => {
var _onProcessEnd;
// Clean the request to avoid sending past requests to workers that fail
// while waiting for a new request (timers, unhandled rejections...)
this._request = null;
const res =
(_onProcessEnd = onProcessEnd) === null || _onProcessEnd === void 0
? void 0
: _onProcessEnd(...args); // Clean up the reference so related closures can be garbage collected.
onProcessEnd = null;
return res;
};
this._onCustomMessage = (...arg) => onCustomMessage(...arg);
this._request = request;
this._retries = 0;
this._worker.postMessage(request);
}
getWorkerId() {
return this._options.workerId;
}
getStdout() {
return this._stdout;
}
getStderr() {
return this._stderr;
}
_getFakeStream() {
if (!this._fakeStream) {
this._fakeStream = new (_stream().PassThrough)();
}
return this._fakeStream;
}
}
exports.default = ExperimentalWorker;

View File

@@ -0,0 +1,8 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/// <reference types="node" />
export default function messageParent(message: unknown, parentProcess?: NodeJS.Process): void;

View File

@@ -0,0 +1,38 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
exports.default = messageParent;
var _types = require('../types');
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const isWorkerThread = (() => {
try {
// `Require` here to support Node v10
const {isMainThread, parentPort} = require('worker_threads');
return !isMainThread && parentPort != null;
} catch {
return false;
}
})();
function messageParent(message, parentProcess = process) {
if (isWorkerThread) {
// `Require` here to support Node v10
const {parentPort} = require('worker_threads'); // ! is safe due to `null` check in `isWorkerThread`
parentPort.postMessage([_types.PARENT_MESSAGE_CUSTOM, message]);
} else if (typeof parentProcess.send === 'function') {
parentProcess.send([_types.PARENT_MESSAGE_CUSTOM, message]);
} else {
throw new Error('"messageParent" can only be used inside a worker');
}
}

View File

@@ -0,0 +1,7 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export {};

View File

@@ -0,0 +1,148 @@
'use strict';
var _types = require('../types');
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
let file = null;
let setupArgs = [];
let initialized = false;
/**
* This file is a small bootstrapper for workers. It sets up the communication
* between the worker and the parent process, interpreting parent messages and
* sending results back.
*
* The file loaded will be lazily initialized the first time any of the workers
* is called. This is done for optimal performance: if the farm is initialized,
* but no call is made to it, child Node processes will be consuming the least
* possible amount of memory.
*
* If an invalid message is detected, the child will exit (by throwing) with a
* non-zero exit code.
*/
const messageListener = request => {
switch (request[0]) {
case _types.CHILD_MESSAGE_INITIALIZE:
const init = request;
file = init[2];
setupArgs = request[3];
break;
case _types.CHILD_MESSAGE_CALL:
const call = request;
execMethod(call[2], call[3]);
break;
case _types.CHILD_MESSAGE_END:
end();
break;
default:
throw new TypeError(
'Unexpected request from parent process: ' + request[0]
);
}
};
process.on('message', messageListener);
function reportSuccess(result) {
if (!process || !process.send) {
throw new Error('Child can only be used on a forked process');
}
process.send([_types.PARENT_MESSAGE_OK, result]);
}
function reportClientError(error) {
return reportError(error, _types.PARENT_MESSAGE_CLIENT_ERROR);
}
function reportInitializeError(error) {
return reportError(error, _types.PARENT_MESSAGE_SETUP_ERROR);
}
function reportError(error, type) {
if (!process || !process.send) {
throw new Error('Child can only be used on a forked process');
}
if (error == null) {
error = new Error('"null" or "undefined" thrown');
}
process.send([
type,
error.constructor && error.constructor.name,
error.message,
error.stack,
typeof error === 'object' ? {...error} : error
]);
}
function end() {
const main = require(file);
if (!main.teardown) {
exitProcess();
return;
}
execFunction(main.teardown, main, [], exitProcess, exitProcess);
}
function exitProcess() {
// Clean up open handles so the process ideally exits gracefully
process.removeListener('message', messageListener);
}
function execMethod(method, args) {
const main = require(file);
let fn;
if (method === 'default') {
fn = main.__esModule ? main['default'] : main;
} else {
fn = main[method];
}
function execHelper() {
execFunction(fn, main, args, reportSuccess, reportClientError);
}
if (initialized || !main.setup) {
execHelper();
return;
}
initialized = true;
execFunction(main.setup, main, setupArgs, execHelper, reportInitializeError);
}
const isPromise = obj =>
!!obj &&
(typeof obj === 'object' || typeof obj === 'function') &&
typeof obj.then === 'function';
function execFunction(fn, ctx, args, onResult, onError) {
let result;
try {
result = fn.apply(ctx, args);
} catch (err) {
onError(err);
return;
}
if (isPromise(result)) {
result.then(onResult, onError);
} else {
onResult(result);
}
}

View File

@@ -0,0 +1,7 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export {};

View File

@@ -0,0 +1,159 @@
'use strict';
function _worker_threads() {
const data = require('worker_threads');
_worker_threads = function () {
return data;
};
return data;
}
var _types = require('../types');
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
let file = null;
let setupArgs = [];
let initialized = false;
/**
* This file is a small bootstrapper for workers. It sets up the communication
* between the worker and the parent process, interpreting parent messages and
* sending results back.
*
* The file loaded will be lazily initialized the first time any of the workers
* is called. This is done for optimal performance: if the farm is initialized,
* but no call is made to it, child Node processes will be consuming the least
* possible amount of memory.
*
* If an invalid message is detected, the child will exit (by throwing) with a
* non-zero exit code.
*/
const messageListener = request => {
switch (request[0]) {
case _types.CHILD_MESSAGE_INITIALIZE:
const init = request;
file = init[2];
setupArgs = request[3];
process.env.JEST_WORKER_ID = request[4];
break;
case _types.CHILD_MESSAGE_CALL:
const call = request;
execMethod(call[2], call[3]);
break;
case _types.CHILD_MESSAGE_END:
end();
break;
default:
throw new TypeError(
'Unexpected request from parent process: ' + request[0]
);
}
};
_worker_threads().parentPort.on('message', messageListener);
function reportSuccess(result) {
if (_worker_threads().isMainThread) {
throw new Error('Child can only be used on a forked process');
}
_worker_threads().parentPort.postMessage([_types.PARENT_MESSAGE_OK, result]);
}
function reportClientError(error) {
return reportError(error, _types.PARENT_MESSAGE_CLIENT_ERROR);
}
function reportInitializeError(error) {
return reportError(error, _types.PARENT_MESSAGE_SETUP_ERROR);
}
function reportError(error, type) {
if (_worker_threads().isMainThread) {
throw new Error('Child can only be used on a forked process');
}
if (error == null) {
error = new Error('"null" or "undefined" thrown');
}
_worker_threads().parentPort.postMessage([
type,
error.constructor && error.constructor.name,
error.message,
error.stack,
typeof error === 'object' ? {...error} : error
]);
}
function end() {
const main = require(file);
if (!main.teardown) {
exitProcess();
return;
}
execFunction(main.teardown, main, [], exitProcess, exitProcess);
}
function exitProcess() {
// Clean up open handles so the worker ideally exits gracefully
_worker_threads().parentPort.removeListener('message', messageListener);
}
function execMethod(method, args) {
const main = require(file);
let fn;
if (method === 'default') {
fn = main.__esModule ? main['default'] : main;
} else {
fn = main[method];
}
function execHelper() {
execFunction(fn, main, args, reportSuccess, reportClientError);
}
if (initialized || !main.setup) {
execHelper();
return;
}
initialized = true;
execFunction(main.setup, main, setupArgs, execHelper, reportInitializeError);
}
const isPromise = obj =>
!!obj &&
(typeof obj === 'object' || typeof obj === 'function') &&
typeof obj.then === 'function';
function execFunction(fn, ctx, args, onResult, onError) {
let result;
try {
result = fn.apply(ctx, args);
} catch (err) {
onError(err);
return;
}
if (isPromise(result)) {
result.then(onResult, onError);
} else {
onResult(result);
}
}

View File

@@ -0,0 +1,38 @@
{
"name": "jest-worker",
"version": "27.5.1",
"repository": {
"type": "git",
"url": "https://github.com/facebook/jest.git",
"directory": "packages/jest-worker"
},
"license": "MIT",
"main": "./build/index.js",
"types": "./build/index.d.ts",
"exports": {
".": {
"types": "./build/index.d.ts",
"default": "./build/index.js"
},
"./package.json": "./package.json"
},
"dependencies": {
"@types/node": "*",
"merge-stream": "^2.0.0",
"supports-color": "^8.0.0"
},
"devDependencies": {
"@types/merge-stream": "^1.1.2",
"@types/supports-color": "^8.1.0",
"get-stream": "^6.0.0",
"jest-leak-detector": "^27.5.1",
"worker-farm": "^1.6.0"
},
"engines": {
"node": ">= 10.13.0"
},
"publishConfig": {
"access": "public"
},
"gitHead": "67c1aa20c5fec31366d733e901fee2b981cb1850"
}

View File

@@ -0,0 +1,24 @@
/* eslint-env browser */
'use strict';
function getChromeVersion() {
const matches = /(Chrome|Chromium)\/(?<chromeVersion>\d+)\./.exec(navigator.userAgent);
if (!matches) {
return;
}
return Number.parseInt(matches.groups.chromeVersion, 10);
}
const colorSupport = getChromeVersion() >= 69 ? {
level: 1,
hasBasic: true,
has256: false,
has16m: false
} : false;
module.exports = {
stdout: colorSupport,
stderr: colorSupport
};

View File

@@ -0,0 +1,152 @@
'use strict';
const os = require('os');
const tty = require('tty');
const hasFlag = require('has-flag');
const {env} = process;
let flagForceColor;
if (hasFlag('no-color') ||
hasFlag('no-colors') ||
hasFlag('color=false') ||
hasFlag('color=never')) {
flagForceColor = 0;
} else if (hasFlag('color') ||
hasFlag('colors') ||
hasFlag('color=true') ||
hasFlag('color=always')) {
flagForceColor = 1;
}
function envForceColor() {
if ('FORCE_COLOR' in env) {
if (env.FORCE_COLOR === 'true') {
return 1;
}
if (env.FORCE_COLOR === 'false') {
return 0;
}
return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
}
}
function translateLevel(level) {
if (level === 0) {
return false;
}
return {
level,
hasBasic: true,
has256: level >= 2,
has16m: level >= 3
};
}
function supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
const noFlagForceColor = envForceColor();
if (noFlagForceColor !== undefined) {
flagForceColor = noFlagForceColor;
}
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
if (forceColor === 0) {
return 0;
}
if (sniffFlags) {
if (hasFlag('color=16m') ||
hasFlag('color=full') ||
hasFlag('color=truecolor')) {
return 3;
}
if (hasFlag('color=256')) {
return 2;
}
}
if (haveStream && !streamIsTTY && forceColor === undefined) {
return 0;
}
const min = forceColor || 0;
if (env.TERM === 'dumb') {
return min;
}
if (process.platform === 'win32') {
// Windows 10 build 10586 is the first Windows release that supports 256 colors.
// Windows 10 build 14931 is the first release that supports 16m/TrueColor.
const osRelease = os.release().split('.');
if (
Number(osRelease[0]) >= 10 &&
Number(osRelease[2]) >= 10586
) {
return Number(osRelease[2]) >= 14931 ? 3 : 2;
}
return 1;
}
if ('CI' in env) {
if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI', 'GITHUB_ACTIONS', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') {
return 1;
}
return min;
}
if ('TEAMCITY_VERSION' in env) {
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
}
if (env.COLORTERM === 'truecolor') {
return 3;
}
if ('TERM_PROGRAM' in env) {
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
switch (env.TERM_PROGRAM) {
case 'iTerm.app':
return version >= 3 ? 3 : 2;
case 'Apple_Terminal':
return 2;
// No default
}
}
if (/-256(color)?$/i.test(env.TERM)) {
return 2;
}
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
return 1;
}
if ('COLORTERM' in env) {
return 1;
}
return min;
}
function getSupportLevel(stream, options = {}) {
const level = supportsColor(stream, {
streamIsTTY: stream && stream.isTTY,
...options
});
return translateLevel(level);
}
module.exports = {
supportsColor: getSupportLevel,
stdout: getSupportLevel({isTTY: tty.isatty(1)}),
stderr: getSupportLevel({isTTY: tty.isatty(2)})
};

View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
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.

View File

@@ -0,0 +1,58 @@
{
"name": "supports-color",
"version": "8.1.1",
"description": "Detect whether a terminal supports color",
"license": "MIT",
"repository": "chalk/supports-color",
"funding": "https://github.com/chalk/supports-color?sponsor=1",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"engines": {
"node": ">=10"
},
"scripts": {
"test": "xo && ava"
},
"files": [
"index.js",
"browser.js"
],
"exports": {
"node": "./index.js",
"default": "./browser.js"
},
"keywords": [
"color",
"colour",
"colors",
"terminal",
"console",
"cli",
"ansi",
"styles",
"tty",
"rgb",
"256",
"shell",
"xterm",
"command-line",
"support",
"supports",
"capability",
"detect",
"truecolor",
"16m"
],
"dependencies": {
"has-flag": "^4.0.0"
},
"devDependencies": {
"ava": "^2.4.0",
"import-fresh": "^3.2.2",
"xo": "^0.35.0"
},
"browser": "browser.js"
}

View File

@@ -0,0 +1,77 @@
# supports-color
> Detect whether a terminal supports color
## Install
```
$ npm install supports-color
```
## Usage
```js
const supportsColor = require('supports-color');
if (supportsColor.stdout) {
console.log('Terminal stdout supports color');
}
if (supportsColor.stdout.has256) {
console.log('Terminal stdout supports 256 colors');
}
if (supportsColor.stderr.has16m) {
console.log('Terminal stderr supports 16 million colors (truecolor)');
}
```
## API
Returns an `Object` with a `stdout` and `stderr` property for testing either streams. Each property is an `Object`, or `false` if color is not supported.
The `stdout`/`stderr` objects specifies a level of support for color through a `.level` property and a corresponding flag:
- `.level = 1` and `.hasBasic = true`: Basic color support (16 colors)
- `.level = 2` and `.has256 = true`: 256 color support
- `.level = 3` and `.has16m = true`: Truecolor support (16 million colors)
### `require('supports-color').supportsColor(stream, options?)`
Additionally, `supports-color` exposes the `.supportsColor()` function that takes an arbitrary write stream (e.g. `process.stdout`) and an optional options object to (re-)evaluate color support for an arbitrary stream.
For example, `require('supports-color').stdout` is the equivalent of `require('supports-color').supportsColor(process.stdout)`.
The options object supports a single boolean property `sniffFlags`. By default it is `true`, which instructs `supportsColor()` to sniff `process.argv` for the multitude of `--color` flags (see _Info_ below). If `false`, then `process.argv` is not considered when determining color support.
## Info
It obeys the `--color` and `--no-color` CLI flags.
For situations where using `--color` is not possible, use the environment variable `FORCE_COLOR=1` (level 1), `FORCE_COLOR=2` (level 2), or `FORCE_COLOR=3` (level 3) to forcefully enable color, or `FORCE_COLOR=0` to forcefully disable. The use of `FORCE_COLOR` overrides all other color support checks.
Explicit 256/Truecolor mode can be enabled using the `--color=256` and `--color=16m` flags, respectively.
## Related
- [supports-color-cli](https://github.com/chalk/supports-color-cli) - CLI for this module
- [chalk](https://github.com/chalk/chalk) - Terminal string styling done right
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [Josh Junon](https://github.com/qix-)
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-supports-color?utm_source=npm-supports-color&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>
---

120
node_modules/terser-webpack-plugin/package.json generated vendored Normal file
View File

@@ -0,0 +1,120 @@
{
"name": "terser-webpack-plugin",
"version": "5.3.10",
"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:spelling": "cspell \"**/*.*\"",
"lint:types": "tsc --pretty --noEmit",
"lint": "npm-run-all -l -p \"lint:**\"",
"fix:js": "npm run lint:js -- --fix",
"fix:prettier": "npm run lint:prettier -- --write",
"fix": "npm-run-all -l fix:js fix:prettier",
"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": {
"@jridgewell/trace-mapping": "^0.3.20",
"jest-worker": "^27.4.5",
"schema-utils": "^3.1.1",
"serialize-javascript": "^6.0.1",
"terser": "^5.26.0"
},
"devDependencies": {
"@babel/cli": "^7.22.15",
"@babel/core": "^7.22.17",
"@babel/preset-env": "^7.22.15",
"@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^17.7.0",
"@swc/core": "^1.3.84",
"@types/node": "^18.15.11",
"@types/serialize-javascript": "^5.0.2",
"@types/uglify-js": "^3.17.1",
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"babel-jest": "^28.1.2",
"copy-webpack-plugin": "^9.0.1",
"cross-env": "^7.0.3",
"cspell": "^6.31.2",
"del": "^6.0.0",
"del-cli": "^3.0.1",
"esbuild": "^0.19.10",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.9.0",
"eslint-plugin-import": "^2.28.1",
"file-loader": "^6.2.0",
"husky": "^7.0.2",
"jest": "^27.5.1",
"lint-staged": "^13.2.3",
"memfs": "^3.4.13",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.7",
"standard-version": "^9.3.1",
"typescript": "^4.9.5",
"uglify-js": "^3.17.4",
"webpack": "^5.88.2",
"webpack-cli": "^4.10.0",
"worker-loader": "^3.0.8"
},
"keywords": [
"uglify",
"uglify-js",
"uglify-es",
"terser",
"webpack",
"webpack-plugin",
"minification",
"compress",
"compressor",
"min",
"minification",
"minifier",
"minify",
"optimize",
"optimizer"
]
}

219
node_modules/terser-webpack-plugin/types/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,219 @@
export = TerserPlugin;
/**
* @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 {TraceMap} [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,
SourceMapInput,
TraceMap,
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 SourceMapInput = import("@jridgewell/trace-mapping").SourceMapInput;
type TraceMap = import("@jridgewell/trace-mapping").TraceMap;
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("@jridgewell/trace-mapping").SourceMapInput | 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: SourceMapInput | 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: SourceMapInput | undefined;
extractComments: ExtractCommentsOptions | undefined;
minimizer: {
implementation: MinimizerImplementation<T>;
options: MinimizerOptions<T>;
};
};
type MinimizerWorker<T> = import("jest-worker").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";

17
node_modules/terser-webpack-plugin/types/minify.d.ts generated vendored Normal file
View File

@@ -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>;

107
node_modules/terser-webpack-plugin/types/utils.d.ts generated vendored Normal file
View File

@@ -0,0 +1,107 @@
export type Task<T> = () => Promise<T>;
export type SourceMapInput = import("@jridgewell/trace-mapping").SourceMapInput;
export type TerserFormatOptions = import("terser").FormatOptions;
export type TerserOptions = import("terser").MinifyOptions;
export type TerserCompressOptions = import("terser").CompressOptions;
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 concurrency.
* @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[]>;
/**
* @template T
* @param fn {(function(): any) | undefined}
* @returns {function(): T}
*/
export function memoize<T>(fn: (() => any) | undefined): () => T;
/**
* @param {Input} input
* @param {SourceMapInput | undefined} sourceMap
* @param {PredefinedOptions & CustomOptions} minimizerOptions
* @param {ExtractCommentsOptions | undefined} extractComments
* @return {Promise<MinimizedResult>}
*/
export function terserMinify(
input: Input,
sourceMap: SourceMapInput | undefined,
minimizerOptions: PredefinedOptions & CustomOptions,
extractComments: ExtractCommentsOptions | undefined
): Promise<MinimizedResult>;
export namespace terserMinify {
/**
* @returns {string | undefined}
*/
function getMinimizerVersion(): string | undefined;
}
/**
* @param {Input} input
* @param {SourceMapInput | undefined} sourceMap
* @param {PredefinedOptions & CustomOptions} minimizerOptions
* @param {ExtractCommentsOptions | undefined} extractComments
* @return {Promise<MinimizedResult>}
*/
export function uglifyJsMinify(
input: Input,
sourceMap: SourceMapInput | undefined,
minimizerOptions: PredefinedOptions & CustomOptions,
extractComments: ExtractCommentsOptions | undefined
): Promise<MinimizedResult>;
export namespace uglifyJsMinify {
/**
* @returns {string | undefined}
*/
function getMinimizerVersion(): string | undefined;
}
/**
* @param {Input} input
* @param {SourceMapInput | undefined} sourceMap
* @param {PredefinedOptions & CustomOptions} minimizerOptions
* @return {Promise<MinimizedResult>}
*/
export function swcMinify(
input: Input,
sourceMap: SourceMapInput | undefined,
minimizerOptions: PredefinedOptions & CustomOptions
): Promise<MinimizedResult>;
export namespace swcMinify {
/**
* @returns {string | undefined}
*/
function getMinimizerVersion(): string | undefined;
}
/**
* @param {Input} input
* @param {SourceMapInput | undefined} sourceMap
* @param {PredefinedOptions & CustomOptions} minimizerOptions
* @return {Promise<MinimizedResult>}
*/
export function esbuildMinify(
input: Input,
sourceMap: SourceMapInput | undefined,
minimizerOptions: PredefinedOptions & CustomOptions
): Promise<MinimizedResult>;
export namespace esbuildMinify {
/**
* @returns {string | undefined}
*/
function getMinimizerVersion(): string | undefined;
}