-
Notifications
You must be signed in to change notification settings - Fork 142
Migrate packages/vue-components Utils files to TS #2834
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
base: master
Are you sure you want to change the base?
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2834 +/- ##
=======================================
Coverage 72.09% 72.09%
=======================================
Files 134 134
Lines 7411 7411
Branches 1587 1550 -37
=======================================
Hits 5343 5343
Misses 1940 1940
Partials 128 128 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR incrementally migrates packages/vue-components/src/utils from JavaScript to TypeScript and wires up the vue-components build/test/lint toolchain to handle .ts utilities without adding a separate compilation output step.
Changes:
- Added vue-components TypeScript typechecking (
tsconfig.json,npm run typecheck) and Babel TS transpilation (@babel/preset-typescript). - Updated Jest + Webpack + ESLint configurations to resolve/transform
.tsfiles and.vueimports correctly. - Migrated utility modules (including
NodeList) from.jsto.tsand updatedSubmenu.vueto match the new submenu exports.
Reviewed changes
Copilot reviewed 18 out of 20 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/vue-components/tsconfig.json | Adds TS project config for vue-components typechecking (no emit). |
| packages/vue-components/src/utils/utils.ts | TS migration of general utilities; refactors getJSON to a real Promise. |
| packages/vue-components/src/utils/utils.js | Removes legacy JS utility implementation. |
| packages/vue-components/src/utils/urls.ts | Adds TS typings to URL normalization util. |
| packages/vue-components/src/utils/submenu.ts | Adds typings and converts submenu util to named exports. |
| packages/vue-components/src/utils/pubsub.ts | Adds typings to pub/sub utility. |
| packages/vue-components/src/utils/dropdown.ts | Adds typings + safer DOM handling for dropdown overflow logic. |
| packages/vue-components/src/utils/colors.ts | Adds typings to bootstrap color utilities. |
| packages/vue-components/src/utils/NodeList.ts | TS migration of the custom DOM wrapper class. |
| packages/vue-components/src/utils/NodeList.js | Removes legacy JS NodeList implementation. |
| packages/vue-components/src/Submenu.vue | Updates imports/usage for the new named submenu exports. |
| packages/vue-components/package.json | Adds typecheck script and TS Babel preset dependency. |
| packages/vue-components/jest.config.js | Enables resolving and transforming .ts with babel-jest. |
| packages/vue-components/babel.config.js | Adds @babel/preset-typescript for TS transpilation. |
| packages/vue-components/.eslintrc.js | Overrides TS rule to allow default exports in vue-components utils. |
| packages/core-web/webpack.common.js | Allows webpack to resolve/transform .ts alongside .js. |
| .eslintrc.js | Updates import resolver/extensions and TS lint project list to include vue-components. |
| .eslintignore | Extends ignores for compiled utils JS and legacy-lint-violating TS utils. |
| .gitignore | Ignores potential compiled JS artifacts for vue-components utils. |
| package-lock.json | Lockfile updates reflecting added/updated dependencies for TS tooling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (window && window.Symbol && Symbol.iterator) { | ||
| NL[Symbol.iterator] = NL.values = ArrayProto[Symbol.iterator]; | ||
| } | ||
| const div: HTMLDivElement | null = document && document.createElement('div'); | ||
| function setterGetter(prop: string): void { |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
window/document are referenced directly (e.g., if (window && ...) and document && ...) which will throw a ReferenceError in non-browser runtimes (SSR, Node without jsdom) just by evaluating the module. Prefer typeof window !== 'undefined' / typeof document !== 'undefined' guards (and avoid using window/document in expressions unless those checks pass).
| NL[prop] = (...args: any[]) => { | ||
| const arr: any[] = []; | ||
| let returnThis = true; | ||
| for (const i in NL) { | ||
| let el = NL[i]; | ||
| if (el && el[prop] instanceof Function) { | ||
| el = el[prop].apply(el, args); | ||
| arr.push(el); | ||
| if (returnThis && el !== undefined) { | ||
| returnThis = false; | ||
| } | ||
| } else { | ||
| arr.push(undefined); | ||
| } | ||
| } | ||
| return returnThis ? NL : flatten(arr, NL); |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In setterGetter, the generated function-method implementation iterates over NL (the prototype) rather than the NodeList instance, so any dynamically-added DOM methods (e.g. focus, click, etc.) will be no-ops / behave incorrectly. This should iterate over this (the current NodeList instance) and return this when appropriate.
| NL[prop] = (...args: any[]) => { | |
| const arr: any[] = []; | |
| let returnThis = true; | |
| for (const i in NL) { | |
| let el = NL[i]; | |
| if (el && el[prop] instanceof Function) { | |
| el = el[prop].apply(el, args); | |
| arr.push(el); | |
| if (returnThis && el !== undefined) { | |
| returnThis = false; | |
| } | |
| } else { | |
| arr.push(undefined); | |
| } | |
| } | |
| return returnThis ? NL : flatten(arr, NL); | |
| NL[prop] = function (...args: any[]) { | |
| const arr: any[] = []; | |
| let returnThis = true; | |
| const self: any = this; | |
| for (let i = 0; i < self.length; i++) { | |
| let el = self[i]; | |
| if (el && el[prop] instanceof Function) { | |
| const result = el[prop].apply(el, args); | |
| arr.push(result); | |
| if (returnThis && result !== undefined) { | |
| returnThis = false; | |
| } | |
| } else { | |
| arr.push(undefined); | |
| } | |
| } | |
| return returnThis ? self : flatten(arr, self); |
|
|
||
| function splitWords(val: string): string[] { | ||
| val = val.trim(); | ||
| return val.length ? val.replace(/\s+/, ' ').split(' ') : []; |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
splitWords only normalizes the first run of whitespace because the regex is missing the global flag. This means strings with multiple spaces/tabs can still produce empty/extra tokens after split(' '). Use a global whitespace replacement so all runs are collapsed before splitting.
| return val.length ? val.replace(/\s+/, ' ').split(' ') : []; | |
| return val.length ? val.replace(/\s+/g, ' ').split(' ') : []; |
| boolean: (val: any): boolean | any => (typeof val === 'string' | ||
| ? val === '' || val === 'true' | ||
| ? true | ||
| : (val === 'false' || val === 'null' || val === 'undefined' ? false : val) | ||
| : val), | ||
| // Attempt to convert a string value to a Number. Otherwise, return alt. | ||
| number: (val: any, alt: number | null = null): number | null => (typeof val === 'number' | ||
| ? val | ||
| : val === undefined || val === null || isNaN(Number(val)) | ||
| ? alt | ||
| : Number(val)), | ||
| // Attempt to convert to string any value, except for null or undefined. | ||
| string: (val: any): string => (val === undefined || val === null ? '' : val + ''), | ||
| // Pattern accept RegExp, function, or string (converted to RegExp). Otherwise return null. | ||
| pattern: (val: any): Function | RegExp | null => (val instanceof Function || val instanceof RegExp | ||
| ? val | ||
| : typeof val === 'string' | ||
| ? new RegExp(val) | ||
| : null), | ||
| }; | ||
|
|
||
| export function toBoolean(val: any): boolean | any { | ||
| return (typeof val === 'string') | ||
| ? ((val === '' || val === 'true') | ||
| ? true | ||
| : ((val === 'false' || val === 'null' || val === 'undefined') | ||
| ? false | ||
| : val)) | ||
| : val; |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return types like boolean | any (and toBoolean(): boolean | any) effectively collapse to any, which defeats the purpose of the annotation and can mislead callers. Consider returning unknown (or a tighter union like boolean | string) if the function intentionally preserves non-boolean values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we just use TypeScript type casting? @MarkBind/active-3281-members
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As someone who hasn't even touched this section of code, this looks a little odd to me. Is there a reason why it's returning boolean | any instead of just boolean?
I mean, the function's name is TO boolean right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
while i’m not exactly sure about the usage of this piece of code too, it seems to only convert the known feel entities into the corresponding boolean (with custom logic vs the default truth and false) values while keeping other objects the same.
Also, from a quick grep (thanks rg) in the repo this function is used in exactly one place too so i wasn’t sure why does it exist in the first place. perhaps @gerteck can give some inputs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The base vue components were not written internally, but from an external source, vue-strap, which was later merged in.
(See
- https://markbind.org/devdocs/devGuide/design/projectStructure.html#ui-components-library
- https://github.com/MarkBind/vue-strap
- https://www.npmjs.com/package/vue-strap
)
My guess is as good as yours, but probably the original npm package authors just copied a set of utilities as some skeletal code when building the components, not borne out of necessity.
Reason why its returning any is because if it is not the recognized boolean forms, it returns the value as is, like @yihao03 mentioned
Since it's been merged in, feel free to refactor it accordingly if needed 👍 , I don't think anyone saw the need to yet since there are greater yield issues to tackle
Add a tsconfig.json that extends the shared tsconfig_base.json to enable type-checking for the migrated .ts files in vue-components. Key decisions: - noEmit: true — Babel handles transpilation via webpack and jest, so tsc is used only for type-checking, not compilation. This avoids duplicating build output and keeps the existing Babel pipeline intact. - allowJs: true — The package still has .js files (e.g. src/index.js, .vue script blocks) that .ts files import from. Without this, tsc would fail to resolve those imports. - checkJs: false — Only type-check .ts files. The remaining .js files are not yet migrated and would produce many errors under strict mode. - Not added to root tsconfig.json references — The root references drive `tsc --build` for packages that emit compiled .js files (core, cli). Since vue-components uses noEmit, adding it there would be misleading and would require composite: true which conflicts with noEmit.
5e577f1 to
6218dc4
Compare
The coerce object was only used at one place and is functionally the same as toBoolean
5318abf to
8b15419
Compare
Harjun751
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should've grabbed a drink before I started reviewing this!
Great work, though I just require a few clarifications here and there.
|
|
||
| function isRightAlign(el) { | ||
| const viewport = { | ||
| interface Rect { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use a type alias here over an interface?
My understanding is that type aliases are used when the object is not intended to be extended, which I think in this case applies? (Relevant documentation here)
But also it's odd to me as I'm mostly used to having interfaces named as "{VERB}able" (like an adjective) 😅
But I'll be the first to admit that I don't have much experience with TS, is this common standard/convention?
| packages/vue-components/src/utils/submenu.js | ||
| packages/vue-components/src/utils/colors.js | ||
| packages/vue-components/src/utils/utils.js | ||
| packages/vue-components/src/utils/NodeList.js |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With babel for transpilation, do we need to ignore these files? My impression is that they do not get generated.
If they still do, would it be appropriate to use a rule like "packages/vue-components/src/util/*.js"?
| packages/vue-components/src/utils/NodeList.js | ||
|
|
||
| # Ignore compiled JS from TS | ||
| packages/vue-components/src/utils/pubsub.js |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as .gitignore comment - is this required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a tsconfig.json if there is no separate compilation step as described?
Or is this for later, when migrating other files in the package?
| "test": "jest --clearCache && jest --colors", | ||
| "updatetest": "jest --updateSnapshot" | ||
| "updatetest": "jest --updateSnapshot", | ||
| "typecheck": "tsc" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a particular reason why the typecheck script is required? It looks to me that type checking is still fine and works in my IDE.
If the intended developer workflow here is slightly different or requires an extra step, should we update the developer documentation?
What is the purpose of this pull request?
Overview of changes:
replaces the preceding PR #2805 to incrementally migrate the
packages/vue-componentsto TypeScript.Migrated all 7 utility files in packages/vue-components/src/utils/
from JavaScript to TypeScript, following the incremental migration
pattern established in the developer guide.
files
Files migrated:
Anything you'd like to highlight/discuss:
emit .js files, vue-components uses Babel for transpilation. TypeScript
types are stripped during Jest/Webpack transforms with no files written
to disk. This maintains the existing build pipeline while adding type safety.
with pre-existing lint violations that existed before migration. They were
already ignored as .js files; the .ts versions are now also ignored. Fixing
these violations is outside the scope of this PR.
enforces named exports for TypeScript, but vue-components conventionally uses
default exports for single-function utility modules consumed by .vue files via
default imports.
Testing instructions:
Run tests in vue-components
Proposed commit message: (wrap lines at 72 characters)
Migrate packages/vue-components/src/utils to TypeScript
Migrate all 7 utility files in vue-components/src/utils from JavaScript
to TypeScript. Add TypeScript infrastructure including @babel/preset-
typescript for transpilation and tsconfig.json for type-checking.
Key changes:
All 153 tests pass. Type-checking and lint pass cleanly.
Checklist: ☑️
Reviewer checklist:
Indicate the SEMVER impact of the PR:
At the end of the review, please label the PR with the appropriate label:
r.Major,r.Minor,r.Patch.Breaking change release note preparation (if applicable):