Skip to content

Commit

Permalink
feat: disallow import chunkname w/ webpack eager mode (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
SukkaW authored Jul 2, 2024
1 parent c0cea7b commit 293fcf4
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/grumpy-games-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-import-x": patch
---

feat: webpack comment regex support `webpackFetchPriority`
5 changes: 5 additions & 0 deletions .changeset/sixty-avocados-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-import-x": patch
---

Allow empty chunk name when webpackMode: 'eager' is set; add suggestions to remove name in eager mode
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
| Name                            | Description | 💼 | ⚠️ | 🚫 | 🔧 | 💡 ||
| :------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | :-- | :---- | :-- | :-- | :-- | :-- |
| [consistent-type-specifier-style](docs/rules/consistent-type-specifier-style.md) | Enforce or ban the use of inline type-only markers for named imports. | | | | 🔧 | | |
| [dynamic-import-chunkname](docs/rules/dynamic-import-chunkname.md) | Enforce a leading comment with the webpackChunkName for dynamic imports. | | | | | | |
| [dynamic-import-chunkname](docs/rules/dynamic-import-chunkname.md) | Enforce a leading comment with the webpackChunkName for dynamic imports. | | | | | 💡 | |
| [exports-last](docs/rules/exports-last.md) | Ensure all exports appear after other statements. | | | | | | |
| [extensions](docs/rules/extensions.md) | Ensure consistent use of file extension within the import path. | | | | | | |
| [first](docs/rules/first.md) | Ensure all imports appear before other statements. | | | | 🔧 | | |
Expand Down
9 changes: 9 additions & 0 deletions docs/rules/dynamic-import-chunkname.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# import-x/dynamic-import-chunkname

💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).

<!-- end auto-generated rule header -->

This rule reports any dynamic imports without a webpackChunkName specified in a leading block comment in the proper format.
Expand Down Expand Up @@ -56,6 +58,13 @@ import(
// webpackChunkName: "someModule"
'someModule'
)

