Automatically finds jQuery methods from existing projects and generates vanilla js alternatives.

Last update: Jul 29, 2022

npm license

Test coverage

Statements Functions Lines
Statements Functions Lines

Automatically replace jQuery

Automatically find jQuery methods from existing projects and generate vanilla js alternatives.

demo.mp4

Why

I've been working on removing jQuery dependency from multiple projects including lightGallery lately. Most of the projects use only 15% to 20% or less than 30% of the jquery methods And in most of the cases, I didn't want to support all the edge cases or legacy browsers. The hardest part was finding the jQuery methods in the existing project and writing the alternative vanilla js methods without making much changes in the codebase. So I wrote this library which automatically finds jquery methods in any particular JavaScript file and generates readable, chainable vanilla js alternatives. This can also be useful if you want to generate your own utility methods similar to jQuery.

Installation and Usage

You can install replace-jquery using npm:

npm install -g replace-jquery
  • Find all jQuery methods from sample.js and write vanillaJs alternatives in out.js
replace-jquery src/sample.js out.js
  • Find all jQuery methods from all matching files and write vanillaJs alternatives in out.js
replace-jquery "src/*.js" out.js
  • Build vanillaJs alternatives for the all available jQuery methods
replace-jquery --build-all out.js
  • Build vanillaJs alternatives for the specified jQuery methods
replace-jquery --methods "addClass, removeClass, attr" -o utils.js

Please note that, the utility functions generated by replace-jquery are not completely equivalent to jQuery methods in all scenarios. Please consider this as a starting point and validate before you adopt it.

Basic Concepts

The generated vanilla JS methods are chainable and can be applied to all matching elements like jQuery.

Note: The below code is just to demonstrate the basics concepts and not covered all scenarios.

export class Utils {
  constructor(selector) {
    this.elements = Utils.getSelector(selector);
    this.element = this.get(0);
    return this;
  }

  static getSelector(selector, context = document) {
    if (typeof selector !== 'string') {
      return selector;
    }
    if (isId(selector)) {
      return document.getElementById(selector.substring(1))
    }
    return context.querySelectorAll(selector);
  }

  each(func) {
    if (!this.elements) {
      return this;
    }
    if (this.elements.length !== undefined) {
      [].forEach.call(this.elements, func);
    } else {
      func(this.element, 0);
    }
    return this;
  }

  siblings() {
    if (!this.element) {
      return this;
    }
    const elements = [].filter.call(
      this.element.parentNode.children,
      (child) => child !== this.element
    );
    return new Utils(elements);
  }

  get(index) {
    if (index !== undefined) {
      return this.elements[index];
    }
    return this.elements;
  }

  addClass(classNames = '') {
    this.each((el) => {
      // IE doesn't support multiple arguments
      classNames.split(' ').forEach((className) => {
        el.classList.add(className);
      });
    });
    return this;
  }
}

export default function $utils(selector) {
  return new Utils(selector);
}

Usage

