6

I am trying to extend the URL class and add a property to customize it. But its not working.

let { URL } = require('url');

class MyURL extends URL {
    constructor(url, base) {
        super(url, base);
        this.base = base;
    }
}
let ur = new MyURL('abc.html','http://www.example.com/about')
console.log(ur);

it logs

MyURL { href: 'http://www.example.com/abc.html', origin: 'http://www.example.com', protocol: 'http:', username: '', password: '', host: 'www.example.com', hostname: 'www.example.com', port: '', pathname: '/abc.html', search: '', searchParams: URLSearchParams {}, hash: '' }

Notice it doesn't have the base property. Why is it happening?

How can I make it have the property base with the base provided in the constructor by extending URL?

vibhor1997a
  • 2,336
  • 2
  • 17
  • 37

2 Answers2

7

Try console.log(ur.base) and you'll see your base class instance attribute. The reason why you could not see it before is that console.log automatically uses toString() method of URL class. So you have to override toString in a child class as well and include base attribute.

Alex
  • 2,074
  • 1
  • 15
  • 14
  • 1
    The .toString() method of URL just returns the url in a string, not the whole object, so that part doesn't seem correct. I'm starting a bounty since I'm also interested in extending the base URL to add parameters and have them show when you log the whole thing. – Francisco Presencia Jun 09 '22 at 04:00
1

I'll answer the bountied sub-question:

I would like to know how to have the new property displayed on the terminal when doing console.log(url) here. I've tried with defineProperty(), writable: true, enumerable: true, etc. to no avail. Should show the properties in Node.js.

After research, I came across this question. Thanks to this method, you can edit the output on node:

let { URL } = require('url');

class MyURL extends URL {
    constructor(url, base) {
        super(url, base);
        this.base = base;
    }
    [require('util').inspect.custom](){
        return `MyURL {base: ${this.base}}`;
    }
}

let u = new MyURL('abc.html','http://www.example.com/about')
console.log(u);

This gives, as you can imagine, this output: MyURL {base: http://www.example.com/about}. However, if I assume well, that's only half the work done, as we don't have all the other properties printed. I am however unable to have a perfect result.


If you don't care about the performances and just want the flat properties, you can use Object.assign to make a copy of your object without the class:

let { URL } = require('url');

class MyURL extends URL {
    constructor(url, base) {
        super(url, base);
        this.base = base;
    }
    [require('util').inspect.custom](){
        let res = require('util').inspect(Object.assign({}, this));
        return `MyURL ${res}`;
    }
}

let ur = new MyURL('abc.html','http://www.example.com/about')
console.log(ur);

this gives the following:

MyURL {
  base: 'http://www.example.com/about',
  [Symbol(context)]: URLContext {
    flags: 400,
    scheme: 'http:',
    username: '',
    password: '',
    host: 'www.example.com',
    port: null,
    path: [ 'abc.html' ],
    query: null,
    fragment: null
  },
  [Symbol(query)]: URLSearchParams {}
}

You can achieve the same without copying using some dirty trick (I'm not sure if it's a good idea to do this though):

let { URL } = require('url');

class MyURL extends URL {
    constructor(url, base) {
        super(url, base);
        this.base = base;
    }
    [require('util').inspect.custom](){
        this.__proto__ = Object.prototype;
        let res = require('util').inspect(this);
        this.__proto__ = MyURL.prototype;
        return `MyURL ${res}`;
    }
}

let ur = new MyURL('abc.html','http://www.example.com/about')
console.log(ur);

But I must admit, it is a bit obscure to me. In case this is not a good solution for you, another possibility is to use a URL instance for the inspect call:

let { URL } = require('url');

class MyURL extends URL {
    constructor(url, base) {
        super(url, base);
        this.base = base;
    }
    [require('util').inspect.custom](){
        return `MyURL { base: ${this.base}, super: ${require('util').inspect(new URL(this))} }`;
    }
}

let u = new MyURL('abc.html','http://www.example.com/about')
console.log(u);

or the faster and dirtier way:

let { URL } = require('url');

class MyURL extends URL {
    constructor(url, base) {
        super(url, base);
        this.base = base;
    }
    [require('util').inspect.custom](){
        this.__proto__ = URL.prototype;
        let res = require('util').inspect(this);
        this.__proto__ = MyURL.prototype;
        return `MyURL { base: ${this.base}, super: ${res} }`;
    }
}

let ur = new MyURL('abc.html','http://www.example.com/about')
console.log(ur);

Giving the output:

MyURL { base: http://www.example.com/about, super: URL {
  href: 'http://www.example.com/abc.html',
  origin: 'http://www.example.com',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'www.example.com',
  hostname: 'www.example.com',
  port: '',
  pathname: '/abc.html',
  search: '',
  searchParams: URLSearchParams {},
  hash: ''
} }

This can be perfected with some string editing.


The last solution I have is to re-create the whole print, by iterating through all the getters of URL and printing them / adding them to an object to finally print:

let { URL } = require('url');

class MyURL extends URL {
    constructor(url, base) {
        super(url, base);
        this.base = base;
    }
    [require('util').inspect.custom](){
        let res = {};
        for (var key in this) {
            res[key] = this[key];
        }
        return `MyURL ${require('util').inspect(res)}`;
    }
}

let u = new MyURL('abc.html','http://www.example.com/about')
console.log(u);

this gives:

MyURL {
  base: 'http://www.example.com/about',
  toString: [Function: toString],
  href: 'http://www.example.com/abc.html',
  origin: 'http://www.example.com',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'www.example.com',
  hostname: 'www.example.com',
  port: '',
  pathname: '/abc.html',
  search: '',
  searchParams: URLSearchParams {},
  hash: '',
  toJSON: [Function: toJSON]
}

If you don't care about the MyURL prefix and prefer to have the coloration, you can simply return res instead of a string.

Naeio
  • 1,112
  • 7
  • 21
  • 1
    That worked perfectly, thanks! I implemented it [in this temp project](https://github.com/franciscop/server-next/blob/master/src/ServerUrl.js#L43) and so the terminal even shows the colors as expected! That `[inspect.custom](){` was the key thing I was missing, and thanks to all your code snippets and tips I finished it properly – Francisco Presencia Jun 15 '22 at 04:12
  • 1
    Glad it helped! And I won't lie, if there weren't the bounty, I would've just given the `[inspect.custom]` thing and wouldn't have tried to find other solutions ^^; Your implementation seems nice by the way! – Naeio Jun 17 '22 at 22:46