I'm looking for a way to implement a jQuery-style initialization pattern using TypeScript - how can I

My library utilizes a jQuery-like initialization pattern, along with some specific requirements for the types it should accept and return:

function JQueryInitializer ( selector /*: string | INSTANCE_OF_JQUERY*/ ) {
  if ( selector.__jquery ) return selector;
}
function initJQuery ( selector/*: string | INSTANCE_OF_JQUERY*/ )/*: INSTANCE_OF_JQUERY*/ {
  return new JQueryInitializer( selector );
}

const plugin = initJQuery.fn = initJQuery.prototype = JQueryInitializer.prototype = {
  constructor: initJQuery,
  __jquery: true
};

plugin.customFunction = function () {};
initJQuery( '*' ).customFunction; // => Function
const $mockObject = { __jquery: true, customFunction() {} };
initJQuery( $mockObject ) === $mockObject; // => true

Although it functions correctly in JavaScript, I am unsure how to properly type it in TypeScript.

Here are the challenges I'm facing:

  • I prefer not to create a separate declaration file as is done with jQuery, rather have TypeScript generate it
  • The minified outputted JavaScript size after compilation must remain relatively the same, maintaining code efficiency for my library
  • In JavaScript, I can return arbitrary values from functions called with new Foo(), whereas TypeScript restricts this behavior to only void functions
  • I need the ability to extend the prototype and ensure a well-defined generated declaration file

I haven't been able to resolve all these issues simultaneously. Any assistance would be greatly appreciated!

Answer №1

To tackle the issues you've presented in a systematic manner:

  • If we ensure our code is compliant with `Typescript` by setting the `declaration` option to true, the compiler can handle this task
  • Although the example you provided is small, once the initial setup is correct, the generated JS TS should be similar in size to plain JavaScript
  • Typescript serves as an extension of JS, where any valid JS code is also syntactically correct in TS. However, Typescript conducts additional semantic checks such as disallowing calling functions with `new` or mismatched types. It is advised to rectify semantic errors before compiling to JS.
  • Typescript offers various merging possibilities between interfaces, classes, and namespaces for achieving type-safe extensibility. Further information can be found here

In my proposed solution, I suggest utilizing a class instead of the function `JQueryConstructor` and implementing class-interface merging for enhanced extensibility. The revised approach would resemble:

class JQuery{
    __jquery!: boolean; // Just the declaration
    constructor(selector: string | JQuery)  {
        if(typeof selector !== 'string' && selector.__jquery) return selector;
    }
}
JQuery.prototype.__jquery = true;

function foo() { };
interface JQuery {
    foo: typeof foo
}
JQuery.prototype.foo = foo;



function jQuery(selector: string | JQuery): JQuery {
    return new JQuery(selector);
}


jQuery('*').foo; // => Function
const $mocked = { __jquery: true, foo() { } };
jQuery($mocked) === $mocked; // => true

The minified versions of both codes don't exhibit significant differences. On this compact example, your version occupies 300 bytes while my revised version compiled to es2015 uses 261 bytes and when compiled to es5, it amounts to 253 bytes (even with the longer name `JQueryConstructor`, it's still at 297 bytes). Although comparing sizes on such a small scale may not be entirely relevant, they appear comparable.

Following the above code, the subsequent definition is produced:

declare class JQuery {
    __jquery: boolean;
    constructor(selector: string | JQuery);
}
declare function foo(): void;
interface JQuery {
    foo: typeof foo;
}
declare function jQuery(selector: string | JQuery): JQuery;
declare const $mocked: {
    __jquery: boolean;
    foo(): void;
};

This generated definition will cater to all users and enable them to implement the same class-interface merging technique for extending functionality.

Answer №2

With the help of @titian-cernicova-dragomir, I have devised a solution that may seem lengthy because it requires explicitly defining all interfaces, but it gets the job done:

interface CustomjQuery {
  constructor: typeof jQueryConstructor,
  __customjquery: boolean
}

function checkIfCustomJQuery ( x ): x is CustomjQuery {
  return x && x['__customjquery'];
}

function CustomjQuery ( selector: string | CustomjQuery ): CustomjQuery {
  if (checkIfCustomJQuery(selector)) return selector;
  return new jQueryConstructor(selector);
}

function jQueryConstructor ( selector: string ) {}

const customFn = CustomjQuery.fn = CustomjQuery.prototype = jQueryConstructor.prototype = {
  constructor: jQueryConstructor,
  __customjquery: true
} as CustomjQuery;


interface CustomjQuery {
  foo ();
}

customFn.foo = function () {};
CustomjQuery('*').foo; // => Function
const $mocked = { __customjquery: true, foo () {} } as CustomjQuery;
CustomjQuery($mocked) === $mocked; // => true
CustomjQuery('*') instanceof CustomjQuery; // => true

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Utilize Boolean operators such as AND, OR, and NOT to locate specific keywords within a text, mirroring the search capabilities

Is it possible to perform Google-style searches in strings using operators like "or", "and" and "not" with regular expressions? For instance, I aim to search for the words "Javascript", "PHP" and "Perl" within a given string in these ways: Javascript an ...

