Skip to content

Commit 46bd532

Browse files
refactor: override
1 parent 748798b commit 46bd532

File tree

12 files changed

+676
-128
lines changed

12 files changed

+676
-128
lines changed

README.md

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
# expose-loader
1616

17-
The `expose-loader` loader allow you to expose a module (in whole or in part) to `global` scope (`self`, `window` and `global`).
17+
The `expose-loader` loader allows to expose a module (in whole or in part) to global object (`self`, `window` and `global`).
1818

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

@@ -30,33 +30,37 @@ Then you can use the `expose-loader` using two approaches.
3030

3131
## Inline
3232

33-
The `|` or `%20` (space) separate command parts.
34-
35-
> `%20` is space in a query string, because you can't use spaces in URLs
36-
3733
**src/index.js**
3834

3935
```js
4036
import $ from 'expose-loader?exposes[]=$&exposes[]=jQuery!jquery';
4137
//
42-
// Adds the `jquery` to the `global` object under the names `$` and `jQuery`
38+
// Adds the `jquery` to the global object under the names `$` and `jQuery`
4339
```
4440

41+
**src/index.js**
42+
4543
```js
4644
import { concat } from 'expose-loader?exposes=_.concat!lodash/concat';
4745
//
48-
// Adds the `lodash/concat` to the `global` object under the name `_.concat`
46+
// Adds the `lodash/concat` to the global object under the name `_.concat`
4947
```
5048

49+
**src/index.js**
50+
5151
```js
5252
import {
5353
map,
5454
reduce,
5555
} from 'expose-loader?exposes[]=_.map|map&exposes[]=_.reduce|reduce!underscore';
5656
//
57-
// Adds the `map` and `reduce` method from `underscore` to the `global` object under the name `_.map` and `_.reduce`
57+
// Adds the `map` and `reduce` method from `underscore` to the global object under the name `_.map` and `_.reduce`
5858
```
5959

60+
The `|` or `%20` (space) allow to separate the export name of the module and the name in the global object.
61+
62+
> `%20` is space in a query string, because you can't use spaces in URLs
63+
6064
Description of string values can be found in the documentation below.
6165

6266
## Using Configuration
@@ -125,12 +129,13 @@ List of exposes.
125129

126130
Allows to use a string to describe an expose.
127131

128-
String syntax - `[[globalName] [moduleLocalName]]` or `[[globalName]|[moduleLocalName]]`, where:
132+
String syntax - `[[globalName] [moduleLocalName] [override]]` or `[[globalName]|[moduleLocalName]|[override]]`, where:
129133

130-
- `globalName` - the name under which the value will be available in the global scope, for example `windows.$` for a browser environment (**required**)
131-
- `moduleLocalName` - the name of method or variable (module should export it) (**may be omitted**)
134+
- `globalName` - the name in the global object, for example `window.$` for a browser environment (**required**)
135+
- `moduleLocalName` - the name of method/variable/etc of the module (the module must export it) (**may be omitted**)
136+
- `override` - allows to override existing value in the global object (**may be omitted**)
132137

133-
If no `moduleLocalName` is specified, it exposes the entire module to global scope, otherwise it exposes only the `moduleLocalName` value.
138+
If `moduleLocalName` is not specified, it exposes the entire module to the global object, otherwise it exposes only the value of `moduleLocalName`.
134139

135140
**src/index.js**
136141

@@ -166,7 +171,7 @@ Allows to use an object to describe an expose.
166171
Type: `String|Array<String>`
167172
Default: `undefined`
168173

169-
Name of an exposed value in `global` scope (**required**).
174+
The name in the global object. (**required**).
170175

171176
**src/index.js**
172177

