diff --git a/packages/replace/README.md b/packages/replace/README.md
index 533e09c9d..bd5ffb26f 100644
--- a/packages/replace/README.md
+++ b/packages/replace/README.md
@@ -70,6 +70,39 @@ For example, if you pass `typeof window` in `values` to-be-replaced, then you co
- `typeof window.document` **will not** be replaced due to `(?!\.)` boundary
- `typeof windowSmth` **will not** be replaced due to a `\b` boundary
+### `objectGuards`
+
+Type: `Boolean`
+Default: `false`
+
+When replacing dot-separated object properties like `process.env.NODE_ENV`, will also replace `typeof process` object guard
+checks against the objects with the string `"object"`.
+
+For example:
+
+```js
+replace({
+ values: {
+ 'process.env.NODE_ENV': '"production"'
+ }
+});
+```
+
+```js
+// Input
+if (typeof process !== 'undefined' && process.env.NODE_ENV === 'production') {
+ console.log('production');
+}
+// Without `objectGuards`
+if (typeof process !== 'undefined' && 'production' === 'production') {
+ console.log('production');
+}
+// With `objectGuards`
+if ('object' !== 'undefined' && 'production' === 'production') {
+ console.log('production');
+}
+```
+
### `preventAssignment`
Type: `Boolean`
diff --git a/packages/replace/src/index.js b/packages/replace/src/index.js
index f2085e7e8..dd001b234 100755
--- a/packages/replace/src/index.js
+++ b/packages/replace/src/index.js
@@ -24,6 +24,7 @@ function getReplacements(options) {
delete values.exclude;
delete values.sourcemap;
delete values.sourceMap;
+ delete values.objectGuards;
return values;
}
@@ -35,10 +36,42 @@ function mapToFunctions(object) {
}, {});
}
+const objKeyRegEx = /^([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)(\.([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*))+$/;
+function expandTypeofReplacements(replacements) {
+ Object.keys(replacements).forEach((key) => {
+ const objMatch = key.match(objKeyRegEx);
+ if (!objMatch) return;
+ let dotIndex = objMatch[1].length;
+ let lastIndex = 0;
+ do {
+ // eslint-disable-next-line no-param-reassign
+ replacements[`typeof ${key.slice(lastIndex, dotIndex)} ===`] = '"object" ===';
+ // eslint-disable-next-line no-param-reassign
+ replacements[`typeof ${key.slice(lastIndex, dotIndex)} !==`] = '"object" !==';
+ // eslint-disable-next-line no-param-reassign
+ replacements[`typeof ${key.slice(lastIndex, dotIndex)}===`] = '"object"===';
+ // eslint-disable-next-line no-param-reassign
+ replacements[`typeof ${key.slice(lastIndex, dotIndex)}!==`] = '"object"!==';
+ // eslint-disable-next-line no-param-reassign
+ replacements[`typeof ${key.slice(lastIndex, dotIndex)} ==`] = '"object" ===';
+ // eslint-disable-next-line no-param-reassign
+ replacements[`typeof ${key.slice(lastIndex, dotIndex)} !=`] = '"object" !==';
+ // eslint-disable-next-line no-param-reassign
+ replacements[`typeof ${key.slice(lastIndex, dotIndex)}==`] = '"object"===';
+ // eslint-disable-next-line no-param-reassign
+ replacements[`typeof ${key.slice(lastIndex, dotIndex)}!=`] = '"object"!==';
+ lastIndex = dotIndex + 1;
+ dotIndex = key.indexOf('.', lastIndex);
+ } while (dotIndex !== -1);
+ });
+}
+
export default function replace(options = {}) {
const filter = createFilter(options.include, options.exclude);
- const { delimiters, preventAssignment } = options;
- const functionValues = mapToFunctions(getReplacements(options));
+ const { delimiters, preventAssignment, objectGuards } = options;
+ const replacements = getReplacements(options);
+ if (objectGuards) expandTypeofReplacements(replacements);
+ const functionValues = mapToFunctions(replacements);
const keys = Object.keys(functionValues).sort(longest).map(escape);
const lookahead = preventAssignment ? '(?!\\s*=[^=])' : '';
const pattern = delimiters
diff --git a/packages/replace/test/fixtures/form/process-check/_config.js b/packages/replace/test/fixtures/form/process-check/_config.js
new file mode 100644
index 000000000..43c1a7f9d
--- /dev/null
+++ b/packages/replace/test/fixtures/form/process-check/_config.js
@@ -0,0 +1,8 @@
+module.exports = {
+ description: 'Handles process type guards in replacements',
+ options: {
+ 'process.env.NODE_ENV': '"production"',
+ preventAssignment: true,
+ objectGuards: true
+ }
+};
diff --git a/packages/replace/test/fixtures/form/process-check/input.js b/packages/replace/test/fixtures/form/process-check/input.js
new file mode 100644
index 000000000..dcce0a89f
--- /dev/null
+++ b/packages/replace/test/fixtures/form/process-check/input.js
@@ -0,0 +1,3 @@
+if (typeof process !== 'undefined' && process.env.NODE_ENV === 'production') {
+ console.log('production');
+}
diff --git a/packages/replace/test/fixtures/form/process-check/output.js b/packages/replace/test/fixtures/form/process-check/output.js
new file mode 100644
index 000000000..5fa7f8f80
--- /dev/null
+++ b/packages/replace/test/fixtures/form/process-check/output.js
@@ -0,0 +1 @@
+process.env.DEBUG = 'test';
diff --git a/packages/replace/test/snapshots/form.js.md b/packages/replace/test/snapshots/form.js.md
index eaeb3189d..c0d1e0c93 100644
--- a/packages/replace/test/snapshots/form.js.md
+++ b/packages/replace/test/snapshots/form.js.md
@@ -39,6 +39,14 @@ Generated by [AVA](https://avajs.dev).
exclude␊
`);`
+## process-check: Handles process type guards in replacements
+
+> Snapshot 1
+
+ `if (typeof process !== 'undefined' && "production" === 'production') {␊
+ console.log('production');␊
+ }`
+
## replace-nothing: replaces nothing
> Snapshot 1
diff --git a/packages/replace/test/snapshots/form.js.snap b/packages/replace/test/snapshots/form.js.snap
index f0d05a94e..f87dfde21 100644
Binary files a/packages/replace/test/snapshots/form.js.snap and b/packages/replace/test/snapshots/form.js.snap differ