Skip to content

Conversation

@rjmunro
Copy link
Contributor

@rjmunro rjmunro commented Oct 16, 2025

About the Contributor

Type of Contribution

This is a code improvement

Current Behavior

Between release52 and release53, particularly in d02f416, the standard for imports seemed to be changed to include .js on the end of the filename. This caused merge conflicts when I was trying to rebase a branch targeted at release52 onto release53.

New Behavior

Use an eslint plugin to enforce extensions on file imports. This enabled me to automatically resolve a bunch of merge conflicts using my git resolve-formatting-conflicts tool.

Currently I have applied this is just to the webui package because that's where I was working. It could probably be done more widely.

Testing

  • I have added one or more unit tests for this PR
  • I have updated the relevant unit tests
  • No unit test changes are needed for this PR

Affected areas

This PR affects code formatting and it's enforcement.

Time Frame

  • Not urgent

Other Information

It looks like there is a script to do the modification here: 88c500f. The advantage of this is that it is part of normal ESLint formatting, so IDEs and will highlight and auto-correct, CI will catch it, and it will work with tools like git resolve-formatting-conflicts without any further customisation.

Status

  • PR is ready to be reviewed.
  • The functionality has been tested by the author.
  • Relevant unit tests has been added / updated.
  • Relevant documentation (code comments, system documentation) has been added / updated.

Summary

This pull request introduces an ESLint rule to standardize and enforce file extensions (.js, .jsx) on import statements within the webui package, addressing recurring merge conflicts caused by inconsistent import formats between release versions.

Key Changes

ESLint Configuration

  • Added eslint-plugin-n to the ESLint configuration in packages/webui/eslint.config.mjs
  • Configured the n/file-extension-in-import rule set to 'error' to enforce extensions on imports
  • Extended the plugin settings with tryExtensions including .js, .cjs, .mjs, .json, .node, .ts, .cts, .mts, .d.ts, and .tsx

Import Path Updates
Systematically updated import statements across approximately 40 files in packages/webui/src/client/ to include explicit file extensions:

  • Updated relative imports to include .js extensions (e.g., './lib''./lib.js', '../../collections''../../collections/index.js')
  • Updated JSX/TSX component imports to include .jsx or .js extensions as appropriate
  • Affected files include components, hooks, and utilities across various subdirectories (RundownView, Settings, TestTools, Shelf, etc.)

Impact

  • No functional logic changes; all modifications are module resolution path updates
  • Leverages the ESLint plugin to automatically enforce and fix import extensions, integrating with IDE auto-correct and CI checks
  • Helps prevent future merge conflicts caused by inconsistent import formatting between releases
  • Changes are compatible with existing code structure and control flow

@codecov-commenter
Copy link

codecov-commenter commented Oct 16, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@rjmunro rjmunro force-pushed the rjmunro/improve-eslint branch from 95848d1 to 33949da Compare October 16, 2025 14:12
@rjmunro rjmunro marked this pull request as ready for review October 22, 2025 13:26
@rjmunro rjmunro requested a review from a team as a code owner October 22, 2025 13:26
Copy link
Member

@Julusian Julusian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like for the backend code there is already a rule in place for this. Perhaps this should use the same plugin instead of adding another one?

I believe that rule is https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/file-extension-in-import.md

That plugin was omitted from the webui as its not using node at runtime, so some of the rules dont make sense. But maybe there are a handful of rules in that plugin which do make sense to enable?

'@typescript-eslint/unbound-method': 'off',
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
'import-extensions/require-extensions': ['error', { expectedExtensions: ['js'] }],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These shouldn't be added here, as these are permanent additions, not Temporary rules to be removed over time

This was confusing ESLint because it effectively overrode packages/webui/src/client/ui/Collections.ts
By running eslint --fix

fixup! chore: Ensure all imports have .js extension where applicable
Needed on Linux macOS and other unix-like systems.
@rjmunro rjmunro force-pushed the rjmunro/improve-eslint branch from ec01055 to 09eb0c6 Compare January 12, 2026 19:26
@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

Walkthrough

The PR adds eslint-plugin-n ESLint configuration to enforce explicit file extensions in imports, then systematically updates approximately 35 import statements across the webui client package to include .js/.jsx extensions for local module resolution.

Changes

