Skip to content

Commit

Permalink
refactor: wrapper option (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
evilebottnawi committed Jun 17, 2020
1 parent c7f8799 commit 574b5f6
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 52 deletions.
116 changes: 102 additions & 14 deletions README.md
Expand Up @@ -17,7 +17,10 @@

The imports loader allows you to use modules that depend on specific global variables.

This is useful for third-party modules that rely on global variables like `$` or `this` being the `window` object. The imports loader can add the necessary `require('whatever')` calls, so those modules work with webpack.
This is useful for third-party modules that rely on global variables like `$` or `this` being the `window` object.
The imports loader can add the necessary `require('whatever')` calls, so those modules work with webpack.

For further hints on compatibility issues, check out [Shimming](https://webpack.js.org/guides/shimming/) of the official docs.

> ⚠ By default loader generate ES module named syntax.
Expand All @@ -39,7 +42,7 @@ Given you have this file:
$('img').doSomeAwesomeJqueryPluginStuff();
```

then you can inject the `jquery` value into the module by configuring the `imports-loader` using two approaches.
Then you can inject the `jquery` value into the module by configuring the `imports-loader` using two approaches.

### Inline

Expand Down Expand Up @@ -82,7 +85,9 @@ import(
// import angular from "angular";
//
// (function () {
// code from example.js
// ...
// Code
// ...
// }.call(window));
```

Expand Down Expand Up @@ -139,10 +144,12 @@ And run `webpack` via your preferred method.

## Options

| Name | Type | Default | Description |
| :-----------------------: | :---------------------------------------: | :---------: | :-------------------------- |
| **[`type`](#type)** | `{String}` | `module` | Format of generated imports |
| **[`imports`](#imports)** | `{String\|Object\|Array<String\|Object>}` | `undefined` | List of imports |
| Name | Type | Default | Description |
| :-------------------------------------: | :---------------------------------------: | :---------: | :--------------------------------------------------------------------- |
| **[`type`](#type)** | `{String}` | `module` | Format of generated imports |
| **[`imports`](#imports)** | `{String\|Object\|Array<String\|Object>}` | `undefined` | List of imports |
| **[`wrapper`](#wrapper)** | `{Boolean\|String\|Object}` | `undefined` | Closes the module code in a function (`(function () { ... }).call();`) |
| **[`additionalCode`](#additionalcode)** | `{String}` | `undefined` | Adds custom code |

### `type`

Expand Down Expand Up @@ -443,12 +450,16 @@ import 'lib_4';
// ...
```

### wrapper
### `wrapper`

Type: `String|Array`
Type: `Boolean|String|Object`
Default: `undefined`

Closes the module code in a function with a given `this` (`(function () { ... }).call(window);`).
Closes the module code in a function with a given `thisArg` and `args` (`(function () { ... }).call();`).

> ⚠ Do not use this option if source code contains ES module import(s)
#### `Boolean`

```js
module.exports = {
Expand All @@ -464,7 +475,7 @@ module.exports = {
moduleName: 'jquery',
name: '$',
},
wrapper: ['window', 'document'],
wrapper: true,
},
},
],
Expand All @@ -483,12 +494,89 @@ import $ from 'jquery';
// ...
// Code
// ...
}.call(window, document));
}.call());
```

> ⚠ Do not use this option if source code contains ES module import(s)
#### `String`

```js
module.exports = {
module: {
rules: [
{
test: require.resolve('example.js'),
use: [
{
loader: 'imports-loader',
options: {
imports: {
moduleName: 'jquery',
name: '$',
},
wrapper: 'window',
},
},
],
},
],
},
};
```

Generate output:

```js
import $ from 'jquery';

(function () {
// ...
// Code
// ...
}.call(window));
```

#### `Object`

```js
module.exports = {
module: {
rules: [
{
test: require.resolve('example.js'),
use: [
{
loader: 'imports-loader',
options: {
imports: {
moduleName: 'jquery',
name: '$',
},
wrapper: {
thisArg: 'window',
args: ['myVariable', 'myOtherVariable'],
},
},
},
],
},
],
},
};
```

Generate output:

```js
import $ from 'jquery';

(function (myVariable, myOtherVariable) {
// ...
// Code
// ...
}.call(window, myVariable, myOtherVariable));
```

### additionalCode
### `additionalCode`

Type: `String`
Default: `undefined`
Expand Down
24 changes: 18 additions & 6 deletions src/index.js
Expand Up @@ -4,7 +4,7 @@
*/

import { SourceNode, SourceMapConsumer } from 'source-map';
import { getOptions, getCurrentRequest } from 'loader-utils';
import { getOptions } from 'loader-utils';
import validateOptions from 'schema-utils';

import schema from './options.json';
Expand Down Expand Up @@ -54,8 +54,22 @@ export default function loader(content, sourceMap) {
let codeAfterModule = '';

if (typeof options.wrapper !== 'undefined') {
importsCode += '\n(function() {';
codeAfterModule += `\n}.call(${options.wrapper.toString()}));\n`;
let thisArg;
let args;

if (typeof options.wrapper === 'boolean') {
thisArg = '';
args = '';
} else if (typeof options.wrapper === 'string') {
thisArg = options.wrapper;
args = '';
} else {
({ thisArg, args } = options.wrapper);
args = args.join(', ');
}

importsCode += `\n(function(${args}) {`;
codeAfterModule += `\n}.call(${thisArg}${args ? `, ${args}` : ''}));\n`;
}

if (this.sourceMap && sourceMap) {
Expand All @@ -67,9 +81,7 @@ export default function loader(content, sourceMap) {
node.prepend(`${importsCode}\n`);
node.add(codeAfterModule);

const result = node.toStringWithSourceMap({
file: getCurrentRequest(this),
});
const result = node.toStringWithSourceMap({ file: this.resourcePath });

callback(null, result.code, result.map.toJSON());

Expand Down
26 changes: 20 additions & 6 deletions src/options.json
Expand Up @@ -66,17 +66,31 @@
},
"wrapper": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string",
"minLength": 1
},
{
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
"type": "object",
"additionalProperties": false,
"properties": {
"thisArg": {
"type": "string",
"minLength": 1
},
"args": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
}
},
"required": ["thisArg"]
}
]
},
Expand Down
33 changes: 25 additions & 8 deletions test/__snapshots__/loader.test.js.snap
Expand Up @@ -1103,9 +1103,9 @@ var someCode = {

exports[`loader should work with the "additionalCode" option: warnings 1`] = `Array []`;

exports[`loader should work with the "wrapper" option: errors 1`] = `Array []`;
exports[`loader should work with the "wrapper" option as a boolean notation: errors 1`] = `Array []`;

exports[`loader should work with the "wrapper" option: module 1`] = `
exports[`loader should work with the "wrapper" option as a boolean notation: module 1`] = `
"/*** IMPORTS FROM imports-loader ***/
(function() {
Expand All @@ -1114,15 +1114,15 @@ var someCode = {
object: { existingSubProperty: 123 }
};
}.call(window));
}.call());
"
`;

