Skip to content

Commit a0e9105

Browse files
authored
feat: add invalid-import rule for ssr (#204)
1 parent 046d40c commit a0e9105

File tree

5 files changed

+226
-0
lines changed

5 files changed

+226
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ To choose from three configuration settings, install the [`eslint-config-lwc`](h
127127
| [lwc/ssr-no-restricted-browser-globals](./docs/rules/ssr/ssr-no-restricted-browser-globals.md) | disallow access to global browser APIs during SSR | |
128128
| [lwc/ssr-no-unsupported-properties](./docs/rules/ssr/ssr-no-unsupported-properties.md) | disallow access of unsupported properties in SSR | |
129129
| [lwc/ssr-no-node-env](./docs/rules/ssr/ssr-no-node-env.md) | disallow usage of process.env.NODE_ENV in SSR | |
130+
| [lwc/ssr-no-disallowed-lwc-imports](./docs/rules/ssr/ssr-no-disallowed-lwc-imports.md) | restrict specific imports from the lwc package in SSR-able components | |
130131
| [lwc/valid-graphql-wire-adapter-callback-parameters](./docs/rules/valid-graphql-wire-adapter-callback-parameters.md) | ensure graphql wire adapters are using 'errors' instead of 'error' | |
131132
| [lwc/ssr-no-host-mutation-in-connected-callback](./docs/rules/ssr/ssr-no-host-mutation-in-connected-callback.md) | disallow the host element mutation in 'connectedCallback' | |
132133
| [lwc/ssr-no-static-imports-of-user-specific-scoped-modules](./docs/rules/ssr/ssr-no-static-imports-of-user-specific-scoped-modules.md) | disallow static imports of user-specific scoped modules in SSR-able components | |
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Prevent importing restricted APIs from the "lwc" package in SSR-able components (`@lwc/lwc/ssr-no-disallowed-lwc-imports`)
2+
3+
Restricts importing specific modules from the `lwc` package in components that may be server-side rendered (`lightning__ServerRenderable` or `lightning__ServerRenderableWithHydration`). Certain LWC APIs may not be compatible with server-side rendering and should be avoided in SSR-able components.
4+
5+
This rule is complementary to the general `no-disallowed-lwc-imports` rule and only flags LWC APIs that are otherwise valid but not supported in SSR contexts.
6+
7+
## Rule details
8+
9+
This rule prevents imports of LWC APIs that are not supported or recommended in server-side rendering contexts. By default, it disallows:
10+
11+
- `readonly` - Not supported in SSR environment
12+
13+
Examples of **incorrect** code:
14+
15+
```js
16+
import { readonly } from 'lwc';
17+
18+
export default class MyComponent extends LightningElement {
19+
@api value = readonly({ name: 'test' });
20+
}
21+
```
22+
23+
```js
24+
import { LightningElement, readonly } from 'lwc';
25+
26+
export default class MyComponent extends LightningElement {
27+
data = readonly({ items: [] });
28+
}
29+
```
30+
31+
Examples of **correct** code:
32+
33+
```js
34+
import { LightningElement } from 'lwc';
35+
36+
export default class MyComponent extends LightningElement {
37+
@api value = { name: 'test' };
38+
}
39+
```
40+
41+
```js
42+
import { LightningElement, api, track } from 'lwc';
43+
44+
export default class MyComponent extends LightningElement {
45+
@api data;
46+
@track items = [];
47+
}
48+
```
49+
50+
```js
51+
// Re-exporting is allowed since the component doesn't use the API itself
52+
export { readonly } from 'lwc';
53+
54+
export default class MyUtilityComponent extends LightningElement {
55+
// This component is SSR-compatible
56+
}
57+
```
58+
59+
## Configuration
60+
61+
### `disallowlist`
62+
63+
The `disallowlist` property allows you to specify which LWC APIs should be disallowed in SSR context. It accepts an array of strings and overrides the default list.
64+
65+
Examples of **incorrect** code with custom disallowlist:
66+
67+
```js
68+
/* eslint @lwc/lwc/ssr-no-disallowed-lwc-imports: ["error", { "disallowlist": ["readonly", "track"] }] */
69+
import { track } from 'lwc';
70+
```
71+
72+
```js
73+
/* eslint @lwc/lwc/ssr-no-disallowed-lwc-imports: ["error", { "disallowlist": ["readonly", "track"] }] */
74+
import { readonly } from 'lwc';
75+
```
76+
77+
Examples of **correct** code with custom disallowlist:
78+
79+
```js
80+
/* eslint @lwc/lwc/ssr-no-disallowed-lwc-imports: ["error", { "disallowlist": ["readonly", "track"] }] */
81+
import { LightningElement, api } from 'lwc';
82+
```
83+
84+
## When Not To Use It
85+
86+
If your components are never server-side rendered or if you need to use specific LWC APIs that are flagged by this rule, you may want to disable it. However, be aware that using unsupported APIs in SSR contexts may cause runtime errors or unexpected behavior during server-side rendering.
87+
88+
## Relationship to Other Rules
89+
90+
This rule works alongside the general `lwc/no-disallowed-lwc-imports` rule:
91+
92+
- **`no-disallowed-lwc-imports`**: Handles general import/export validation (bare imports, namespace imports, default imports, etc.)
93+
- **`ssr-no-disallowed-lwc-imports`**: Focuses only on SSR-specific import restrictions for otherwise valid imports
94+
95+
Both rules should typically be used together in SSR-able components.

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const rules = {
3939
'ssr-no-unsupported-node-api': require('./rules/ssr/ssr-no-unsupported-node-api'),
4040
'ssr-no-static-imports-of-user-specific-scoped-modules': require('./rules/ssr/ssr-no-static-imports-of-user-specific-scoped-modules'),
4141
'ssr-no-form-factor': require('./rules/ssr/ssr-no-form-factor'),
42+
'ssr-no-disallowed-lwc-imports': require('./rules/ssr/ssr-no-disallowed-lwc-imports'),
4243
};
4344

4445
const processors = {
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright (c) 2024, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: MIT
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
6+
*/
7+
'use strict';
8+
9+
const { docUrl } = require('../../util/doc-url');
10+
11+
// Default APIs that are not supported in SSR context
12+
const SSR_DISALLOWED_APIS = new Set(['readonly']);
13+
14+
const isLwcImport = (node) =>
15+
node.source && node.source.type === 'Literal' && node.source.value === 'lwc';
16+
17+
module.exports = {
18+
meta: {
19+
type: 'problem',
20+
docs: {
21+
description: 'restrict specific imports from the lwc package in SSR-able components',
22+
category: 'LWC',
23+
url: docUrl('ssr/ssr-no-disallowed-lwc-imports'),
24+
recommended: true,
25+
},
26+
messages: {
27+
invalidImport:
28+
'Invalid import. "{{importName}}" from "lwc" cannot be used in SSR context.',
29+
},
30+
schema: [
31+
{
32+
type: 'object',
33+
properties: {
34+
disallowlist: {
35+
type: 'array',
36+
items: {
37+
type: 'string',
38+
},
39+
description: 'List of LWC APIs to disallow in SSR context',
40+
},
41+
},
42+
additionalProperties: false,
43+
},
44+
],
45+
},
46+
47+
create(context) {
48+
const options = context.options[0] || {};
49+
const { disallowlist } = options;
50+
51+
// Use custom disallowlist if provided, otherwise use default
52+
let ssrDisallowedApis = SSR_DISALLOWED_APIS;
53+
if (disallowlist) {
54+
ssrDisallowedApis = new Set(disallowlist);
55+
}
56+
57+
return {
58+
ImportDeclaration(node) {
59+
if (isLwcImport(node)) {
60+
const { specifiers } = node;
61+
for (const specifier of specifiers) {
62+
const { type, imported } = specifier;
63+
if (type === 'ImportSpecifier' && ssrDisallowedApis.has(imported.name)) {
64+
// import { readonly } from 'lwc'
65+
context.report({
66+
node: specifier,
67+
messageId: 'invalidImport',
68+
data: {
69+
importName: imported.name,
70+
},
71+
});
72+
}
73+
}
74+
}
75+
},
76+
};
77+
},
78+
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2024, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: MIT
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
6+
*/
7+
'use strict';
8+
9+
const { testRule } = require('../../shared');
10+
11+
testRule('ssr/ssr-no-disallowed-lwc-imports', {
12+
valid: [
13+
{
14+
code: `import { LightningElement } from 'lwc';`,
15+
},
16+
{
17+
code: `import { LightningElement, api, track, wire } from 'lwc';`,
18+
},
19+
{
20+
code: `import { readonly } from 'lwc';`,
21+
options: [{ disallowlist: ['track'] }],
22+
},
23+
],
24+
invalid: [
25+
{
26+
code: `import { readonly } from 'lwc';`,
27+
errors: [
28+
{
29+
message: 'Invalid import. "readonly" from "lwc" cannot be used in SSR context.',
30+
},
31+
],
32+
},
33+
{
34+
code: `import { LightningElement, readonly } from 'lwc';`,
35+
errors: [
36+
{
37+
message: 'Invalid import. "readonly" from "lwc" cannot be used in SSR context.',
38+
},
39+
],
40+
},
41+
{
42+
code: `import { track } from 'lwc';`,
43+
options: [{ disallowlist: ['track'] }],
44+
errors: [
45+
{
46+
message: 'Invalid import. "track" from "lwc" cannot be used in SSR context.',
47+
},
48+
],
49+
},
50+
],
51+
});

0 commit comments

Comments
 (0)