Cohort / File(s) Summary
ESLint Configuration
packages/webui/eslint.config.mjs
Imports eslint-plugin-n and registers it with ESLint config, adds n/file-extension-in-import rule set to 'error', and configures tryExtensions to include .js, .cjs, .mjs, .json, .node, .ts, .cts, .mts, .d.ts, .tsx.
Core Lib Module Imports
packages/webui/src/client/lib/Components/BreadCrumbTextInput.tsx, packages/webui/src/client/lib/Components/MultiLineIntInput.tsx, packages/webui/src/client/lib/Moment.tsx, packages/webui/src/client/lib/VirtualElement.tsx, packages/webui/src/client/ui/PreviewPopUp/Previews/LayerInfoPreview.tsx
Updated import paths for local modules to include explicit .js extensions (e.g., ./lib./lib.js, ./viewPort./viewPort.js).
RundownView Component Imports
packages/webui/src/client/ui/RundownView.tsx, packages/webui/src/client/ui/RundownView/CasparCGRestartButtons.tsx, packages/webui/src/client/ui/RundownView/RundownDetachedShelf.tsx, packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeader.tsx, packages/webui/src/client/ui/RundownView/RundownHeader/RundownReloadResponse.ts, packages/webui/src/client/ui/RundownView/RundownHeader/TimingDisplay.tsx, packages/webui/src/client/ui/RundownView/RundownHeader/useRundownPlaylistOperations.tsx, packages/webui/src/client/ui/RundownView/RundownSorensenContext.tsx, packages/webui/src/client/ui/RundownView/RundownViewContextProviders.tsx, packages/webui/src/client/ui/RundownView/RundownViewSubscriptions.ts, packages/webui/src/client/ui/RundownView/WarningDisplay.tsx, packages/webui/src/client/ui/RundownView/useQueueMiniShelfAdlib.ts
Updated numerous import paths with explicit .js and .jsx extensions; includes index.js paths for directory imports (e.g., ../../collections../../collections/index.js).
UI Component Imports
packages/webui/src/client/ui/Settings/Studio/PackageManager/AccessorTableRow.tsx, packages/webui/src/client/ui/Shelf/Shelf.tsx, packages/webui/src/client/ui/TestTools/DeviceTriggers.tsx, packages/webui/src/client/ui/TestTools/IngestRundownStatus.tsx, packages/webui/src/client/ui/TestTools/Mappings.tsx, packages/webui/src/client/ui/TestTools/Timeline.tsx, packages/webui/src/client/ui/TestTools/TimelineDatastore.tsx, packages/webui/src/client/ui/TestTools/collections.ts, packages/webui/src/client/ui/TestTools/index.tsx
Updated import paths to include explicit .js and .jsx file extensions for local module resolution (e.g., ./collections./collections.js).
Test Files
packages/webui/src/client/lib/__tests__/lib.test.ts
Updated import path from ../lib to ../lib.js for test hook imports.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Extensions, oh extensions bright,
Now explicit in every import sight!
.js and .jsx on every line,
Module resolution now feels so fine!
Hops with glee—ESLint rules align! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Add ESLint rule to standardise file extensions on imports' accurately summarizes the main change: introducing an ESLint rule (n/file-extension-in-import) to enforce explicit file extensions in import statements across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rjmunro rjmunro requested a review from Julusian January 12, 2026 19:26
@rjmunro
Copy link
Contributor Author

rjmunro commented Jan 12, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @packages/webui/eslint.config.mjs:
- Around line 29-31: The tryExtensions array in the configuration object (the n:
{ tryExtensions: [...] } block) is missing the '.jsx' entry; update that
tryExtensions list to include '.jsx' (e.g., add '.jsx' alongside other
extensions) so JSX imports are resolvable by the plugin and consistent with the
PR's enforcement of .jsx imports.
🧹 Nitpick comments (3)
packages/webui/src/client/lib/__tests__/lib.test.ts (1)

1-1: Remove stale comment.

The comment // Adjust the import path as needed appears to be a leftover reminder from when the path needed adjustment. Since the path has now been updated, this comment should be removed.

🧹 Suggested cleanup
-import { useCurrentTime } from '../lib.js' // Adjust the import path as needed
+import { useCurrentTime } from '../lib.js'
packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeader.tsx (1)

15-17: Consider consolidating imports from the same module.

Two separate imports from ../../../lib/lib.js could be combined into a single import statement for clarity.

♻️ Suggested consolidation
-import { useRundownViewEventBusListener } from '../../../lib/lib.js'
 import { RundownLayoutRundownHeader } from '@sofie-automation/meteor-lib/dist/collections/RundownLayouts'