@@ -201,9 +206,8 @@ module.exports = {
201206
Type: `String`
202207
Default: `undefined`
203208

204-
Name of method or variable (module should export it).
205-
206-
If the `moduleLocalName` option is specified, it exposes only the `moduleLocalName` value.
209+
The name of method/variable/etc of the module (the module must export it).
210+
If `moduleLocalName` is specified, it exposes only the value of `moduleLocalName`.
207211

208212
**src/index.js**
209213

@@ -232,6 +236,44 @@ module.exports = {
232236
};
233237
```
234238

239+
##### `override`
240+
241+
Type: `Boolean`
242+
Default: `false`
243+
244+
By default loader does not override the existing value in the global object, because it is unsafe.
245+
In `development` mode, we throw an error if the value already present in the global object.
246+
But you can configure loader to override the existing value in the global object using this option.
247+
248+
To force override the value that is already present in the global object you can set the `override` option to the `true` value.
249+
250+
**src/index.js**
251+
252+
```js
253+
import $ from 'jquery';
254+
```
255+
256+
**webpack.config.js**
257+
258+
```js
259+
module.exports = {
260+
module: {
261+
rules: [
262+
{
263+
test: require.resolve('jquery'),
264+
loader: 'expose-loader',
265+
options: {
266+
exposes: {
267+
globalName: '$',
268+
override: true,
269+
},
270+
},
271+
},
272+
],
273+
},
274+
};
275+
```
276+
235277
#### `Array`
236278

237279
**src/index.js**
@@ -268,7 +310,7 @@ module.exports = {
268310
};
269311
```
270312

271-
It will expose **only** `map`, `filter` and `find` (under `myNameForFind` name) methods in global scope.
313+
It will expose **only** `map`, `filter` and `find` (under `myNameForFind` name) methods to the global object.
272314

273315
In a browser these methods will be available under `windows._.map(..args)`, `windows._.filter(...args)` and `windows._.myNameForFind(...args)` methods.
274316

src/index.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ export default function loader() {
5656
this,
5757
require.resolve('./runtime/getGlobalThis.js')
5858
)});\n`;
59-
code += `var ___EXPOSE_LOADER_GLOBAL_THIS___ = ___EXPOSE_LOADER_GET_GLOBAL_THIS___();\n`;
59+
code += `var ___EXPOSE_LOADER_GLOBAL_THIS___ = ___EXPOSE_LOADER_GET_GLOBAL_THIS___;\n`;
6060

6161
for (const expose of exposes) {
62-
const { globalName, moduleLocalName } = expose;
62+
const { globalName, moduleLocalName, override } = expose;
6363
const globalNameInterpolated = globalName.map((item) =>
6464
interpolateName(this, item, {})
6565
);
@@ -72,16 +72,28 @@ export default function loader() {
7272

7373
for (let i = 0; i < globalName.length; i++) {
7474
if (i > 0) {
75-
code += `if (!${propertyString}) ${propertyString} = {};\n`;
75+
code += `if (typeof ${propertyString} === 'undefined') ${propertyString} = {};\n`;
7676
}
7777

7878
propertyString += `[${JSON.stringify(globalNameInterpolated[i])}]`;
7979
}
8080

81+
if (!override) {
82+
code += `if (typeof ${propertyString} === 'undefined') `;
83+
}
84+
8185
code +=
8286
typeof moduleLocalName !== 'undefined'
8387
? `${propertyString} = ___EXPOSE_LOADER_IMPORT_MODULE_LOCAL_NAME___;\n`
8488
: `${propertyString} = ___EXPOSE_LOADER_IMPORT___;\n`;
89+
90+
if (!override) {
91+
if (this.mode === 'development') {
92+
code += `else throw new Error('[exposes-loader] The "${globalName.join(
93+
'.'
94+
)}" value exists in the global scope, it may not be safe to overwrite it, use the "override" option')\n`;
95+
}
96+
}
8597
}
8698

8799
code += `module.exports = ___EXPOSE_LOADER_IMPORT___;\n`;

src/options.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
"moduleLocalName": {
2424
"type": "string",
2525
"minLength": 1
26+
},
27+
"override": {
28+
"type": "boolean"
2629
}
2730
},
2831
"required": ["globalName"]

src/runtime/getGlobalThis.js

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,31 @@
1-
module.exports = function getGlobalThis() {
2-
if (typeof globalThis !== 'undefined') {
1+
// eslint-disable-next-line func-names
2+
module.exports = (function () {
3+
if (typeof globalThis === 'object') {
34
return globalThis;
45
}
56

6-
if (typeof self !== 'undefined') {
7-
return self;
8-
}
7+
let g;
98

10-
if (typeof window !== 'undefined') {
11-
return window;
12-
}
9+
try {
10+
// This works if eval is allowed (see CSP)
11+
// eslint-disable-next-line no-new-func
12+
g = this || new Function('return this')();
13+
} catch (e) {
14+
// This works if the window reference is available
15+
if (typeof window === 'object') {
16+
return window;
17+
}
18+
19+
// This works if the self reference is available
20+
if (typeof self === 'object') {
21+
return self;
22+
}
1323

14-
if (typeof global !== 'undefined') {
15-
return global;
24+
// This works if the global reference is available
25+
if (typeof global !== 'undefined') {
26+
return global;
27+
}
1628
}
1729

18-
throw new Error('unable to locate global object');
19-
};
30+
return g;
31+
})();

src/utils.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,39 @@ function splitCommand(command) {
3333
return result;
3434
}
3535

36+
function parseBoolean(string, defaultValue = null) {
37+
if (typeof string === 'undefined') {
38+
return defaultValue;
39+
}
40+
41+
switch (string.toLowerCase()) {
42+
case 'true':
43+
return true;
44+
case 'false':
45+
return false;
46+
default:
47+
return defaultValue;
48+
}
49+
}
50+
3651
function resolveExposes(item) {
3752
let result;
3853

3954
if (typeof item === 'string') {
4055
const splittedItem = splitCommand(item.trim());
4156

42-
if (splittedItem.length > 2) {
57+
if (splittedItem.length > 3) {
4358
throw new Error(`Invalid "${item}" for exposes`);
4459
}
4560

4661
result = {
4762
globalName: splittedItem[0],
4863
moduleLocalName: splittedItem[1],
64+
override:
65+
typeof splittedItem[2] !== 'undefined'
66+
? parseBoolean(splittedItem[2], false)
67+
: // eslint-disable-next-line no-undefined
68+
undefined,
4969
};
5070
} else {
5171
result = item;

0 commit comments

Comments
 (0)