Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add no node env ssr rule #130

Merged
merged 10 commits into from
Oct 12, 2023
Merged
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
26 changes: 26 additions & 0 deletions lib/rule-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,29 @@ module.exports.noPropertyAccessDuringSSR = function noPropertyAccessDuringSSR(
},
};
};

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

return {
...withinLWCVisitors,
MemberExpression: (node) => {
if (!isInsideReachableMethod() || 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'
) {
reporter(node);
}
},
};
};
36 changes: 36 additions & 0 deletions lib/rules/no-node-env-in-ssr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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((node) => {
context.report({
node,
messageId: 'nodeEnvFound',
data: {
identifier: node.property.name,
},
});
});
},
};
204 changes: 204 additions & 0 deletions test/lib/rules/no-node-env-ssr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*
* 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: [
divmain marked this conversation as resolved.
Show resolved Hide resolved
{
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();
}
renderedCallbac() {
this.foo();
}
foo() {
doSomethingWith(process.env.NODE_ENV);
}
}
`,
errors: [
{
messageId: 'nodeEnvFound',
},
],
},
],
});