npm package: @stefanprobst/val-loader
Description: val loader module for webpack
Author: Tobias Koppers @sokra
Latest version: 4.0.0-4

<div align="center">
  <a href="https://github.com/webpack/webpack">
    <img width="200" height="200" src="https://webpack.js.org/assets/icon-square-big.svg">
  </a>
</div>

[![npm][npm]][npm-url]
[![node][node]][node-url]
[![deps][deps]][deps-url]
[![tests][tests]][tests-url]
[![coverage][cover]][cover-url]
[![chat][chat]][chat-url]
[![size][size]][size-url]

# val-loader

A webpack loader which executes a given module, and returns the result of the
execution at build-time, when the module is required in the bundle. In this way,
the loader changes a module from code to a result.

Another way to view `val-loader`, is that it allows a user a way to make their
own custom loader logic, without having to write a custom loader.

The target module is called with two arguments: `(options, loaderContext)`

- `options`: The loader options (for instance provided in the webpack config. See the [example](#examples) below).
- `loaderContext`: [The loader context](https://webpack.js.org/api/loaders/#the-loader-context).

## Getting Started

To begin, you'll need to install `val-loader`:

```console
$ npm install val-loader --save-dev
```

Then add the loader to your `webpack` config. For example:

**target-file.js**

```js
module.exports = (options, loaderContext) => {
  return { code: "module.exports = 42;" };
};
```

**webpack.config.js**

```js
module.exports = {
  module: {
    rules: [
      {
        test: /target-file.js$/,
        use: [
          {
            loader: `val-loader`,
          },
        ],
      },
    ],
  },
};
```

**src/entry.js**

```js
const answer = require("target-file");
```

And run `webpack` via your preferred method.

## Options

|                  Name                   |    Type    |   Default   | Description                                   |
| :-------------------------------------: | :--------: | :---------: | :-------------------------------------------- |
| **[`executableFile`](#executableFile)** | `{String}` | `undefined` | Allows to specify path to the executable file |

### executableFile

Type: `String`
Default: `undefined`

Allows to specify path to the executable file

**data.json**

```json
{
  "years": "10"
}
```

**executable-file.js**

```js
module.exports = function yearsInMs(options, loaderContext, content) {
  const { years } = JSON.parse(content);
  const value = years * 365 * 24 * 60 * 60 * 1000;

  return {
    cacheable: true,
    code: "module.exports = " + value,
  };
};
```

**webpack.config.js**

```js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(json)$/i,
        rules: [
          {
            loader: "val-loader",
            options: {
              executableFile: path.resolve(
                __dirname,
                "fixtures",
                "executableFile.js"
              ),
            },
          },
        ],
      },
      {
        test: /\.json$/i,
        type: "asset/resource",
      },
    ],
  },
};
```

## Return Object Properties

Targeted modules of this loader must export a `Function` that returns an object,
or a `Promise` resolving an object (e.g. async function), containing a `code` property at a minimum, but can
contain any number of additional properties.

### `code`

Type: `String|Buffer`
Default: `undefined`
_Required_

Code passed along to webpack or the next loader that will replace the module.

### `sourceMap`

Type: `Object`
Default: `undefined`

A source map passed along to webpack or the next loader.

### `ast`

Type: `Array[Object]`
Default: `undefined`

An [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree)
that will be passed to the next loader. Useful to speed up the build time if the
next loader uses the same AST.

### `dependencies`

Type: `Array[String]`
Default: `[]`

An array of absolute, native paths to file dependencies that should be watched by webpack for changes.

Dependencies can also be added using [`loaderContext.addDependency(file: string)`](https://webpack.js.org/api/loaders/#thisadddependency).

### `contextDependencies`

Type: `Array[String]`
Default: `[]`

An array of absolute, native paths to directory dependencies that should be watched by webpack for changes.

Context dependencies can also be added using [`loaderContext.addContextDependency(directory: string)`](https://webpack.js.org/api/loaders/#thisaddcontextdependency).

### `buildDependencies`

Type: `Array[String]`
Default: `[]`

An array of absolute, native paths to directory dependencies that should be watched by webpack for changes.

Build dependencies can also be added using `loaderContext.addBuildDependency(file: string)`.

### `cacheable`

Type: `Boolean`
Default: `false`

If `true`, specifies that the code can be re-used in watch mode if none of the
`dependencies` have changed.

## Examples

### Simple

In this example the loader is configured to operator on a file name of
`years-in-ms.js`, execute the code, and store the result in the bundle as the
result of the execution. This example passes `years` as an `option`, which
corresponds to the `years` parameter in the target module exported function:

**years-in-ms.js**

```js
module.exports = function yearsInMs({ years }) {
  const value = years * 365 * 24 * 60 * 60 * 1000;

  // NOTE: this return value will replace the module in the bundle
  return {
    cacheable: true,
    code: "module.exports = " + value,
  };
};
```

**webpack.config.js**

```js
module.exports = {
  module: {
    rules: [
      {
        test: require.resolve("src/years-in-ms.js"),
        use: [
          {
            loader: "val-loader",
            options: {
              years: 10,
            },
          },
        ],
      },
    ],
  },
};
```

In the bundle, requiring the module then returns:

```js
import tenYearsMs from "years-in-ms";

console.log(tenYearsMs); // 315360000000
```

### Modernizr

Example shows how to build [`modernizr`](https://www.npmjs.com/package/modernizr).

**entry.js**

```js
import modenizr from "./modernizr.js";
```

**modernizr.js**

```js
const modernizr = require("modernizr");

module.exports = function (options) {
  return new Promise(function (resolve) {
    // It is impossible to throw an error because modernizr causes the process.exit(1)
    modernizr.build(options, function (output) {
      resolve({
        cacheable: true,
        code: `var modernizr; var hadGlobal = 'Modernizr' in window; var oldGlobal = window.Modernizr; ${output} modernizr = window.Modernizr; if (hadGlobal) { window.Modernizr = oldGlobal; } else { delete window.Modernizr; } export default modernizr;`,
      });
    });
  });
};
```

**webpack.config.js**

```js
const path = require("path");
module.exports = {
  module: {
    rules: [
      {
        test: path.resolve(__dirname, "src", "modernizr.js"),
        use: [
          {
            loader: "val-loader",
            options: {
              minify: false,
              options: ["setClasses"],
              "feature-detects": [
                "test/css/flexbox",
                "test/es6/promises",
                "test/serviceworker",
              ],
            },
          },
        ],
      },
    ],
  },
};
```

### Figlet

Example shows how to build [`figlet`](https://www.npmjs.com/package/figlet).

**entry.js**

```js
import { default as figlet } from "./figlet.js";

console.log(figlet);
```

**figlet.js**

```js
const figlet = require("figlet");

function wrapOutput(output, config) {
  let figletOutput = "";

  if (config.textBefore) {
    figletOutput += encodeURI(`${config.textBefore}\n`);
  }

  output.split("\n").forEach((line) => {
    figletOutput += encodeURI(`${line}\n`);
  });

  if (config.textAfter) {
    figletOutput += encodeURI(`${config.textAfter}\n`);
  }

  return `module.exports = decodeURI("${figletOutput}");`;
}

module.exports = function (options) {
  const defaultConfig = {
    fontOptions: {
      font: "ANSI Shadow",
      horizontalLayout: "default",
      kerning: "default",
      verticalLayout: "default",
    },
    text: "FIGLET-LOADER",
    textAfter: null,
    textBefore: null,
  };

  const config = Object.assign({}, defaultConfig, options);

  return new Promise(function (resolve, reject) {
    figlet.text(config.text, config.fontOptions, (error, output) => {
      if (error) {
        return reject(error);
      }

      resolve({
        cacheable: true,
        code: "module.exports = " + wrapOutput(output, config),
      });
    });
  });
};
```

**webpack.config.js**

```js
const path = require("path");
module.exports = {
  module: {
    rules: [
      {
        test: path.resolve(__dirname, "src", "figlet.js"),
        use: [
          {
            loader: "val-loader",
            options: {
              text: "FIGLET",
            },
          },
        ],
      },
    ],
  },
};
```

## 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/val-loader.svg
[npm-url]: https://npmjs.com/package/val-loader
[node]: https://img.shields.io/node/v/val-loader.svg
[node-url]: https://nodejs.org
[deps]: https://david-dm.org/webpack-contrib/val-loader.svg
[deps-url]: https://david-dm.org/webpack-contrib/val-loader
[tests]: https://github.com/webpack-contrib/val-loader/workflows/val-loader/badge.svg
[tests-url]: https://github.com/webpack-contrib/val-loader/actions
[cover]: https://codecov.io/gh/webpack-contrib/val-loader/branch/master/graph/badge.svg
[cover-url]: https://codecov.io/gh/webpack-contrib/val-loader
[chat]: https://img.shields.io/badge/gitter-webpack%2Fwebpack-brightgreen.svg
[chat-url]: https://gitter.im/webpack/webpack
[size]: https://packagephobia.now.sh/badge?p=val-loader
[size-url]: https://packagephobia.now.sh/result?p=val-loader