// chunk names are disallowed when eager mode is set
import(
/* webpackMode: "eager" */
/* webpackChunkName: "someModule" */
'someModule'
)
```

### valid
Expand Down
92 changes: 88 additions & 4 deletions src/rules/dynamic-import-chunkname.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import vm from 'node:vm'

import type { TSESTree } from '@typescript-eslint/utils'
import type { RuleFixer } from '@typescript-eslint/utils/dist/ts-eslint'

import { createRule } from '../utils'

Expand All @@ -16,6 +17,9 @@ type MessageId =
| 'paddedSpaces'
| 'webpackComment'
| 'chunknameFormat'
| 'webpackEagerModeNoChunkName'
| 'webpackRemoveEagerMode'
| 'webpackRemoveChunkName'

export = createRule<[Options?], MessageId>({
name: 'dynamic-import-chunkname',
Expand All @@ -26,6 +30,7 @@ export = createRule<[Options?], MessageId>({
description:
'Enforce a leading comment with the webpackChunkName for dynamic imports.',
},
hasSuggestions: true,
schema: [
{
type: 'object',
Expand Down Expand Up @@ -56,7 +61,11 @@ export = createRule<[Options?], MessageId>({
webpackComment:
'dynamic imports require a "webpack" comment with valid syntax',
chunknameFormat:
'dynamic imports require a leading comment in the form /*{{format}}*/',
'dynamic imports require a leading comment in the form /* {{format}} */',
webpackEagerModeNoChunkName:
'dynamic imports using eager mode do not need a webpackChunkName',
webpackRemoveEagerMode: 'Remove webpackMode',
webpackRemoveChunkName: 'Remove webpackChunkName',
},
},
defaultOptions: [],
Expand All @@ -69,9 +78,12 @@ export = createRule<[Options?], MessageId>({

const paddedCommentRegex = /^ (\S[\S\s]+\S) $/
const commentStyleRegex =
/^( ((webpackChunkName: .+)|((webpackPrefetch|webpackPreload): (true|false|-?\d+))|(webpackIgnore: (true|false))|((webpackInclude|webpackExclude): \/.*\/)|(webpackMode: ["'](lazy|lazy-once|eager|weak)["'])|(webpackExports: (["']\w+["']|\[(["']\w+["'], *)+(["']\w+["']*)]))),?)+ $/
const chunkSubstrFormat = ` webpackChunkName: ["']${webpackChunknameFormat}["'],? `
/^( (((webpackChunkName|webpackFetchPriority): .+)|((webpackPrefetch|webpackPreload): (true|false|-?\d+))|(webpackIgnore: (true|false))|((webpackInclude|webpackExclude): \/.+\/)|(webpackMode: ["'](lazy|lazy-once|eager|weak)["'])|(webpackExports: (["']\w+["']|\[(["']\w+["'], *)+(["']\w+["']*)]))),?)+ $/

const chunkSubstrFormat = `webpackChunkName: ["']${webpackChunknameFormat}["'],?`
const chunkSubstrRegex = new RegExp(chunkSubstrFormat)
const eagerModeFormat = `webpackMode: ["']eager["'],?`
const eagerModeRegex = new RegExp(eagerModeFormat)

function run(node: TSESTree.Node, arg: TSESTree.Node) {
const { sourceCode } = context
Expand All @@ -86,6 +98,7 @@ export = createRule<[Options?], MessageId>({
}

let isChunknamePresent = false
let isEagerModePresent = false

for (const comment of leadingComments) {
if (comment.type !== 'Block') {
Expand Down Expand Up @@ -123,12 +136,83 @@ export = createRule<[Options?], MessageId>({
return
}

if (eagerModeRegex.test(comment.value)) {
isEagerModePresent = true
}

if (chunkSubstrRegex.test(comment.value)) {
isChunknamePresent = true
}
}

if (!isChunknamePresent && !allowEmpty) {
const removeCommentsAndLeadingSpaces = (
fixer: RuleFixer,
comment: TSESTree.Comment,
) => {
const leftToken = sourceCode.getTokenBefore(comment)
const leftComments = sourceCode.getCommentsBefore(comment)
if (leftToken) {
if (leftComments.length > 0) {
return fixer.removeRange([
Math.max(
leftToken.range[1],
leftComments[leftComments.length - 1].range[1],
),
comment.range[1],
])
}
return fixer.removeRange([leftToken.range[1], comment.range[1]])
}
return fixer.remove(comment)
}

if (isChunknamePresent && isEagerModePresent) {
context.report({
node,
messageId: 'webpackEagerModeNoChunkName',
suggest: [
{
messageId: 'webpackRemoveChunkName',
fix(fixer) {
for (const comment of leadingComments) {
if (chunkSubstrRegex.test(comment.value)) {
const replacement = comment.value
.replace(chunkSubstrRegex, '')
.trim()
.replace(/,$/, '')

return replacement === ''
? removeCommentsAndLeadingSpaces(fixer, comment)
: fixer.replaceText(comment, `/* ${replacement} */`)
}
}

return null
},
},
{
messageId: 'webpackRemoveEagerMode',
fix(fixer) {
for (const comment of leadingComments) {
if (eagerModeRegex.test(comment.value)) {
const replacement = comment.value
.replace(eagerModeRegex, '')
.trim()
.replace(/,$/, '')
return replacement === ''
? removeCommentsAndLeadingSpaces(fixer, comment)
: fixer.replaceText(comment, `/* ${replacement} */`)
}
}

return null
},
},
],
})
}

if (!isChunknamePresent && !allowEmpty && !isEagerModePresent) {
context.report({
node,
messageId: 'chunknameFormat',
Expand Down
122 changes: 101 additions & 21 deletions test/rules/dynamic-import-chunkname.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const parser = parsers.BABEL
const pickyChunkNameFormatError = {
messageId: 'chunknameFormat',
data: {
format: ` webpackChunkName: ["']${pickyCommentFormat}["'],? `,
format: `webpackChunkName: ["']${pickyCommentFormat}["'],?`,
},
} as const

Expand Down Expand Up @@ -366,15 +366,6 @@ ruleTester.run('dynamic-import-chunkname', rule, {
options,
parser,
},
{
code: `import(
/* webpackChunkName: "someModule" */
/* webpackMode: "eager" */
'someModule'
)`,
options,
parser,
},
{
code: `import(
/* webpackChunkName: "someModule" */
Expand Down Expand Up @@ -426,7 +417,7 @@ ruleTester.run('dynamic-import-chunkname', rule, {
/* webpackPrefetch: true */
/* webpackPreload: true */
/* webpackIgnore: false */
/* webpackMode: "eager" */
/* webpackMode: "lazy" */
/* webpackExports: ["default", "named"] */
'someModule'
)`,
Expand Down Expand Up @@ -1220,15 +1211,6 @@ describe('TypeScript', () => {
options,
parser: typescriptParser,
},
{
code: `import(
/* webpackChunkName: "someModule" */
/* webpackMode: "eager" */
'someModule'
)`,
options,
parser: typescriptParser,
},
{
code: `import(
/* webpackChunkName: "someModule" */
Expand Down Expand Up @@ -1280,7 +1262,7 @@ describe('TypeScript', () => {
/* webpackPrefetch: true */
/* webpackPreload: true */
/* webpackIgnore: false */
/* webpackMode: "eager" */
/* webpackMode: "lazy" */
/* webpackExports: ["default", "named"] */
'someModule'
)`,
Expand Down Expand Up @@ -1707,6 +1689,104 @@ describe('TypeScript', () => {
},
],
},
{
code: `import(
/* webpackChunkName: "someModule" */
/* webpackMode: "eager" */
'someModule'
)`,
options,
parser,
output: null,
errors: [
{
messageId: 'webpackEagerModeNoChunkName',
type: nodeType,
suggestions: [
{
messageId: 'webpackRemoveChunkName',
output: `import(
/* webpackMode: "eager" */
'someModule'
)`,
},
{
messageId: 'webpackRemoveEagerMode',
output: `import(
/* webpackChunkName: "someModule" */
'someModule'
)`,
},
],
},
],
},
{
code: `import(
/* webpackChunkName: "someModule", webpackPrefetch: true */
/* webpackMode: "eager" */
'someModule'
)`,
options,
parser,
output: null,
errors: [
{
messageId: 'webpackEagerModeNoChunkName',
type: nodeType,
suggestions: [
{
messageId: 'webpackRemoveChunkName',
output: `import(
/* webpackPrefetch: true */
/* webpackMode: "eager" */
'someModule'
)`,
},
{
messageId: 'webpackRemoveEagerMode',
output: `import(
/* webpackChunkName: "someModule", webpackPrefetch: true */
'someModule'
)`,
},
],
},
],
},
{
code: `import(
/* webpackChunkName: "someModule" */
/* webpackMode: "eager", webpackPrefetch: true */
'someModule'
)`,
options,
parser,
output: null,
errors: [
{
messageId: 'webpackEagerModeNoChunkName',
type: nodeType,
suggestions: [
{
messageId: 'webpackRemoveChunkName',
output: `import(
/* webpackMode: "eager", webpackPrefetch: true */
'someModule'
)`,
},
{
messageId: 'webpackRemoveEagerMode',
output: `import(
/* webpackChunkName: "someModule" */
/* webpackPrefetch: true */
'someModule'
)`,
},
],
},
],
},
],
})
})

0 comments on commit 293fcf4

Please sign in to comment.