Javascript code that enables me to specify the type of weather

My intention with this code was to create unique characteristics for different weather types and randomly select one by generating a random number. I defined 11 different weather types as objects of the Weather class. I then implemented a getWeather funct ...

Vuetify Autocomplete that allows for adding values not in the predefined list

I am utilizing a vuetify autocomplete component to showcase a list of names for users to select from. In the case where a user enters a name not on the list, I want to ensure that value is still accepted. Check out my code snippet below: <v-autocomplete ...

Endless loop in React Native with an array of objects using the useEffect hook

For the current project I am working on, I am facing the challenge of retrieving selected items from a Flatlist and passing them to the parent component. To tackle this issue, I have initialized a local state as follows: const [myState, setMyState] = useS ...

Issue with converting form data to JSON format

Having an issue converting a filled form in HTML to a JSON request for sending to the server via HTTP POST. Despite having a filled form, the JSON request only shows an empty array. Here is the JavaScript snippet: $("#submitSurveyBtn").on("click", functi ...

What is the proper way to type a collection and put it into action?

I am looking for a way to create an object that mimics a set. Specifically, I want the transaction id to act as a key and the transaction details as the value. To achieve this, I created the following: type TransactionDetail = { [key: TransactionId]: Tra ...

Tips on combining multiple HTML tags within a single div element

After extracting a value from an XML document using Javascript, I encountered a problem while attempting to generate multiple image tags based on that value. Despite my attempt with a for loop, only one image was being displayed. for(i=0; i<red; i++){ ...

Retrieving JSON information stored in a JavaScript variable

I'm feeling a bit embarrassed to admit it, but I am still learning the ropes when it comes to Javascript development. I've hit a roadblock and could really use some help from the experts here. Thank you in advance for all the assistance this comm ...

Altering the hover functionality for dynamically created class elements

I have a vision to create a unique e-commerce storefront with tile design, featuring an item thumbnail that reveals the item name upon hovering. I found inspiration from this example, but I want the item name to slide up smoothly on hover, rather than simp ...

Is it necessary for the version of the @types packages in TypeScript to match their non-types packages?

Are @types and untyped packages versioned the same way? npm i bluebird @types/bluebird -S returns "@types/bluebird": "^3.5.0", "bluebird": "^3.5.0", This seems logical. npm i request @types/request -S yields "@types/request": "0.0.41", "request": "^2. ...

Retrieving data for a route resolver involves sending HTTP requests, where the outcome of the second request is contingent upon the response from the first request

In my routing module, I have a resolver implemented like this: { path: 'path1', component: FirstComponent, resolve: { allOrders: DataResolver } } Within the resolve function of DataResolver, the following logic exists: re ...

Unable to change the color of InputBase component's placeholder in React.js

I've been attempting to modify the color of the placeholder in an inputbase. I came across several methods online and tried implementing them, but none have been successful. Below are the codes I have tried. <InputBase id="input-id&quo ...

The requirement of the second parameter being optional or required will depend on the value of the first

Is there a way to make the second parameter of my function optional or required based on the value of the first parameter? Here's an example code snippet: enum Endpoint { USERS = '/users/:userId', ORDERS = '/orders' } typ ...

Accessing a form by inputting a personal identification number

I am in the process of developing a web application form that requires users to make a payment before gaining access. After completing payment and receiving the PIN number, users must enter the number correctly to be granted access. If incorrect, access w ...

Leverage TypeScript generics to link props with state in a React class-based component

Can the state type be determined based on the prop type that is passed in? type BarProps = { availableOptions: any[] } type BarState = { selectedOption: any } export default class Bar extends React.Component<BarProps, BarState> { ...

Bring in exclusively typescript module declarations

In my various React projects, I find myself constantly declaring the same typescript modules, such as fonts.d.ts: declare module "*.woff"; declare module "*.woff2"; or images.d.ts: declare module "*.jpg" { const src: string ...

Error in NextJS: The name 'NextApplicationPage' cannot be found

const { Component, pageProps}: { Component: NextApplicationPage; pageProps: any } = props After implementing the code above with 'Component' type set to NextApplicationPage, an error message pops up stating, The name 'NextApplicationPage&ap ...

Firefox seems to handle webpages smoothly, whereas IE struggles to display them properly

Check out the code snippet below: self.xmlHttpReq = new XMLHttpRequest(); self.xmlHttpReq.onreadystatechange = function() { if(self.xmlHttpReq.readyState == 4 && self.xmlHttpReq.status == 200) { xmlDoc = self.xmlHttpReq.response ...

Using checkboxes to filter a list within a ReactiveForm can result in a rendering issue

I have implemented a dynamic form that contains both regular input fields and checkboxes organized in a list. There is also an input field provided to filter the checkbox list. Surprisingly, I found out that when using the dot (.) character in the search f ...

angular2 variable turns null during post request, synchronization breakdown

Currently, I am in the process of developing an ecommerce web application using Angular2 and have encountered a issue with saving ordered information in session. addToCart(productId:string,noOfItems:number):void{ let itemCounts; let selectedItems= ...