-import { contextMenuHoldToDisplayTime } from '../../../lib/lib.js'
+import { useRundownViewEventBusListener, contextMenuHoldToDisplayTime } from '../../../lib/lib.js'
packages/webui/eslint.config.mjs (1)

15-15: Consider whether this rule belongs in tmpRules.

The comment indicates tmpRules contains "Temporary rules to be removed over time," but n/file-extension-in-import appears to be a permanent enforcement rule for ES module conventions rather than a temporary workaround.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3bae757 and 09eb0c6.

📒 Files selected for processing (30)
  • .husky/pre-commit
  • packages/webui/eslint.config.mjs
  • packages/webui/src/client/lib/Components/BreadCrumbTextInput.tsx
  • packages/webui/src/client/lib/Components/MultiLineIntInput.tsx
  • packages/webui/src/client/lib/Moment.tsx
  • packages/webui/src/client/lib/VirtualElement.tsx
  • packages/webui/src/client/lib/__tests__/lib.test.ts
  • packages/webui/src/client/ui/Collections/index.ts
  • packages/webui/src/client/ui/PreviewPopUp/Previews/LayerInfoPreview.tsx
  • packages/webui/src/client/ui/RundownView.tsx
  • packages/webui/src/client/ui/RundownView/CasparCGRestartButtons.tsx
  • packages/webui/src/client/ui/RundownView/RundownDetachedShelf.tsx
  • packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeader.tsx
  • packages/webui/src/client/ui/RundownView/RundownHeader/RundownReloadResponse.ts
  • packages/webui/src/client/ui/RundownView/RundownHeader/TimingDisplay.tsx
  • packages/webui/src/client/ui/RundownView/RundownHeader/useRundownPlaylistOperations.tsx
  • packages/webui/src/client/ui/RundownView/RundownSorensenContext.tsx
  • packages/webui/src/client/ui/RundownView/RundownViewContextProviders.tsx
  • packages/webui/src/client/ui/RundownView/RundownViewSubscriptions.ts
  • packages/webui/src/client/ui/RundownView/WarningDisplay.tsx
  • packages/webui/src/client/ui/RundownView/useQueueMiniShelfAdlib.ts
  • packages/webui/src/client/ui/Settings/Studio/PackageManager/AccessorTableRow.tsx
  • packages/webui/src/client/ui/Shelf/Shelf.tsx
  • packages/webui/src/client/ui/TestTools/DeviceTriggers.tsx
  • packages/webui/src/client/ui/TestTools/IngestRundownStatus.tsx
  • packages/webui/src/client/ui/TestTools/Mappings.tsx
  • packages/webui/src/client/ui/TestTools/Timeline.tsx
  • packages/webui/src/client/ui/TestTools/TimelineDatastore.tsx
  • packages/webui/src/client/ui/TestTools/collections.ts
  • packages/webui/src/client/ui/TestTools/index.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Package (main) (22.x, corelib, true)
  • GitHub Check: Test Package (main) (22.x, job-worker, true)
  • GitHub Check: Publish Docs
🔇 Additional comments (33)
packages/webui/src/client/ui/PreviewPopUp/Previews/LayerInfoPreview.tsx (1)

2-2: LGTM!

The import path correctly updated to include the explicit .js extension, consistent with the new ESLint rule being enforced across the webui package.

packages/webui/src/client/lib/Components/MultiLineIntInput.tsx (1)

1-1: LGTM!

The import path correctly includes the .js extension, aligning with the new ESLint rule for explicit file extensions. This is the proper approach for ES module resolution with compiled TypeScript.

packages/webui/src/client/lib/VirtualElement.tsx (1)

3-3: LGTM!

The explicit .js extension aligns with ES module resolution requirements. TypeScript correctly resolves .js imports to their corresponding .ts/.tsx source files during compilation.

packages/webui/src/client/ui/RundownView.tsx (1)

30-30: LGTM!

The import path updates to include explicit .js extensions are consistent with the rest of the file and align with the PR's goal of standardizing ES module resolution.

Also applies to: 41-41

packages/webui/src/client/ui/TestTools/index.tsx (1)

12-12: LGTM!

The .jsx extension is appropriate for this import, consistent with the file type convention used in this codebase.

packages/webui/src/client/lib/Components/BreadCrumbTextInput.tsx (1)