jQuery
  • React
  • Vue.js
  • Angular
  • Lit
  • ">
    <ul>
      <li class="jquery">jQueryli>
      <li class="react">Reactli>
      <li class="vue">Vue.jsli>
      <li class="angular">Angularli>
      <li class="lit">Litli>
    ul>
    .highlight {
      background-color: red;
      color: #fff;
    }
    $utils(".vue").siblings().addClass("highlight");

    Demo - https://codepen.io/sachinchoolur/pen/oNWNdxE

    You can see that the addClass method is depended on the each method, so if you generate addClass method using replace-jquery --methods "addClass" -o utils.js the output file will contain addClass and each methods.

    Similarly, all the examples you see below, will add it's dependencies when you generate it using replace-jquery

    List of jQuery alternative methods in alphabetical order

    addClass
    addClass(classNames = '') {
      this.each((el) => {
        classNames.split(' ').forEach((className) => {
          el.classList.add(className);
        });
      });
      return this;
    }
    // Usage
    $utils('ul li').addClass('myClass yourClass');
    append
    append(html) {
      this.each((el) => {
        if (typeof html === 'string') {
          el.insertAdjacentHTML('beforeend', html);
        } else {
          el.appendChild(html);
        }
      });
      return this;
    }
    attr
    attr(name, value) {
      if (value === undefined) {
        if (!this.element) {
          return '';
        }
        return this.element.getAttribute(name);
      }
      this.each((el) => {
        el.setAttribute(name, value);
      });
      return this;
    }
    children
    children() {
      return new Utils(this.element.children);
    }
    closest
    closest(selector) {
      if (!this.element) {
        return this;
      }
      const matchesSelector =
        this.element.matches ||
        this.element.webkitMatchesSelector ||
        this.element.mozMatchesSelector ||
        this.element.msMatchesSelector;
    
      while (this.element) {
        if (matchesSelector.call(this.element, selector)) {
          return new Utils(this.element);
        }
        this.element = this.element.parentElement;
      }
      return this;
    }
    css
    css(css, value) {
      if (value !== undefined) {
        this.each((el) => {
          Utils.setCss(el, css, value);
        });
        return this;
      }
      if (typeof css === 'object') {
        for (const property in css) {
          if (Object.prototype.hasOwnProperty.call(css, property)) {
            this.each((el) => {
              Utils.setCss(el, property, css[property]);
            });
          }
        }
        return this;
      }
      const cssProp = Utils.camelCase(css);
      const property = Utils.styleSupport(cssProp);
      return getComputedStyle(this.element)[property];
    }
    data
    data(name, value) {
      return this.attr(`data-${name}`, value);
    }
    each
    each(func) {
        if (!this.elements) {
            return this;
        }
        if (this.elements.length !== undefined) {
            [].slice.call(this.elements).forEach((el, index) => {
                func.call(el, el, index);
            });
        } else {
            func.call(this.element, this.element, 0);
        }
        return this;
    }
    empty
    empty() {
      this.each((el) => {
        el.innerHTML = '';
      });
      return this;
    }
    eq
    eq(index) {
      return new Utils(this.elements[index]);
    }
    find
    find(selector) {
      return new Utils(Utils.getSelector(selector, this.element));
    }
    first
    first() {
      if (this.elements && this.elements.length !== undefined) {
        return new Utils(this.elements[0]);
      }
      return new Utils(this.elements);
    }
    get
    get() {
      return this.elements;
    }
    hasClass
    hasClass(className) {
      if (!this.element) {
        return false;
      }
      return this.element.classList.contains(className);
    }
    height
    height() {
      if (!this.element) {
        return 0;
      }
      const style = window.getComputedStyle(this.element, null);
      return parseFloat(style.height.replace('px', ''));
    }
    html
    html(html) {
      if (html === undefined) {
        if (!this.element) {
          return '';
        }
        return this.element.innerHTML;
      }
      this.each((el) => {
        el.innerHTML = html;
      });
      return this;
    }
    index
    index() {
      if (!this.element) return -1;
      let i = 0;
      do {
        i++;
      } while ((this.element = this.element.previousElementSibling));
      return i;
    }
    is
    is(el) {
      if (typeof el === 'string') {
        return (
          this.element.matches ||
          this.element.matchesSelector ||
          this.element.msMatchesSelector ||
          this.element.mozMatchesSelector ||
          this.element.webkitMatchesSelector ||
          this.element.oMatchesSelector
        ).call(this.element, el);
      }
      return this.element === (el.element || el);
    }
    next
    next() {
      if (!this.element) {
        return this;
      }
      return new Utils(this.element.nextElementSibling);
    }
    nextAll
    nextAll(filter) {
      if (!this.element) {
        return this;
      }
      const sibs = [];
      let nextElem = this.element.parentNode.firstChild;
      do {
        if (nextElem.nodeType === 3) continue; // ignore text nodes
        if (nextElem === this.element) continue; // ignore this.element of target
        if (nextElem === this.element.nextElementSibling) {
          if (!filter || filter(this.element)) {
            sibs.push(nextElem);
            this.element = nextElem;
          }
        }
      } while ((nextElem = nextElem.nextSibling));
      return new Utils(sibs);
    }
    off
    off(event) {
      if (!this.elements) {
        return this;
      }
      Object.keys(Utils.eventListeners).forEach((eventName) => {
        if (Utils.isEventMatched(event, eventName)) {
          Utils.eventListeners[eventName].forEach((listener) => {
            this.each((el) => {
              el.removeEventListener(
                eventName.split('.')[0],
                listener
              );
            });
          });
        }
      });
    
      return this;
    }
    offset
    offset() {
      if (!this.element) {
        return {
          left: 0,
          top: 0,
        };
      }
      const box = this.element.getBoundingClientRect();
      return {
        top:
          box.top +
          window.pageYOffset -
          document.documentElement.clientTop,
        left:
          box.left +
          window.pageXOffset -
          document.documentElement.clientLeft,
      };
    }
    offsetParent
    offsetParent() {
      if (!this.element) {
        return this;
      }
      return new Utils(this.element.offsetParent);
    }
    on
    on(events, listener) {
      if (!this.elements) {
        return this;
      }
      events.split(' ').forEach((event) => {
        if (!Array.isArray(Utils.eventListeners[event])) {
          Utils.eventListeners[event] = [];
        }
        Utils.eventListeners[event].push(listener);
        this.each((el) => {
          el.addEventListener(event.split('.')[0], listener);
        });
      });
    
      return this;
    }
    one
    one(event, listener) {
      this.each((el) => {
        new Utils(el).on(event, () => {
          new Utils(el).off(event);
          listener(event);
        });
      });
      return this;
    }
    outerHeight
    outerHeight(margin) {
      if (!this.element) {
        return 0;
      }
      if (margin !== undefined) {
        let height = this.element.offsetHeight;
        const style = getComputedStyle(this.element);
    
        height +=
          parseInt(style.marginTop, 10) +
          parseInt(style.marginBottom, 10);
        return height;
      }
      return this.element.offsetHeight;
    }
    outerWidth
    outerWidth(margin) {
      if (!this.element) {
        return 0;
      }
      if (margin !== undefined) {
        let width = this.element.offsetWidth;
        const style = window.getComputedStyle(this.element);
    
        width +=
          parseInt(style.marginLeft, 10) +
          parseInt(style.marginRight, 10);
        return width;
      }
      return this.element.offsetWidth;
    }
    parent
    parent() {
      return new Utils(this.element.parentElement);
    }
    parentsUntil
    parentsUntil(selector, filter) {
      if (!this.element) {
        return this;
      }
      const result = [];
      const matchesSelector =
        this.element.matches ||
        this.element.webkitMatchesSelector ||
        this.element.mozMatchesSelector ||
        this.element.msMatchesSelector;
    
      // match start from parent
      let el = this.element.parentElement;
      while (el && !matchesSelector.call(el, selector)) {
        if (!filter) {
          result.push(el);
        } else if (matchesSelector.call(el, filter)) {
          result.push(el);
        }
        el = el.parentElement;
      }
      return new Utils(result);
    }
    position
    position() {
      return {
        left: this.element.offsetLeft,
        top: this.element.offsetTop,
      };
    }
    prepend
    prepend(html) {
      this.each((el) => {
        if (typeof html === 'string') {
          el.insertAdjacentHTML('afterbegin', html);
        } else {
          el.insertBefore(html, el.firstChild);
        }
      });
      return this;
    }
    prev
    prev() {
      if (!this.element) {
        return this;
      }
      return new Utils(this.element.previousElementSibling);
    }
    prevAll
    prevAll(filter) {
      if (!this.element) {
        return this;
      }
      const sibs = [];
      while ((this.element = this.element.previousSibling)) {
        if (this.element.nodeType === 3) {
          continue; // ignore text nodes
        }
        if (!filter || filter(this.element)) sibs.push(this.element);
      }
      return new Utils(sibs);
    }
    remove
    remove() {
      this.each((el) => {
        el.parentNode.removeChild(el);
      });
      return this;
    }
    removeAttr
    removeAttr(attributes) {
      const attrs = attributes.split(' ');
      this.each((el) => {
        attrs.forEach((attr) => el.removeAttribute(attr));
      });
      return this;
    }
    removeClass
    removeClass(classNames) {
      this.each((el) => {
        // IE doesn't support multiple arguments
        classNames.split(' ').forEach((className) => {
          el.classList.remove(className);
        });
      });
      return this;
    }
    siblings
    siblings() {
      if (!this.element) {
        return this;
      }
      const elements = Array.prototype.filter.call(
        this.element.parentNode.children,
        (child) => child !== this.element
      );
      return new Utils(elements);
    }
    text
    text(text) {
      if (text === undefined) {
        if (!this.element) {
          return '';
        }
        return this.element.textContent;
      }
      this.each((el) => {
        el.textContent = text;
      });
      return this;
    }
    toggleClass
    toggleClass(className) {
      if (!this.element) {
        return this;
      }
      this.element.classList.toggle(className);
    }
    trigger
    trigger(event, detail) {
      if (!this.element) {
        return this;
      }
      const eventName = event.split('.')[0];
      const isNativeEvent =
        typeof document.body[`on${eventName}`] !== 'undefined';
      if (isNativeEvent) {
        this.each((el) => {
          el.dispatchEvent(new Event(eventName));
        });
        return this;
      }
      const customEvent = new CustomEvent(eventName, {
        detail: detail || null,
      });
      this.each((el) => {
        el.dispatchEvent(customEvent);
      });
      return this;
    }
    unwrap
    unwrap() {
      this.each((el) => {
        const elParentNode = el.parentNode;
    
        if (elParentNode !== document.body) {
          elParentNode.parentNode.insertBefore(el, elParentNode);
          elParentNode.parentNode.removeChild(elParentNode);
        }
      });
      return this;
    }
    val
    val(value) {
      if (!this.element) {
        return '';
      }
      if (value === undefined) {
        return this.element.value;
      }
      this.element.value = value;
    }
    width
    width() {
      if (!this.element) {
        return 0;
      }
      const style = window.getComputedStyle(this.element, null);
      return parseFloat(style.width.replace('px', ''));
    }
    wrap
    wrap(className) {
      this.each((el) => {
        const wrapper = document.createElement('div');
        wrapper.className = className;
        el.parentNode.insertBefore(wrapper, el);
        wrapper.appendChild(el);
      });
      return this;
    }

    Browser support - IE 11+

    GitHub

    https://github.com/sachinchoolur/replace-jquery
    Comments
    • 1. Fix entry name from npm registry for installation

      I attempted to install the tool using the instructions in the README. When executing "npm install -g replace-jQuery", the response report '[email protected]*' is not in the npm registry.. However, "'replace-jquery" and it appears to be authored by @sachinchoolur, but perhaps npm dropped the capitalization.

      Regardless, this PR changes the README to allow for installation based on the name in npm.

      Reviewed by machawk1 at 2021-09-07 14:52
    • 2. add typescript support

      add typescript support

      Can you use the babel?

      "@babel/plugin-syntax-typescript"

      pnpx replace-jquery "src/*.ts" out.js
      Finding jQuery function reference in src/addfetch.ts ...
      Finding jQuery function reference in src/cachepromise.ts ...
      (node:4227) UnhandledPromiseRejectionWarning: SyntaxError: Unexpected token <
          at Espree.raise (/data/data/com.termux/files/usr/pnpm-global/5/node_modules/.pnpm/[email protected]/node_modules/espree/lib/espree.js:190:25)
          at Espree.unexpected (/data/data/com.termux/files/usr/pnpm-global/5/node_modules/.pnpm/[email protected]/node_modules/espree/lib/espree.js:235:18)
          at Espree.pp.expect (/data/data/com.termux/files/usr/pnpm-global/5/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:692:28)
          at Espree.pp$1.parseFunctionParams (/data/data/com.termux/files/usr/pnpm-global/5/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:1303:10)
          at Espree.pp$1.parseFunction (/data/data/com.termux/files/usr/pnpm-global/5/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:1293:10)
          at Espree.pp$1.parseFunctionStatement (/data/data/com.termux/files/usr/pnpm-global/5/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:992:17)
          at Espree.pp$1.parseStatement (/data/data/com.termux/files/usr/pnpm-global/5/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:839:19)
          at Espree.pp$1.parseExport (/data/data/com.termux/files/usr/pnpm-global/5/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:1458:31)
          at Espree.pp$1.parseStatement (/data/data/com.termux/files/usr/pnpm-global/5/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:872:74)
          at Espree.pp$1.parseTopLevel (/data/data/com.termux/files/usr/pnpm-global/5/node_modules/.pnpm/[email protected]/node_modules/acorn/dist/acorn.js:755:23)
      (Use `node --trace-warnings ...` to show where the warning was created)
      (node:4227) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
      (node:4227) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
      
      Reviewed by masx200 at 2021-09-24 14:06
    • 3. replace-jQuery: command not found

      I'm following this quick start guide: https://github.com/sachinchoolur/replace-jquery#installation-and-usage but it fails on replace-jQuery src/sample.js out.js with error: replace-jQuery: command not found but it works when I replace Q with q:

      Screenshot from 2021-09-17 18-31-45

      Is that case-sensitivity issue related to my system or is your tutorial broken?

      Reviewed by krystiangorecki at 2021-09-17 16:36
    • 4. Drag ID-ed objects via jquery

      Could you mention whether dragging objects via jquery is handled by replace-jquery as well? I use that feature of jquery primarily on many images. I like being able to shift the image a bit. I know there is more-than-one-way to do it but I settled for jquery, so knowing that it is easily possible in vanilla js and replace-jquery suggesting the alternative would be useful IMO. Perhaps the main README could mention that.

      It's jquery-ui though so not sure this applies to the project as well (not sure whether jquery-ui is handled by replace-jquery or not)

      See here how that code looks for jquery: https://www.javatpoint.com/jquery-ui-draggable

      Reviewed by rubyFeedback at 2021-09-11 08:57
    • 5. Just saying thanks!

      I'm making this issue just to say thanks! I'm really looking forward to using this. As I often have to go to my old projects which are jQuery based and I want to replace it with vanilla just for the sake of modernization and optimization.

      I see that not all the methods are replaceable, but helps a lot.

      Again thanks, if I find a way to replace some other methods, I'll try to make a PR.

      Reviewed by eboye at 2021-09-10 23:27
    Reactivated.app is an open-source app that scans your JS dependencies every 4 hours and generates cool dashboards
    Reactivated.app is an open-source app that scans your JS dependencies every 4 hours and generates cool dashboards

    Reactivated.app is an open-source app that scans your JS dependencies every 4 hours and generates cool dashboards

    Jul 20, 2022
    Extension for vscode, which generates the value of a variable from a type definition
    Extension for vscode, which generates the value of a variable from a type definition

    Extension for vscode, which generates the value of a variable from a type definition

    Feb 25, 2022
    Automatically AJAXify plain HTML with the power of React. It's magic!

    React-Magic and HTMLtoJSX React-Magic is an experimental library that uses the power of Facebook's React library to inject AJAX-loading goodness into

    Aug 5, 2022
    An interactive CLI automation tool 🛠️ for creating react.js and next.js projects most fast and efficiently. ⚛️
    An interactive CLI automation tool 🛠️ for creating react.js and next.js projects most fast and efficiently. ⚛️

    An interactive CLI automation tool ??️ for creating react.js and next.js projects most fast and efficiently. ⚛️

    Apr 12, 2022
    This project is collection of large projects's source code (codebases), built with Reactjs. Eg: Bestbuy, Postman, Trello, Udacity, Coursera, Skillshare, Invision, Intercom, Pipedrive, ... and more.
    This project is collection of large projects's source code (codebases), built with Reactjs. Eg: Bestbuy, Postman, Trello, Udacity, Coursera, Skillshare, Invision, Intercom, Pipedrive, ... and more.

    This project is collection of large projects's source code (codebases), built with Reactjs. Eg: Bestbuy, Postman, Trello, Udacity, Coursera, Skillshare, Invision, Intercom, Pipedrive, ... and more.

    Aug 5, 2022
    This repository contains different React components, hooks, apps and libraries that are used in different projects here at NaN Labs.

    This repository contains different React components, hooks, apps and libraries that are used in different projects here at NaN Labs.

    Aug 9, 2022
    A library that makes using pdfjs in react projects easy.

    A library that makes using pdfjs in react projects easy.

    Feb 5, 2022
    This is my Portfolio built with React. The app shows information about me, my projects, resume.
    This is my Portfolio built with React. The app shows information about me, my projects, resume.

    Elyor Doniyorov Built With React.js Live demo Netlify Live link Getting Started To get a local copy run the following steps: Copy this link https://gi

    Jan 21, 2022
    ✉️ Display e-mails in your React.js projects. (Targets Gmail rendering.)
    ✉️ Display e-mails in your React.js projects. (Targets Gmail rendering.)

    react-letter is a React.js component that allows for an easy display of HTML e-mail content with automatic sanitization. Support for features should m

    Jul 27, 2022
    ReactJs Advanced Projects

    ReactJs-Advanced-Projects Tech We Used ReactJs Firebase Hosting Firebase Auth Firebase Storage React-Dom React Redux Steps to run in your machine Run

    Jun 8, 2022
    Responsive and customizable search and select for Giphy's GIFs and Stickers.
    Responsive and customizable search and select for Giphy's GIFs and Stickers.

    Responsive and customizable search and select for Giphy's GIFs. https://sergiop.github.io/react-giphy-searchbox/ React Giphy Searchbox is a powerful r

    Jun 20, 2022
    A React JS project to make age and gender prediction using Agify.io and Genderize.io. Only for practice use and just for fun~

    Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

    May 29, 2022
    HTML meta tags for React-based apps. Works for both client- and server-side rendering, and has a strict but flexible API.

    React Document Meta HTML meta tags for React-based apps. Works for both client- and server-side rendering, and has a strict but flexible API. Built wi

    Jun 14, 2022
    A starter for React with Typescript with the fast Vite and all static code testing with Eslint and formatting with Prettier.
    A starter for React with Typescript with the fast Vite and all static code testing with Eslint and formatting with Prettier.

    A starter for React with Typescript with the fast Vite and all static code testing with Eslint and formatting with Prettier.

    Aug 4, 2022
    Open source authentication and authorization service, container-native, PassportJS-native, built with React and Node.

    id6 Authentication and authorization as a service Docs - Twitter Why id6 ? I wrote id6 out of frustration of re-writing authentication and authorizati

    Oct 31, 2021
    React project with Google Firebase API, create partys with admin view and user view. Users can ask topics to admin, and support other topics.

    ?? Tecnologias Esse projeto foi desenvolvido com as seguintes tecnologias: React Firebase TypeScript ?? Como executar Clone o projeto e acesse a pasta

    Aug 27, 2021
    Compares Discord libraries and their support of new API features. Made with React, Next.js, and Bulma.

    Compares Discord libraries and their support of new API features. Made with React, Next.js, and Bulma.

    Jul 22, 2022
    ⚛️ Deliver UI for Web and Mobile platforms without taking care about complexity on how to style there, learn React once and code everywhere
    ⚛️  Deliver UI for Web and Mobile platforms without taking care about complexity on how to style there, learn React once and code everywhere

    SkynexUI Use the platform, don't care about how to style there ⚠️ Version 1.x.x is alpha, trust only in v2 A set of components writen on top of React

    Jul 31, 2022
    In this assignment you will start with a basic calculator and style it using CSS, inline styles, and styled-components.
    In this assignment you will start with a basic calculator and style it using CSS, inline styles, and styled-components.

    In this assignment you will start with a basic calculator and style it using CSS, inline styles, and styled-components.

    Dec 27, 2021