Skip to content

Conversation

@yihao03
Copy link
Contributor

@yihao03 yihao03 commented Feb 11, 2026

What is the purpose of this pull request?

  • Documentation update
  • Bug fix
  • Feature addition or enhancement
  • Code maintenance
  • DevOps
  • Improve developer experience
  • Others, please explain:

Overview of changes:
replaces the preceding PR #2805 to incrementally migrate the
packages/vue-components to 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.

  • Added @babel/preset-typescript to enable Babel-based TypeScript transpilation
  • Configured Jest and Webpack to process .ts files
  • Added tsconfig.json for type-checking via npm run typecheck
  • Updated ESLint configuration to handle .ts files and resolve imports from .vue
    files
  • Added import/resolver settings for .ts files to the root ESLint config

Files migrated:

  1. pubsub.ts - Simple event pub/sub module
  2. urls.ts - URL normalization utility
  3. dropdown.ts - Mobile dropdown overflow prevention
  4. submenu.ts - Submenu positioning utilities (converted to named exports)
  5. colors.ts - Bootstrap color utilities
  6. utils.ts - General utilities (refactored getJSON to native Promise)
  7. NodeList.ts - Custom DOM wrapper class (most complex)

Anything you'd like to highlight/discuss:

  1. No separate compilation step - Unlike packages/core which uses tsc to
    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.
  2. NodeList.ts and utils.ts in .eslintignore - These files contain legacy code
    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.
  3. import/no-default-export disabled for .ts files - The root ESLint config
    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

cd packages/vue-components
npm test          # All 153 tests should pass
npm run typecheck # TypeScript type-checking (no errors)
# Run lint from root
npm run lint      # Should pass cleanly

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:

  • Add @babel/preset-typescript and configure Jest/Webpack for .ts files
  • Add tsconfig.json with noEmit:true (Babel handles transpilation)
  • Refactor getJSON to use native Promise (47 → 18 lines)
  • Convert submenu default export to named exports
  • Update ESLint to resolve .ts imports from .vue files
    All 153 tests pass. Type-checking and lint pass cleanly.

Checklist: ☑️

  • Updated the documentation for feature additions and enhancements
  • Added tests for bug fixes or features
  • Linked all related issues
  • No unrelated changes

Reviewer checklist:

Indicate the SEMVER impact of the PR:

  • Major (when you make incompatible API changes)
  • Minor (when you add functionality in a backward compatible manner)
  • Patch (when you make backward compatible bug fixes)

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):

  • To be included in the release note for any feature that is made obsolete/breaking

Give a brief explanation note about:

  • what was the old feature that was made obsolete
  • any replacement feature (if any), and
  • how the author should modify his website to migrate from the old feature to the replacement feature (if possible).

@codecov
Copy link

codecov bot commented Feb 11, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.09%. Comparing base (01b8ce4) to head (5318abf).

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

Copilot AI left a 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 .ts files and .vue imports correctly.
  • Migrated utility modules (including NodeList) from .js to .ts and updated Submenu.vue to 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.

Comment on lines +355 to +359
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 {
Copy link

Copilot AI Feb 11, 2026

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).

Copilot uses AI. Check for mistakes.
Comment on lines +362 to +377
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);
Copy link

Copilot AI Feb 11, 2026

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.

Suggested change
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);

Copilot uses AI. Check for mistakes.

function splitWords(val: string): string[] {
val = val.trim();
return val.length ? val.replace(/\s+/, ' ').split(' ') : [];
Copy link

Copilot AI Feb 11, 2026

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.

Suggested change
return val.length ? val.replace(/\s+/, ' ').split(' ') : [];
return val.length ? val.replace(/\s+/g, ' ').split(' ') : [];

Copilot uses AI. Check for mistakes.
Comment on lines 7 to 35
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;
Copy link

Copilot AI Feb 11, 2026

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.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

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

Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Member

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

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

@yihao03 yihao03 mentioned this pull request Feb 11, 2026
14 tasks
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.
@yihao03 yihao03 force-pushed the feat/migrate-vue-utils-to-ts branch from 5e577f1 to 6218dc4 Compare February 12, 2026 03:24
The coerce object was only used at one place and is functionally the
same as toBoolean
@yihao03 yihao03 marked this pull request as ready for review February 12, 2026 11:01
@yihao03 yihao03 force-pushed the feat/migrate-vue-utils-to-ts branch from 5318abf to 8b15419 Compare February 13, 2026 02:47
Copy link
Contributor

@Harjun751 Harjun751 left a 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 {
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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?

Copy link
Contributor

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"
Copy link
Contributor

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?

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