25-25: LGTM!

The import path update to include the explicit .jsx extension is consistent with the PR's module resolution standardization effort.

packages/webui/src/client/ui/RundownView/WarningDisplay.tsx (1)

5-6: LGTM!

The import path updates to include explicit .js extensions are consistent with the ES module resolution standardization across the codebase.

packages/webui/src/client/ui/TestTools/IngestRundownStatus.tsx (1)

1-11: LGTM!

The import path updates correctly add explicit file extensions (.jsx for JSX files, .js for TypeScript-compiled modules), aligning with ES module resolution conventions enforced by the new ESLint rule.

packages/webui/src/client/ui/Settings/Studio/PackageManager/AccessorTableRow.tsx (1)

20-20: LGTM!

The import path update for IntInputControl now includes the .js extension, consistent with the other component imports in this file.

packages/webui/src/client/ui/TestTools/collections.ts (1)

2-2: LGTM!

The import path update correctly adds the .js extension for the compiled TypeScript module, consistent with ES module resolution.

packages/webui/src/client/ui/TestTools/DeviceTriggers.tsx (1)

2-2: LGTM!

The import path updates correctly add explicit extensions (.jsx for the React Meteor data hooks, .js for the local collections module), maintaining consistency with the ES module resolution pattern.

Also applies to: 11-11

packages/webui/src/client/ui/TestTools/Timeline.tsx (1)

29-29: LGTM!

The import path update for StudioTimeline now includes the .js extension, consistent with the other imports in this file.

packages/webui/src/client/ui/TestTools/Mappings.tsx (1)

14-14: LGTM!

The import extension update to ./collections.js aligns with the PR's ESLint rule enforcement for explicit file extensions.

packages/webui/src/client/ui/RundownView/RundownHeader/TimingDisplay.tsx (1)

6-14: LGTM!

All import paths correctly updated with explicit .js extensions, consistent with the ES module resolution standardization.

packages/webui/src/client/lib/Moment.tsx (1)

3-3: LGTM!

Import path correctly updated with explicit .js extension.

packages/webui/src/client/ui/RundownView/RundownHeader/useRundownPlaylistOperations.tsx (7)

4-6: LGTM!

Import paths correctly updated with explicit .js extensions for clientUserAction, meteorApi, and ModalDialog modules.


9-9: LGTM!

Import path for UserPermissions correctly updated.


12-12: LGTM!

Import path for logging correctly updated.


14-14: LGTM!

Import path for notifications correctly updated.


18-20: LGTM!

Import paths for RundownReloadResponse, viewPort, and lib correctly updated.


23-23: LGTM!

Import path for systemTime correctly updated.


25-25: LGTM!

Import path for WarningDisplay correctly updated.

packages/webui/src/client/ui/RundownView/RundownSorensenContext.tsx (1)

6-13: LGTM!

Import paths correctly updated with explicit .js extensions. All imports reference real files in the codebase.

packages/webui/src/client/ui/TestTools/TimelineDatastore.tsx (1)

1-2: LGTM!

The import path updates correctly apply explicit file extensions (.jsx for React component files and .js for collection modules), aligning with the ESLint standardization effort.

Also applies to: 10-10

packages/webui/src/client/ui/RundownView/RundownDetachedShelf.tsx (1)

7-13: LGTM!

The import paths are correctly updated with explicit .js extensions for all local module imports, consistent with the project-wide ESM standardization.

packages/webui/src/client/ui/RundownView/useQueueMiniShelfAdlib.ts (1)

4-6: LGTM!

The import paths are correctly updated with explicit .js extensions for all local module imports, aligning with the ESLint standardization effort.

Also applies to: 8-8, 20-23

packages/webui/src/client/ui/RundownView/RundownViewContextProviders.tsx (1)

2-6: LGTM!

The import paths are correctly updated with explicit .js extensions, maintaining consistency with the project-wide ESM standardization.

Also applies to: 8-8

packages/webui/src/client/ui/RundownView/CasparCGRestartButtons.tsx (1)

10-15: LGTM!

The import paths are correctly updated with explicit .js extensions. Using index.js explicitly for the collections import (line 10) is a good practice for ESM resolution clarity.

packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeader.tsx (1)

5-5: LGTM!

The .js extensions on relative imports align with ES module resolution requirements. The import paths are correctly structured relative to the file location.

Also applies to: 11-14, 24-24, 26-28, 32-37

