Finally, someone started jQuery and removed the project's dependence on it with one click

Posted by prometheuzz on Mon, 03 Jan 2022 13:12:32 +0100

Hello, I'm zero or one . Although many front-end projects are now using Vue and React, there are also many projects that rely on jquery, especially those with a long history. Do you want to remove jquery from your project? After all, the library is so large that only 15% ~ 30% of the code you can use, and jquery has done a lot of processing on the compatibility of various browsers (code up), but in fact, many old projects will not consider browsers that are very compatible with edge browsers, so in fact, the compatible code in jquery is not necessary

Recently, an interesting tool has been found. There are 600 + stars in only two weeks of online. It says it can help your project get rid of the dependence on jquery. It feels like a good idea. Let's have a look~

Mode of use

The name of this tool is replace-jquery It is said that it can help you automatically find all the jquery methods used in the project and generate a set of native js methods to replace them

Let's start with a minimalist jquery project

index.html

main.js

Test the function of the page. It's OK

Next, we use the replace jquery tool to try to remove main jquery code in JS

Download it globally first

npm install -g replace-jquery

Then, in the project directory, use the syntax to generate the js file for the replace jQuery target js file

replace-jquery main.js newMain.js

The tool will automatically find all jquery methods used in your file. There is a confirmation step here. You can choose which methods you want to replace (select all by default)

Press enter to complete the replacement and generate a new file

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

    on(events, listener) {
        events.split(' ').forEach((eventName) => {
            this.each((el) => {
                const tNEventName = Utils.setEventName(el, eventName);
                if (!Array.isArray(Utils.eventListeners[tNEventName])) {
                    Utils.eventListeners[tNEventName] = [];
                }
                Utils.eventListeners[tNEventName].push(listener);

                // https://github.com/microsoft/TypeScript/issues/28357
                if (el) {
                    el.addEventListener(eventName.split('.')[0], listener);
                }
            });
        });

        return this;
    }
    remove() {
        this.each((el) => {
            el.parentNode.removeChild(el);
        });
        return this;
    }
    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];
    }
    static getSelector(selector, context) {
        if (selector && typeof selector !== 'string') {
            if (selector.length !== undefined) {
                return selector;
            }
            return [selector];
        }
        context = context || document;

        // For performance reasons, use getElementById
        // eslint-disable-next-line no-control-regex
        const idRegex = /^#(?:[\w-]|\\.|[^\x00-\xa0])*$/;
        if (idRegex.test(selector)) {
            const el = document.getElementById(selector.substring(1));
            return el ? [el] : [];
        }
        return [].slice.call(context.querySelectorAll(selector) || []);
    }
    get(index) {
        if (index !== undefined) {
            return this.elements[index];
        }
        return this.elements;
    }
    each(func) {
        if (!this.elements.length) {
            return this;
        }
        this.elements.forEach((el, index) => {
            func.call(el, el, index);
        });
        return this;
    }
    static setEventName(el, eventName) {
        // Need to verify https://stackoverflow.com/questions/1915341/whats-wrong-with-adding-properties-to-dom-element-objects
        const elementUUId = el.eventEmitterUUID;
        const uuid = elementUUId || Utils.generateUUID();
        // eslint-disable-next-line no-param-reassign
        el.eventEmitterUUID = uuid;
        return Utils.getEventName(eventName, uuid);
    }
    static setCss(el, prop, value) {
        // prettier-ignore
        let cssProperty = Utils.camelCase(prop);
        cssProperty = Utils.styleSupport(cssProperty);
        el.style[cssProperty] = value;
    }
    static camelCase(text) {
        return text.replace(/-([a-z])/gi, (s, group1) => group1.toUpperCase());
    }
    static styleSupport(prop) {
        let vendorProp;
        let supportedProp;
        const capProp = prop.charAt(0).toUpperCase() + prop.slice(1);
        const prefixes = ['Moz', 'Webkit', 'O', 'ms'];
        let div = document.createElement('div');

        if (prop in div.style) {
            supportedProp = prop;
        } else {
            for (let i = 0; i < prefixes.length; i++) {
                vendorProp = prefixes[i] + capProp;
                if (vendorProp in div.style) {
                    supportedProp = vendorProp;
                    break;
                }
            }
        }

        div = null;
        return supportedProp;
    }
    static generateUUID() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
            // eslint-disable-next-line no-bitwise
            const r = (Math.random() * 16) | 0;
            // eslint-disable-next-line no-bitwise
            const v = c === 'x' ? r : (r & 0x3) | 0x8;
            return v.toString(16);
        });
    }
    static getEventName(eventName, uuid) {
        return `${eventName}__EVENT_EMITTER__${uuid}`;
    }
}

Utils.eventListeners = {};

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

After a brief look, it seems that the jquery method we use is replaced with a simple native method and encapsulated in the Utils class. Then every time we call $("xxx"), we are actually calling the method on this class. Then make some modifications to this file

// Delete export here
class Utils {
	// ... Omit some code
}

Utils.eventListeners = {};

// Delete export default here and change the function $utils to$
function $(selector) {
    return new Utils(selector);
}

This is equivalent to defining a $method in global simulation jquery. At this point, the jquery reference in the html file can be deleted and the file we just generated can be imported

Try to operate dom on the page again. You can see that the effect is the same as before. Success!

supplement

If you want to use this tool to generate alternative files for all jquery APIs, that is, to generate a super mini jquery, you can do so

replace-jquery --build-all super-mini-jquery.js

After obfuscating and vilifying the code, it is only about 10kb

Because this tool has only been released for less than two weeks, it only implements most jquery code replacement. For example, ajax can't be replaced temporarily. If you try to replace it, the tool will remind you

Generally speaking, the idea of this tool is good. I hope it can support more syntax replacement in the later stage!

I'm zero one, sharing technology, not just the front end. Follow me and learn more about the new front end pose~