exports[`loader should work with the "wrapper" option: warnings 1`] = `Array []`;
exports[`loader should work with the "wrapper" option as a boolean notation: warnings 1`] = `Array []`;

exports[`loader should work with the "wrapper" options and arguments: errors 1`] = `Array []`;
exports[`loader should work with the "wrapper" option as a string notation: errors 1`] = `Array []`;

exports[`loader should work with the "wrapper" options and arguments: module 1`] = `
exports[`loader should work with the "wrapper" option as a string notation: module 1`] = `
"/*** IMPORTS FROM imports-loader ***/
(function() {
Expand All @@ -1131,8 +1131,25 @@ var someCode = {
object: { existingSubProperty: 123 }
};
}.call(window,document));
}.call(window));
"
`;

exports[`loader should work with the "wrapper" option as a string notation: warnings 1`] = `Array []`;

exports[`loader should work with the "wrapper" options as an object notation: errors 1`] = `Array []`;

exports[`loader should work with the "wrapper" options as an object notation: module 1`] = `
"/*** IMPORTS FROM imports-loader ***/
(function(myGlobalVariable, myOtherGlobalVariable) {
var someCode = {
number: 123,
object: { existingSubProperty: 123 }
};
}.call(window, myGlobalVariable, myOtherGlobalVariable));
"
`;

exports[`loader should work with the "wrapper" options and arguments: warnings 1`] = `Array []`;
exports[`loader should work with the "wrapper" options as an object notation: warnings 1`] = `Array []`;
57 changes: 44 additions & 13 deletions test/__snapshots__/validate-options.test.js.snap
Expand Up @@ -229,32 +229,63 @@ exports[`validate options should throw an error on the "unknown" option with "tr
* options misses the property 'additionalCode' | should be any non-object."
`;
exports[`validate options should throw an error on the "wrapper" option with "[""]" value 1`] = `
exports[`validate options should throw an error on the "wrapper" option with "/test/" value 1`] = `
"Invalid options object. Imports Loader has been initialized using an options object that does not match the API schema.
- options.wrapper[0] should be an non-empty string."
- options.wrapper misses the property 'thisArg'. Should be:
non-empty string"
`;
exports[`validate options should throw an error on the "wrapper" option with "[]" value 1`] = `
exports[`validate options should throw an error on the "wrapper" option with "[""]" value 1`] = `
"Invalid options object. Imports Loader has been initialized using an options object that does not match the API schema.
- options.wrapper should be an non-empty array."
- options.wrapper should be one of these:
boolean | non-empty string | object { thisArg, args? }
Details:
* options.wrapper should be a boolean.
* options.wrapper should be a non-empty string.
* options.wrapper should be an object:
object { thisArg, args? }"
`;
exports[`validate options should throw an error on the "wrapper" option with "false" value 1`] = `
exports[`validate options should throw an error on the "wrapper" option with "[]" value 1`] = `
"Invalid options object. Imports Loader has been initialized using an options object that does not match the API schema.
- options.wrapper should be one of these:
non-empty string | [non-empty string, ...] (should not have fewer than 1 item)
boolean | non-empty string | object { thisArg, args? }
Details:
* options.wrapper should be a boolean.
* options.wrapper should be a non-empty string.
* options.wrapper should be an array:
[non-empty string, ...] (should not have fewer than 1 item)"
* options.wrapper should be an object:
object { thisArg, args? }"
`;
exports[`validate options should throw an error on the "wrapper" option with "{"thisArg":"window","args":[1,"bar"]}" value 1`] = `
"Invalid options object. Imports Loader has been initialized using an options object that does not match the API schema.
- options.wrapper.args[0] should be a non-empty string."
`;
exports[`validate options should throw an error on the "wrapper" option with "true" value 1`] = `
exports[`validate options should throw an error on the "wrapper" option with "{"thisArg":"window","args":true}" value 1`] = `
"Invalid options object. Imports Loader has been initialized using an options object that does not match the API schema.
- options.wrapper.args should be an array:
[non-empty string, ...] (should not have fewer than 1 item)"
`;
exports[`validate options should throw an error on the "wrapper" option with "{"thisArg":1}" value 1`] = `
"Invalid options object. Imports Loader has been initialized using an options object that does not match the API schema.
- options.wrapper.thisArg should be a non-empty string."
`;
exports[`validate options should throw an error on the "wrapper" option with "{"unknown":true}" value 1`] = `
"Invalid options object. Imports Loader has been initialized using an options object that does not match the API schema.
- options.wrapper should be one of these:
non-empty string | [non-empty string, ...] (should not have fewer than 1 item)
boolean | non-empty string | object { thisArg, args? }
Details:
* options.wrapper should be a non-empty string.
* options.wrapper should be an array:
[non-empty string, ...] (should not have fewer than 1 item)"
* options.wrapper has an unknown property 'unknown'. These properties are valid:
object { thisArg, args? }
* options.wrapper misses the property 'thisArg'. Should be:
non-empty string"
`;
exports[`validate options should throw an error on the "wrapper" option with "{}" value 1`] = `
"Invalid options object. Imports Loader has been initialized using an options object that does not match the API schema.
- options.wrapper misses the property 'thisArg'. Should be:
non-empty string"
`;

0 comments on commit 574b5f6

Please sign in to comment.