packages/webui/eslint.config.mjs (1)

25-27: LGTM!

The plugin is correctly registered in the flat config format.

packages/webui/src/client/ui/RundownView/RundownHeader/RundownReloadResponse.ts (1)

1-1: LGTM!

The .js extensions are correctly applied to all relative imports, including the index.js barrel file reference.

Also applies to: 7-7, 9-10, 15-17

packages/webui/src/client/ui/RundownView/RundownViewSubscriptions.ts (1)

6-6: LGTM!

The .js extensions are correctly applied to all relative imports, maintaining consistency with the ESLint rule configuration.

Also applies to: 11-11, 13-14, 16-16

packages/webui/src/client/ui/Shelf/Shelf.tsx (1)

44-48: LGTM! Import extensions standardized correctly.

The addition of explicit .js extensions to these three relative imports is correct and consistent with the ES module pattern used throughout the rest of the file. All relative imports now include extensions while external package imports correctly omit them.

Comment on lines +29 to +31
n: {
tryExtensions: ['.js', '.cjs', '.mjs', '.json', '.node', '.ts', '.cts', '.mts', '.d.ts', '.tsx'],
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing .jsx extension in tryExtensions.

The PR objective mentions enforcing .jsx extensions on imports, but .jsx is not included in the tryExtensions list. This may cause the plugin to fail to resolve JSX imports correctly.

🔧 Suggested fix
 			n: {
-				tryExtensions: ['.js', '.cjs', '.mjs', '.json', '.node', '.ts', '.cts', '.mts', '.d.ts', '.tsx'],
+				tryExtensions: ['.js', '.jsx', '.cjs', '.mjs', '.json', '.node', '.ts', '.cts', '.mts', '.d.ts', '.tsx'],
 			},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
n: {
tryExtensions: ['.js', '.cjs', '.mjs', '.json', '.node', '.ts', '.cts', '.mts', '.d.ts', '.tsx'],
},
n: {
tryExtensions: ['.js', '.jsx', '.cjs', '.mjs', '.json', '.node', '.ts', '.cts', '.mts', '.d.ts', '.tsx'],
},
🤖 Prompt for AI Agents
In @packages/webui/eslint.config.mjs around lines 29 - 31, The tryExtensions
array in the configuration object (the n: { tryExtensions: [...] } block) is
missing the '.jsx' entry; update that tryExtensions list to include '.jsx'
(e.g., add '.jsx' alongside other extensions) so JSX imports are resolvable by
the plugin and consistent with the PR's enforcement of .jsx imports.

@rjmunro
Copy link
Contributor Author

rjmunro commented Jan 13, 2026

@Julusian I tried using the other plugin. It incorrectly added a few more .js extensions where the import was a folder which caused errors.

I also noticed that metor has:

		'n/file-extension-in-import': ['error', 'never'], // Meteor breaks on importing ts files with a js extension

in it's eslint config, i.e. disabling all .js extensions in imports. I wonder why we have it set at all in sofie-standard-code-preset. I'd rather remove them all globally. I think it's much neater, especially as the mostly aren't js files, they are ts or tsx files that are complied to js files. Are they actually needed anywhere?

@Julusian
Copy link
Member

I wonder why we have it set at all in sofie-standard-code-preset.

For any packages which are actually generated as ESM, the extensions are necessary. So I thought it easier to enforce everywhere rather than having that variation based on how the project is generated.
We have a couple of things which are ESM, and I want to push more things across. Most notably, vite gets a bit upset that
meteor-lib and other dependencies in the mono-repo arent esm, which is why it doesnt always detect all changes without a restart.

It incorrectly added a few more .js extensions where the import was a folder which caused errors.

Yeah, in ESM you can't import a folder, those imports will need to be adjusted to point to the index file within the folder

@rjmunro
Copy link
Contributor Author

rjmunro commented Jan 13, 2026

For any packages which are actually generated as ESM, the extensions are necessary.

Can you not configure typescript or a bundler or something to do that at compile time?

@Julusian
Copy link
Member

Julusian commented Jan 13, 2026

typescript will not (https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#path-rewriting-for-relative-paths talks about some ts->js conversion, but states it doesnt change extensionless)
Maybe we should even be using that setting and import everything as .ts

Most bundler probably can, but then we would need to deal with the complexity of that extra build step.
And this is something that will apply to all the sofie libraries eventually (if we start migrating them to esm), not just this repository

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants