Skip to content

Commit f9e5859

Browse files
committed
1.1.0 Multiple replacement
1 parent 8a55f56 commit f9e5859

21 files changed

+252
-64
lines changed

CONTRIBUTING.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,16 @@ That should be message in terminal:
3838

3939
```bash
4040
Message from replacement.js
41+
Message from replacement-a.js
42+
Message from replacement-b.js
4143
```
4244

4345
This means that file-replace-loader works.
4446

4547
**NOTE:** Example dependencies has link to **local** file-replace-loader.<br/>
4648
Look in `example/[3x|4x]/package.json`:
4749

48-
```javascript
50+
```json
4951
{
5052
"devDependencies": {
5153
"file-replace-loader": "file:../../"

README.md

+86-7
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ file-replace-loader is webpack loader that allows you replace files in compile t
88
## Features
99

1010
* Compatibility with webpack 3.x, 4.x;
11-
* Supports watch webpack mode;
11+
* Support watch webpack mode;
1212
* Replace files in compile time without change source files;
13+
* Multiple replacement;
1314
* Sync and async modes;
1415
* Compatibility with other loaders;
1516
* Support binary files.
@@ -43,11 +44,86 @@ module.exports = {
4344
}
4445
```
4546

46-
This example rule will replace all of imports `/\.config.js$/` to `config.local.js` file, <br/>if replacement exists (condition `if-replacement-exists`).
47+
This example rule replaces all of imports `/\.config.js$/` to `config.local.js` file, <br/>if replacement exists (condition `if-replacement-exists`).
4748

48-
After example build in bundle file will some code from `config.local.js` and original sources
49+
After this build a bundle file will contain code from `config.local.js` and original sources
4950
won't changed.
5051

52+
## Multiple replace
53+
54+
To describe replace rules for two or more files you can use function as replacement value.<br/>
55+
56+
How does it work?
57+
1. Webpack runs file-replace-loader according to `test` rule, `include` and `exclude` rule options;
58+
2. file-replace-loader looks on `replacement` option. If it is string then the loader just replace a file. If it is a function
59+
then file-replace-loader checking what it returns. If the function returns a path to file then the loader
60+
replaces, if returns nothing then current match skips.
61+
3. If `replacement` function returns a path then file-replace-loader looks to `condition`. If condition is `always` then it replace every match. If `condition` is
62+
`if-replacement-exists` then loader checking existing file, etc;
63+
64+
For example:
65+
66+
```javascript
67+
const { resolve } = require('path');
68+
69+
module.exports = {
70+
//...
71+
module: {
72+
rules: [{
73+
test: /\.js$/,
74+
loader: 'file-replace-loader',
75+
options: {
76+
condition: 'always', // <-- Note that the rule applies for all files!
77+
replacement(resourcePath) {
78+
if (resourcePath.endsWith('foo.js')) {
79+
return resolve('./bar.js');
80+
}
81+
if (resourcePath.endsWith('foo-a.js')) {
82+
return resolve('./bar-a.js');
83+
}
84+
},
85+
async: true,
86+
}
87+
}]
88+
}
89+
}
90+
```
91+
92+
file-replace-loader passes to `replacement` function `resourcePath` for every matching.
93+
file-replace-loader doesn't care what developer does with this path but if `repalcement` function returns a new path then file-replace-loader replaces file.
94+
If `replacement` function returns nothing then file-replace-loading skip replace for current `resourcePath`.
95+
96+
Example with mapping:
97+
98+
```javascript
99+
const { resolve } = require('path');
100+
101+
module.exports = {
102+
//...
103+
module: {
104+
rules: [{
105+
test: /\.js$/,
106+
loader: 'file-replace-loader',
107+
options: {
108+
condition: 'always', // <-- Note that the rule applies for all files! But you can use other conditions too
109+
replacement(resourcePath) {
110+
const mapping = {
111+
[resolve('./src/foo-a.js')]: resolve('./src/bar-a.js'),
112+
[resolve('./src/foo-b.js')]: resolve('./src/bar-b.js'),
113+
[resolve('./src/foo-c.js')]: resolve('./src/bar-c.js'),
114+
};
115+
return mapping[resourcePath];
116+
},
117+
async: true,
118+
}
119+
}]
120+
}
121+
}
122+
```
123+
124+
**NOTE:** Make shure that all replacement files contains necessary imports and exports
125+
that other files are expecting.
126+
51127
## Using with binary files
52128

53129
file-replace-loader allows replace binary files. <br/>For example:
@@ -81,7 +157,7 @@ module.exports = {
81157

82158
## Using with other loaders
83159

84-
file-replace-loader must executes before other loaders. It means that in webpack config file the loader must be last in list. <br/>For example:
160+
file-replace-loader must executes before other loaders. This means that in webpack config file the loader must be last in list. <br/>For example:
85161

86162
```javascript
87163
//webpack.config.js
@@ -140,12 +216,15 @@ module.exports = {
140216
}
141217
```
142218

219+
In incorrect example above file-replace-loader first in rule list.
220+
This case throw an error because file-replace-loader should be last in list.
221+
143222
## Loader options
144223

145-
| Key | Type | Required | Default | Possible values
224+
| Option | Type | Required | Default | Possible values
146225
| ------------ | ------------- | ------------- | ------------- | -------------
147-
| `condition`<br/>Condition to replace | `enum` | no | `'if-replacement-exists'` | `true`,<br/>`false`,<br/>`'always'`,<br/>`'never'`,<br/>`'if-replacement-exists'`,<br/>`'if-source-is-empty'`
148-
| `replacement`<br/>Replacement file | `string` | yes | — | Full path to file
226+
| `condition`<br/>Condition to replace | `string`&#124;`boolean` | no | `'if-replacement-exists'` | `true`,<br/>`false`,<br/>`'always'`,<br/>`'never'`,<br/>`'if-replacement-exists'`,<br/>`'if-source-is-empty'`
227+
| `replacement`<br/>Replacement file | `string`&#124;`Function` | yes | — | Full path to file or function returning full path to file
149228
| `async`<br/>Asynchronous file reading | `boolean` | no | `true` | `true`,<br/>`false`
150229

151230
## Contributing

dist/constants.js

+17-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ var _path = require("path");
99

1010
var packageJson = _interopRequireWildcard(require("../package.json"));
1111

12-
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
12+
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
13+
14+
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (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; }
1315

1416
/**
1517
* Constants of file replace loader
@@ -59,12 +61,14 @@ ERROR_TYPES[0] = 'Invalid options';
5961
ERROR_TYPES[1] = 'Replacement error';
6062
ERROR_TYPES[2] = 'File reading error';
6163
ERROR_TYPES[3] = 'Usage error';
64+
var HELP_INFO_MESSAGE = `If you are experiencing difficulties to solve this problem please create an issue on ${packageJson.bugs.url}`;
6265
var ERROR_MESSAGES = [];
6366
exports.ERROR_MESSAGES = ERROR_MESSAGES;
64-
ERROR_MESSAGES[0] = `File ($1) doesn't exist but specified in ${LOADER_NAME} options with \n` + ` condition ${LOADER_REPLACEMENT_CONDITIONS[1]} or '${LOADER_REPLACEMENT_CONDITIONS[2]}'. \n` + ` Perhaps this is due replacement isn't full path. Make sure that file exists and replacement\n` + ` option is full path to file.\n` + ` If you are experiencing difficulties to solve this problem, you can create an issue on ${packageJson.bugs.url}`;
65-
ERROR_MESSAGES[1] = `File ($1) doesn't exist but specified in replacement. ${LOADER_NAME} can't replace\n` + ` it by '${LOADER_REPLACEMENT_CONDITIONS[5]}' condition. Make sure that replacement file exists. \n` + ` If you are experiencing difficulties to solve this problem, you can create an issue on ${packageJson.bugs.url}`;
66-
ERROR_MESSAGES[2] = `should be equal to one of the allowed values: [$1]. \n` + ` If you are experiencing difficulties to solve this problem, you can create an issue on ${packageJson.bugs.url}`;
67-
ERROR_MESSAGES[3] = `${LOADER_NAME} must executes before other loaders. Check your Webpack config file.\n` + ` NOTE: Webpack reads loaders from right to left. So ${LOADER_NAME} have to be the last in array of loaders. \n` + ` If you are experiencing difficulties to solve this problem, you can create an issue on ${packageJson.bugs.url}`;
67+
ERROR_MESSAGES[0] = `File ($1) doesn't exist but specified in ${LOADER_NAME} options with \n` + ` condition ${LOADER_REPLACEMENT_CONDITIONS[1]} or '${LOADER_REPLACEMENT_CONDITIONS[2]}'. \n` + ` Perhaps this is due replacement isn't full path. Make sure that file exists and replacement\n` + ` option is full path to file.\n` + ` ${HELP_INFO_MESSAGE}`;
68+
ERROR_MESSAGES[1] = `File ($1) doesn't exist but specified in replacement. ${LOADER_NAME} can't replace\n` + ` it by '${LOADER_REPLACEMENT_CONDITIONS[5]}' condition. Make sure that replacement file exists. \n` + ` ${HELP_INFO_MESSAGE}`;
69+
ERROR_MESSAGES[2] = `should be equal to one of the allowed values: [$1]. \n` + ` ${HELP_INFO_MESSAGE}`;
70+
ERROR_MESSAGES[3] = `${LOADER_NAME} must executes before other loaders. Check your Webpack config file.\n` + ` NOTE: Webpack reads loaders from right to left. So ${LOADER_NAME} have to be the last in array of loaders. \n` + ` ${HELP_INFO_MESSAGE}`;
71+
ERROR_MESSAGES[4] = `should be full path to file or function returning full path to file. \n` + ` ${HELP_INFO_MESSAGE}`;
6872
/**
6973
* Schema for validate loader options
7074
* @const
@@ -81,7 +85,14 @@ var LOADER_OPTIONS_SCHEMA = {
8185
}
8286
},
8387
replacement: {
84-
type: 'string'
88+
anyOf: [{
89+
type: 'string'
90+
}, {
91+
instanceof: 'Function'
92+
}],
93+
errorMessages: {
94+
anyOf: ERROR_MESSAGES[4]
95+
}
8596
},
8697
async: {
8798
type: 'boolean',

dist/index.js

+41-19
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ function getOptions(loaderContext) {
9797
properties.forEach(function (key) {
9898
return defaultOptions[key] = _constants.LOADER_OPTIONS_SCHEMA.properties[key].default;
9999
});
100-
var result = Object.assign({}, defaultOptions, _loaderUtils.default.getOptions(loaderContext));
101-
result.replacement && (result.replacement = (0, _path.resolve)(loaderContext.context, result.replacement));
100+
var result = Object.assign({}, defaultOptions, _loaderUtils.default.getOptions(loaderContext)); //result.replacement && (result.replacement = resolve(loaderContext.context, result.replacement));
101+
102102
return result;
103103
}
104104
/**
@@ -145,10 +145,15 @@ function _default(source) {
145145
var options = getOptions(this);
146146
var isAsync = options && options.async === true;
147147
var callback = isAsync === true && this.async() || null;
148+
149+
var replacement = function replacement(resourcePath) {
150+
return options.replacement instanceof Function ? options.replacement(resourcePath) || null : options.replacement;
151+
};
148152
/**
149153
* Validate loader options before its work
150154
*/
151155

156+
152157
try {
153158
progress(`Validate options`);
154159
(0, _schemaUtils.default)(_constants.LOADER_OPTIONS_SCHEMA, options);
@@ -179,17 +184,22 @@ function _default(source) {
179184

180185
if (condition(options.condition).oneOf(_constants.LOADER_REPLACEMENT_CONDITIONS[1], _constants.LOADER_REPLACEMENT_CONDITIONS[2])) {
181186
progress(`Trying replace by condition '${options.condition}'`);
187+
var replacementPath = replacement(this.resourcePath);
188+
189+
if (replacementPath === null) {
190+
return isAsync ? callback(null, source) : source; // Skip replacement
191+
}
182192

183-
if ((0, _fs.existsSync)(options.replacement)) {
184-
progress(`Replace [${this.resourcePath}] -> [${options.replacement}]`);
185-
this.addDependency(options.replacement);
186-
return isAsync ? readFile(options.replacement, true, function (content) {
193+
if ((0, _fs.existsSync)(replacementPath)) {
194+
progress(`Replace [${this.resourcePath}] -> [${replacementPath}]`);
195+
this.addDependency(replacementPath);
196+
return isAsync ? readFile(replacementPath, true, function (content) {
187197
callback(null, content);
188-
}) : readFile(options.replacement, false);
198+
}) : readFile(replacementPath, false);
189199
} else {
190200
throw new Exception({
191201
title: _constants.ERROR_TYPES[1],
192-
message: _constants.ERROR_MESSAGES[0].replace('$1', options.replacement)
202+
message: _constants.ERROR_MESSAGES[0].replace('$1', replacementPatht)
193203
});
194204
}
195205
}
@@ -201,12 +211,18 @@ function _default(source) {
201211
if (condition(options.condition).is(_constants.LOADER_REPLACEMENT_CONDITIONS[4])) {
202212
progress(`Trying replace by condition '${options.condition}'`);
203213

204-
if ((0, _fs.existsSync)(options.replacement)) {
205-
progress(`Replace [${this.resourcePath}] -> [${options.replacement}]`);
206-
this.addDependency(options.replacement);
207-
return isAsync ? readFile(options.replacement, true, function (content) {
214+
var _replacementPath = replacement(this.resourcePath);
215+
216+
if (_replacementPath === null) {
217+
return isAsync ? callback(null, source) : source; // Skip replacement
218+
}
219+
220+
if ((0, _fs.existsSync)(_replacementPath)) {
221+
progress(`Replace [${this.resourcePath}] -> [${_replacementPath}]`);
222+
this.addDependency(_replacementPath);
223+
return isAsync ? readFile(_replacementPath, true, function (content) {
208224
callback(null, content);
209-
}) : readFile(options.replacement, false);
225+
}) : readFile(_replacementPath, false);
210226
} else {
211227
return isAsync ? callback(null, source) : source;
212228
}
@@ -219,23 +235,29 @@ function _default(source) {
219235
if (condition(options.condition).is(_constants.LOADER_REPLACEMENT_CONDITIONS[5])) {
220236
progress(`Trying replace by condition '${options.condition}'`);
221237

222-
if ((0, _fs.existsSync)(options.replacement)) {
238+
var _replacementPath2 = replacement(this.resourcePath);
239+
240+
if (_replacementPath2 === null) {
241+
return isAsync ? callback(null, source) : source; // Skip replacement
242+
}
243+
244+
if ((0, _fs.existsSync)(_replacementPath2)) {
223245
var stat = (0, _fs.statSync)(this.resourcePath);
224246

225247
if (stat.size === 0) {
226-
progress(`Replace [${this.resourcePath}] -> [${options.replacement}]`);
227-
this.addDependency(options.replacement);
228-
return isAsync ? readFile(options.replacement, true, function (content) {
248+
progress(`Replace [${this.resourcePath}] -> [${_replacementPath2}]`);
249+
this.addDependency(_replacementPath2);
250+
return isAsync ? readFile(_replacementPath2, true, function (content) {
229251
callback(null, content);
230-
}) : readFile(options.replacement, false);
252+
}) : readFile(_replacementPath2, false);
231253
} else {
232254
progress(`Skip replacement because source file [${this.resourcePath}] is not empty`);
233255
return isAsync ? callback(null, source) : source;
234256
}
235257
} else {
236258
throw new Exception({
237259
title: _constants.ERROR_TYPES[1],
238-
message: _constants.ERROR_MESSAGES[1].replace('$1', options.replacement)
260+
message: _constants.ERROR_MESSAGES[1].replace('$1', _replacementPath2)
239261
});
240262
}
241263
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const showMessage = () => console.log(`Message from replacement-a.js`);
2+
module.exports = { showMessage };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const showMessage = () => console.log(`Message from replacement-b.js`);
2+
module.exports = { showMessage };

example/3x/src/multiple-replacement-test/source-a.js

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const showMessage = () => console.log(`Message from source-b.js`);
2+
module.exports = { showMessage };

example/3x/src/replacement.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
console.log(`Message from replacement.js`);
1+
import moduleA from './multiple-replacement-test/source-a.js';
2+
import moduleB from './multiple-replacement-test/source-b.js';
3+
4+
console.log(`Message from replacement.js`);
5+
moduleA.showMessage();
6+
moduleB.showMessage();

example/3x/src/source.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
console.log(`Message from source.js`);
1+
import moduleA from './multiple-replacement-test/source-a.js';
2+
import moduleB from './multiple-replacement-test/source-b.js';
3+
4+
console.log(`Message from source.js`);
5+
moduleA.showMessage();
6+
moduleB.showMessage();

example/3x/webpack.3x.config.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ module.exports = {
2525
loader: 'file-replace-loader',
2626
options: {
2727
condition: 'if-replacement-exists',
28-
replacement: resolve('./src/replacement.js')
28+
replacement(resourcePath) {
29+
const mapping = {
30+
[resolve('./src/source.js')]: resolve('./src/replacement.js'),
31+
[resolve('./src/multiple-replacement-test/source-a.js')]: resolve('./src/multiple-replacement-test/replacement-a.js'),
32+
[resolve('./src/multiple-replacement-test/source-b.js')]: resolve('./src/multiple-replacement-test/replacement-b.js'),
33+
};
34+
return mapping[resourcePath];
35+
}
2936
}
3037
}]
3138
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const showMessage = () => console.log(`Message from replacement-a.js`);
2+
module.exports = { showMessage };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const showMessage = () => console.log(`Message from replacement-b.js`);
2+
module.exports = { showMessage };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const showMessage = () => console.log(`Message from source-a.js`);
2+
module.exports = { showMessage };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const showMessage = () => console.log(`Message from source-b.js`);
2+
module.exports = { showMessage };

example/4x/src/replacement.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
console.log(`Message from replacement.js`);
1+
import moduleA from './multiple-replacement-test/source-a.js';
2+
import moduleB from './multiple-replacement-test/source-b.js';
3+
4+
console.log(`Message from replacement.js`);
5+
moduleA.showMessage();
6+
moduleB.showMessage();

example/4x/src/source.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
console.log(`Message from source.js`);
1+
import moduleA from './multiple-replacement-test/source-a.js';
2+
import moduleB from './multiple-replacement-test/source-b.js';
3+
4+
console.log(`Message from source.js`);
5+
moduleA.showMessage();
6+
moduleB.showMessage();

example/4x/webpack.4x.config.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ module.exports = {
2525
loader: 'file-replace-loader',
2626
options: {
2727
condition: 'if-replacement-exists',
28-
replacement: resolve('./src/replacement.js')
28+
replacement(resourcePath) {
29+
const mapping = {
30+
[resolve('./src/source.js')]: resolve('./src/replacement.js'),
31+
[resolve('./src/multiple-replacement-test/source-a.js')]: resolve('./src/multiple-replacement-test/replacement-a.js'),
32+
[resolve('./src/multiple-replacement-test/source-b.js')]: resolve('./src/multiple-replacement-test/replacement-b.js'),
33+
};
34+
return mapping[resourcePath];
35+
}
2936
}
3037
}]
3138
},

0 commit comments

Comments
 (0)