Skip to content

Cannot set property of #<Object> which has only a getter #587

Description

@Kivol

Let me explain with an example.

When bundling axios to esModule output with esbuild, the following error occurs when imported:

      wrappedProtocol.request = function(input, options, callback) {
                              ^
TypeError: Cannot set property request of #<Object> which has only a getter

Looks like this comes from the follow-redirects package(which is required by axios) that has this code:

var wrappedProtocol = exports[scheme] = Object.create(nativeProtocol);
wrappedProtocol.request = function(input, options, callback) {
...

where nativeProtocol is originally require('http').

On the process of bundling, the require('http') seems to be converted with esbuild's __exportStar function to have only getter, which the code is produced with this line:

__defProp(target, key, { get: () => module[key], enumerable: !(desc = __getOwnPropDesc(module, key)) || desc.enumerable })

I'm not sure this should be addressed in esbuild, but axios is really common library on JS ecosystem and I think there might be helpful if there's any workaround.

You can reproduce this bug with: https://github.com/Kivol/esbuild-export-star-bug-reproduce

Activity

  1. evanw commented on Dec 10, 2020

    @evanw
    Owner

    Thank you for filing this issue. This is the first time I've heard about this kind of problem happening.

    The specific reason this happens is because assigning a property to an object where the property already exists on the prototype chain will succeed if it's a read-only property but will fail if it's a getter-only property. The namespace object for the built-in http module has read-only properties but the namespace object that esbuild generates has getter-only properties.

    Using import * as ns from 'path' means ns is an "exotic namespace object" according to the specification. It behaves in ways that are unusual for JavaScript objects. Each export is exposed as a writable property even though you can't write to it. And it's value is automatically updated whenever the corresponding exported variable is mutated even though there are no setters involved. This means there is no setter for esbuild to hook into. Pretty much the only way esbuild has of "forwarding" accesses to the underlying export is by using a getter. Namespace objects could also have used getters instead, but they don't for reasons outlined here: tc39/ecma262#749. I suppose esbuild could technically also use a Proxy object to implement forwarding but I assume doing that would have too high of a performance cost.

    One easy way to fix this in your specific case is to export the namespace object itself using a CommonJS re-export:

     build.onLoad({ filter: /.*/, namespace: 'external' }, args => ({
    -  contents: `export * from ${JSON.stringify(args.path)}`
    +  contents: `import * as ns from ${JSON.stringify(args.path)}; module.exports = ns`
     }));

    That will work for code that does require('http'). Although it won't work for code that does import * as http from 'http', so that's not a general approach. Does this approach work for you?

    Another way to fix this would be for axios to use Object.defineProperty() instead of a normal assignment to assign to the request property. Doing that doesn't run setters so that wouldn't have this problem. But that would of course require modifying the axios library.

  2. Kivol commented on Dec 10, 2020

    @Kivol
    Author

    Thanks for the quick workaround.

    Unfortunately, this approach does not work for me, because in my case, the code is packed into a (bundled) library, and the library is bundled with another code again. In that scenario, the module.exports is packed back to a module with __toModule wrapper.

    Then, I'm getting this error message: TypeError: Cannot set property request of [object Object] which has only a getter

    You can reproduce with: https://github.com/Kivol/esbuild-export-star-bug-reproduce/tree/main2

    node build.js
    node build2.js
    node test.mjs
    
  3. Kivol commented on Dec 14, 2020

    @Kivol
    Author

    I've tested with the latest version of follow-redirects and it works like a charm. I'm not sure if there will be any similar issue for the other popular dependencies, but I'd rather choose the similar approach (or hard-code at the custom plugin level) for the further issue like this.

    Thank you very much for the help!

  4. MetatronL commented on Apr 15, 2021

    @MetatronL

    An plugin with configurable import list.
    Perhaps this might help some people looking for a solution.

    /* eslint-disable camelcase */
    const plugin = (arrEntries = []) => {
    	const strWords = arrEntries.join("|");
    	const _expression = `(.*?)(${strWords})(.*?)`;
    	const _regex = new RegExp(_expression);
    
    	return {
    		name: "default_getter_fix",
    		setup: (build) => {
    			build.onLoad({ filter: _regex }, (args) => {
    				return {
    					contents: `import * as ns from ${JSON.stringify(args.path)}; console.log(${JSON.stringify(args.path)}); module.exports = ns`,
    					loader: "js",
    				};
    			});
    		},
    	};
    };
    
    module.exports = {
    	plugin,
    };
    
    
    

    You use it like this:

    plugins: [
    	DefaultGetterFix.plugin([
    		"update-input-width",
    	]),
    ],
    
  5. kevinfaveri commented on May 12, 2021

    @kevinfaveri

    An plugin with configurable import list.
    Perhaps this might help some people looking for a solution.

    /* eslint-disable camelcase */
    const plugin = (arrEntries = []) => {
    	const strWords = arrEntries.join("|");
    	const _expression = `(.*?)(${strWords})(.*?)`;
    	const _regex = new RegExp(_expression);
    
    	return {
    		name: "default_getter_fix",
    		setup: (build) => {
    			build.onLoad({ filter: _regex }, (args) => {
    				return {
    					contents: `import * as ns from ${JSON.stringify(args.path)}; console.log(${JSON.stringify(args.path)}); module.exports = ns`,
    					loader: "js",
    				};
    			});
    		},
    	};
    };
    
    module.exports = {
    	plugin,
    };
    

    You use it like this:

    plugins: [
    	DefaultGetterFix.plugin([
    		"update-input-width",
    	]),
    ],
    

    I think this is not working. Basically, the exported object becomes: { default: {empty_object }, esModule: true }. Which means, it break the code. But it seems like we have an opportunity to improve with a plugin support for older libraries...

    I think a good case that has a lot of requires and stuff like that would be getting this library to work: https://github.com/wojtekmaj/react-datetime-picker

  6. christophgysin commented on May 25, 2021

    @christophgysin

    Could this be reopened? The issue has not been fixed in esbuild. While the library that exposed this issue has been patched, there are others that are still affected by this.

  7. dmvvilela commented on Jun 12, 2021

    @dmvvilela

    Same problem here using swell-node lib

  8. teemuniiranen commented on Jul 19, 2021

    @teemuniiranen

    Same problem here when using aws-xray-sdk and trying to wrap http or https with captureHTTPsGlobal() function.

  9. aabounegm commented on Aug 18, 2021

    @aabounegm

    I had the same problem using TypeScript in my Node.js project when importing puppeteer using the normal imports (import puppeteer from 'puppeteer';). However, it worked when I changed that import to:

    import puppeteer = require('puppeteer');

    I am not sure about why it works that way or why it doesn't work with the normal imports, but I just wanted to share something that worked with me.

  10. evanw commented on Aug 18, 2021

    @evanw
    Owner

    I am not sure about why it works that way or why it doesn't work with the normal imports

    This happens because ES module import statements result in an object with immutable properties while CommonJS require calls result in an object with mutable properties (for the reasons already described above).

  11. easy-why-not commented on Nov 9, 2022

    @easy-why-not

    I have the same problem. I can't figure out what the reason is
    TypeError: Cannot set property value of [object Object] which has only a getter
    at Zl.FA (/Users/aleksandrnistratov/Webstorm/wd_api/dist/bundle.js:184:67050)
    at new Zl (/Users/aleksandrnistratov/Webstorm/wd_api/dist/bundle.js:184:70098)
    at new mc (/Users/aleksandrnistratov/Webstorm/wd_api/dist/bundle.js:190:26147)
    at Wct (/Users/aleksandrnistratov/Webstorm/wd_api/dist/bundle.js:190:29347)
    at KC.find (/Users/aleksandrnistratov/Webstorm/wd_api/dist/bundle.js:190:39169)
    at llt (/Users/aleksandrnistratov/Webstorm/wd_api/dist/bundle.js:190:39660)
    at $t.findUser (/Users/aleksandrnistratov/Webstorm/wd_api/dist/bundle.js:190:48407)
    at c (/Users/aleksandrnistratov/Webstorm/wd_api/dist/bundle.js:190:56237)
    at new Promise ()
    at /Users/aleksandrnistratov/Webstorm/wd_api/dist/bundle.js:190:56215

  12. morsmodr commented on May 27, 2023

    @morsmodr

    @evanw Is the guidance to stick to cjs until we are 100% sure that the packages we use all have esm exports?

    Experiencing a similar issue only with production build of my remix app, hence unable to find core issue. Remix dev doesn't throw this error
    Screenshot 2023-05-26 at 6 01 03 PM

  13. HriBB commented on May 27, 2023

    @HriBB

    I believe I have the same issue:

    react-reconciler.development.js:9860 The above error occurred in the <Circle> component:
    
        at Circle
        at Layer
        at View
    
    Consider adding an error boundary to your tree to customize error handling behavior.
    Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
    logCapturedError @ react-reconciler.development.js:9860
    update.callback @ react-reconciler.development.js:9893
    callCallback @ react-reconciler.development.js:5094
    commitUpdateQueue @ react-reconciler.development.js:5115
    commitLayoutEffectOnFiber @ react-reconciler.development.js:15019
    commitLayoutMountEffects_complete @ react-reconciler.development.js:16366
    commitLayoutEffects_begin @ react-reconciler.development.js:16352
    commitLayoutEffects @ react-reconciler.development.js:16290
    commitRootImpl @ react-reconciler.development.js:18966
    commitRoot @ react-reconciler.development.js:18825
    finishConcurrentRender @ react-reconciler.development.js:18000
    performConcurrentWorkOnRoot @ react-reconciler.development.js:17917
    workLoop @ scheduler.development.js:266
    flushWork @ scheduler.development.js:239
    performWorkUntilDeadline @ scheduler.development.js:533
    paper-core.js:441 Uncaught TypeError: Cannot set property project of [object Object] which has only a getter
        at handleKey (paper-core.js:441:14)
        at Array.forEach (<anonymous>)
        at Function.filter (paper-core.js:456:46)
        at Path2.set (paper-core.js:271:9)
        at createPath (paper-core.js:9993:15)
        at createEllipse (paper-core.js:10006:10)
        at new Circle (paper-core.js:10022:11)
        at createInstance (Renderer.js:167:28)
        at completeWork (react-reconciler.development.js:13604:28)
        at completeUnitOfWork (react-reconciler.development.js:18739:16)
    
  14. Mikastark commented on Nov 9, 2023

    @Mikastark

    Same issue with logrocket on Angular 17 using esbuild builder (application)

  15. canutoalex commented on Jan 18, 2024

    @canutoalex

    Same issue with logrocket on Angular 17 using esbuild builder (application)

    That's 2 for LogRocket in Angular (16 in my case)...

  16. verveguy commented on Feb 6, 2024

    @verveguy

    Same problem with latest esbuild trying to use a d3-axes dependency.

    axis.js:3 Uncaught TypeError: Cannot set property top of # which has only a getter
    at node_modules/d3-axis/src/axis.js (axis.js:3:5)
    at __init (Classdiagram.js:9:56)
    at node_modules/d3-axis/src/index.js (index.js:1:1)
    at __init (Classdiagram.js:9:56)
    at node_modules/d3/src/index.js (index.js:2:1)
    at __init (Classdiagram.js:9:56)

    I finally tracked down a solution: I was using format='cjs' in my esbuild.config.

    Changing this to format='iife' has solved this issue for me. Good question as to why I was using cjs in the first place...probably cribbed from an earlier node.js project.

  17. Stanzilla commented on Mar 14, 2024

    @Stanzilla

    If you are finding this issue via Google and are using Vite and Electron, adding 'fs-extra': { type: 'cjs' }, to your renderer config will solve it.

  18. adamwong246 commented on Apr 8, 2024

    @adamwong246

    Nock also exhibits this problem. https://github.com/nock/nock

  19. nikita-fuchs commented on Nov 26, 2024

    @nikita-fuchs
    - ng2-completer [es2015/esm2015] (oferh/ng2-completer)
    Error: Error on worker #1: TypeError: Cannot set property getExpandoInitializer of #<Object> which has only a getter
    

    let's see how many more years we will face this issue...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions