Skip to content

Commit

Permalink
feat: add no node env ssr rule (#130)
Browse files Browse the repository at this point in the history
* chore: add rule to disallow process.env.NODE_ENV

* docs: update no-node-env-in-ssr.md

* chore: add no-node-env-in-ssr to README

* chore: prettier

* test: add no-node-env-in-ssr

* Update lib/rules/no-node-env-in-ssr.js

Co-authored-by: Dale Bustad <dale@divmain.com>

* fix: add rule-helper for no-node-env

* fix: add object guard

* test: add more tests

* fix: typo

---------

Co-authored-by: Dale Bustad <dale@divmain.com>
  • Loading branch information
khangsfdc and divmain committed Oct 12, 2023
1 parent efe6ca4 commit c5ef560
Show file tree
Hide file tree
Showing 7 changed files with 395 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ To choose from three configuration settings, install the [`eslint-config-lwc`](h
| [lwc/valid-wire](./docs/rules/valid-wire.md) | validate `wire` decorator usage | |
| [lwc/no-restricted-browser-globals-during-ssr](./docs/rules/no-restricted-browser-globals-during-ssr.md) | disallow access to global browser APIs during SSR | |
| [lwc/no-unsupported-ssr-properties](./docs/rules/no-unsupported-ssr-properties.md) | disallow access of unsupported properties in SSR | |
| [lwc/no-node-env-in-ssr](./docs/rules/no-node-env-in-ssr.md) | disallow usage of process.env.NODE_ENV in SSR | |

### Best practices

Expand Down
33 changes: 33 additions & 0 deletions docs/rules/no-node-env-in-ssr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Disallow use of `process.env.NODE_ENV` during SSR (`lwc/no-node-env-in-ssr`)

Using process.env.NODE_ENV during server-side rendering in JavaScript is not recommended because it can introduce unexpected behavior and bugs in your application. This environment variable is typically used for conditional logic related to development or production builds, which is more relevant on the client side.

## Rule Details

Example of **incorrect** code for this rule:

```js
import { LightningElement } from 'lwc';

export default class Foo extends LightningElement {
connectedCallback() {
if (process.env.NODE_ENV !== 'production') {
console.log('Foo:connectedCallback');
}
}
}
```

Examples of **correct** code for this rule:

```js
import { LightningElement } from 'lwc';

export default class Foo extends LightningElement {
connectedCallback() {
if (!import.meta.env.SSR && process.env.NODE_ENV !== 'production') {
console.log('Foo:connectedCallback');
}
}
}
```
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const rules = {
'valid-wire': require('./rules/valid-wire'),
'no-restricted-browser-globals-during-ssr': require('./rules/no-restricted-browser-globals-during-ssr'),
'no-unsupported-ssr-properties': require('./rules/no-unsupported-ssr-properties'),
'no-node-env-in-ssr': require('./rules/no-node-env-in-ssr'),
};

module.exports = {
Expand Down
41 changes: 41 additions & 0 deletions lib/rule-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,44 @@ module.exports.noPropertyAccessDuringSSR = function noPropertyAccessDuringSSR(
},
};
};

module.exports.noNodeEnvInSSR = function noNodeEnvInSSR(context) {
const {
withinLWCVisitors,
isInsideReachableFunction,
isInsideReachableMethod,
isInsideSkippedBlock,
} = reachableDuringSSRPartial();

return {
...withinLWCVisitors,
MemberExpression: (node) => {
if (
(!isInsideReachableMethod() &&
!isInsideReachableFunction() &&
!inModuleScope(node, context)) ||
isInsideSkippedBlock()
) {
return;
}
if (
node.property.type === 'Identifier' &&
node.property.name === 'NODE_ENV' &&
node.object.type === 'MemberExpression' &&
node.object.object &&
node.object.object.type === 'Identifier' &&
node.object.object.name === 'process' &&
node.object.property.type === 'Identifier' &&
node.object.property.name === 'env'
) {
context.report({
node,
messageId: 'nodeEnvFound',
data: {
identifier: node.property.name,
},
});
}
},
};
};
28 changes: 28 additions & 0 deletions lib/rules/no-node-env-in-ssr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2023, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
'use strict';

const { noNodeEnvInSSR } = require('../rule-helpers');
const { docUrl } = require('../util/doc-url');

module.exports = {
meta: {
type: 'problem',
docs: {
url: docUrl('no-node-env-in-ssr'),
category: 'LWC',
description: 'disallow access of process.env.NODE_ENV in SSR',
},
schema: [],
messages: {
nodeEnvFound: 'process.env.NODE_ENV is unsupported in SSR.',
},
},
create: (context) => {
return noNodeEnvInSSR(context);
},
};
258 changes: 258 additions & 0 deletions test/lib/rules/no-node-env-ssr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
/*
* Copyright (c) 2018, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
'use strict';
const { RuleTester } = require('eslint');

const { ESLINT_TEST_CONFIG } = require('../shared');
const rule = require('../../../lib/rules/no-node-env-in-ssr');

const tester = new RuleTester(ESLINT_TEST_CONFIG);

tester.run('no-node-env-in-ssr', rule, {
valid: [
{
code: `
import { LightningElement } from 'lwc';
import tmplA from './a.html';
export default class Foo extends LightningElement {
connectedCallback() {
// we can't use process.env.NODE_ENV here
}
renderedCallback() {
if (process.env.NODE_ENV === 'development') {
console.log('test');
}
}
bar() {
if (process.env.NODE_ENV === 'development') {
console.log('test');
}
}
}
`,
},
{
code: `
import { LightningElement } from 'lwc';
import tmplA from './a.html';
export default class Foo extends LightningElement {
connectedCallback() {
// we can't use process.env.NODE_ENV here
}
renderedCallback() {
this.bar();
}
bar() {
if (process.env.NODE_ENV === 'development') {
console.log('test');
}
}
}
`,
},
{
code: `
import { LightningElement } from 'lwc';
import tmplA from './a.html';
export default class Foo extends LightningElement {
connectedCallback() {
// we can't use process.env.NODE_ENV here
}
bar() {
doSomething(process.emv.NODE_ENV);
}
}
`,
},
],
invalid: [
{
code: `
import { LightningElement } from 'lwc';
import tmplA from './a.html';
export default class Foo extends LightningElement {
connectedCallback() {
if (process.env.NODE_ENV === 'development') {
console.log('test');
}
}
}
`,
errors: [
{
messageId: 'nodeEnvFound',
},
],
},
{
code: `
import { LightningElement } from 'lwc';
import tmplA from './a.html';
export default class Foo extends LightningElement {
connectedCallback() {
this.foo();
}
foo() {
if (process.env.NODE_ENV === 'development') {
console.log('test');
}
}
}
`,
errors: [
{
messageId: 'nodeEnvFound',
},
],
},
{
code: `
import { LightningElement } from 'lwc';
import tmplA from './a.html';
export default class Foo extends LightningElement {
connectedCallback() {
doSomethingWith(process.env.NODE_ENV);
}
}
`,
errors: [
{
messageId: 'nodeEnvFound',
},
],
},
{
code: `
import { LightningElement } from 'lwc';
import tmplA from './a.html';
export default class Foo extends LightningElement {
connectedCallback() {
this.foo();
}
renderedCallback() {
this.foo();
}
foo() {
doSomethingWith(process.env.NODE_ENV);
}
}
`,
errors: [
{
messageId: 'nodeEnvFound',
},
],
},
{
code: `
import { LightningElement } from 'lwc';
import tmplA from './a.html';
export default class Foo extends LightningElement {
connectedCallback() {
this.foo();
}
renderedCallback() {
this.foo();
}
foo() {
doSomethingWith(process.env.NODE_ENV);
}
}
`,
errors: [
{
messageId: 'nodeEnvFound',
},
],
},
{
code: `
import { LightningElement } from 'lwc';
import tmplA from './a.html';
export default class Foo extends LightningElement {
connectedCallback() {
this.foo();
}
renderedCallback() {
this.foo();
}
foo() {
doSomethingWith(process.env.NODE_ENV);
}
}
`,
errors: [
{
messageId: 'nodeEnvFound',
},
],
},
{
code: `
import { LightningElement } from 'lwc';
import tmplA from './a.html';
export default class Foo extends LightningElement {
constructor() {
if (process.env.NODE_ENV === 'development') {
console.log('test');
}
}
}
`,
errors: [
{
messageId: 'nodeEnvFound',
},
],
},
{
code: `
import { LightningElement } from 'lwc';
import tmplA from './a.html';
export default class Foo extends LightningElement {
constructor() {
this.foo();
}
foo() {
doSomethingWith(process.env.NODE_ENV);
}
}
`,
errors: [
{
messageId: 'nodeEnvFound',
},
],
},
{
code: `
import { LightningElement } from 'lwc';
import tmplA from './a.html';
export default class Foo extends LightningElement {
foo = process.env.NODE_ENV;
}
`,
errors: [
{
messageId: 'nodeEnvFound',
},
],
},
],
});
Loading

0 comments on commit c5ef560

Please sign in to comment.