Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The results of code compiled with vite and code compiled directly with esbuild are inconsistent,Decorators related issues #18105

Closed
7 tasks done
alamhubb opened this issue Sep 13, 2024 · 9 comments

Comments

@alamhubb
Copy link

Describe the bug

The execution results of vite and esbuild are consistent

Reproduction

https://stackblitz.com/edit/vitejs-vite-zmqkuj?file=vite.config.ts,index.html,package.json&terminal=dev

Steps to reproduce

You can get the compiled main.ts from the developer tools at https://stackblitz.com/edit/vitejs-vite-zmqkuj?file=vite.config.ts,index.html,package.json&terminal=dev

var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
var __typeError = (msg) => {
    throw TypeError(msg);
};
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
    enumerable: true,
    configurable: true,
    writable: true,
    value
}) : obj[key] = value;
var __name = (target, value) => __defProp(target, "name", {value, configurable: true});
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
var __decoratorContext = (kind, name, done, metadata, fns) => ({
    kind: __decoratorStrings[kind],
    name,
    metadata,
    addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null))
});
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
var __decorateElement = (array, flags, name, decorators, target, extra) => {
    var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
    var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
    var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
    var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : {
        get [name]() {
            return __privateGet(this, extra);
        }, set [name](x) {
            return __privateSet(this, extra, x);
        }
    }, name));
    k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
    for (var i = decorators.length - 1; i >= 0; i--) {
        ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
        if (k) {
            ctx.static = s, ctx.private = p, access = ctx.access = {has: p ? (x) => __privateIn(target, x) : (x) => name in x};
            if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
            if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
        }
        it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : {
            get: desc.get,
            set: desc.set
        } : target, ctx), done._ = 1;
        if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
        else if (typeof it !== "object" || it === null) __typeError("Object expected");
        else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
    }
    return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
};
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var _testA_dec, _init;

function Resource(target, {kind, name}) {
    return function (initialValue) {
        console.log(222222);
        return 666;
    };
}

_testA_dec = [Resource];

class TestSerivce {
    test() {
        console.log(11111);
        console.log(this.testA);
    }
}

_init = __decoratorStart(null);
__decorateElement(_init, 5, "testA", _testA_dec, TestSerivce);
__decoratorMetadata(_init, TestSerivce);
const newc = new TestSerivce();
newc.test();

Use esbuild (0.21.5) to execute locally

npx esbuild main.ts --target=es2020 --outfile=out.js; node out.js

The resulting file is

var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
var __typeError = (msg) => {
    throw TypeError(msg);
};
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
    enumerable: true,
    configurable: true,
    writable: true,
    value
}) : obj[key] = value;
var __name = (target, value) => __defProp(target, "name", {value, configurable: true});
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
var __decoratorContext = (kind, name, done, metadata, fns) => ({
    kind: __decoratorStrings[kind],
    name,
    metadata,
    addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null))
});
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
var __runInitializers = (array, flags, self, value) => {
    for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
    return value;
};
var __decorateElement = (array, flags, name, decorators, target, extra) => {
    var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
    var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
    var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
    var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : {
        get [name]() {
            return __privateGet(this, extra);
        }, set [name](x) {
            return __privateSet(this, extra, x);
        }
    }, name));
    k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
    for (var i = decorators.length - 1; i >= 0; i--) {
        ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
        if (k) {
            ctx.static = s, ctx.private = p, access = ctx.access = {has: p ? (x) => __privateIn(target, x) : (x) => name in x};
            if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
            if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
        }
        it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : {
            get: desc.get,
            set: desc.set
        } : target, ctx), done._ = 1;
        if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
        else if (typeof it !== "object" || it === null) __typeError("Object expected");
        else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
    }
    return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
};
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var _testA_dec, _init;

function Resource(target, {kind, name}) {
    return function (initialValue) {
        console.log(222222);
        return 666;
    };
}

_testA_dec = [Resource];

class TestSerivce {
    constructor() {
        __publicField(this, "testA", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
    }

    test() {
        console.log(11111);
        console.log(this.testA);
    }
}

_init = __decoratorStart(null);
__decorateElement(_init, 5, "testA", _testA_dec, TestSerivce);
__decoratorMetadata(_init, TestSerivce);
const newc = new TestSerivce();
newc.test();

The result of code execution is

image

The correct result was 666

The result obtained in

https://stackblitz.com/edit/vitejs-vite-zmqkuj?file=vite.config.ts,index.html,package.json&terminal=dev

is

image

System Info

System:
    OS: Windows 10 10.0.19045
    CPU: (16) x64 AMD Ryzen 7 5800X 8-Core Processor
    Memory: 7.97 GB / 31.89 GB
  Binaries:
    Node: 20.12.2 - D:\Program Files\nodejs\node.EXE
    npm: 10.5.0 - D:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Chromium (127.0.2651.98)
    Internet Explorer: 11.0.19041.4355

Used Package Manager

npm

Logs

No response

Validations

Copy link

stackblitz bot commented Sep 13, 2024

Fix this issue in StackBlitz Codeflow Start a new pull request in StackBlitz Codeflow.

@alamhubb
Copy link
Author

alamhubb commented Sep 13, 2024

Related issues

evanw/esbuild#3911 (comment)

#18101 (comment)

This problem is also caused by the decorator,Decorators related issues

#17308

@alamhubb alamhubb changed the title The results of code compiled with vite and code compiled directly with esbuild are inconsistent The results of code compiled with vite and code compiled directly with esbuild are inconsistent,Decorators related issues Sep 13, 2024
@alamhubb
Copy link
Author

evanw/esbuild#3911 (comment)

https://esbuild.github.io/content-types/#tsconfig-json

If I use a decorator

This rule is too hidden, and it does not use the configuration in tsconfig.json, so additional configuration is required.

esbuild: {
        sourcemap: false,
        target: 'es2020',
        tsconfigRaw: {
            compilerOptions: {
                useDefineForClassFields: true,
                target: 'es2020'
            }
        }
    }

This problem makes me sad. Can this rule be made better?

For example, tsconfigRaw will replace compilerOptions with the configuration in tsconfig.json by default

@hi-ogawa
Copy link
Collaborator

For example, tsconfigRaw will replace compilerOptions with the configuration in tsconfig.json by default

Vite should automatically read user's tsconfig.json for esbuild's tsconfigRaw and it looks working for me here https://stackblitz.com/edit/vitejs-vite-tqphgw?file=tsconfig.json

One caveat is that, when you don't have useDefineForClassFields explicitly, Vite will use useDefineForClassFields: false #13708 but this is to align with tsc instead of esbuild as a default behavior.

@alamhubb
Copy link
Author

alamhubb commented Sep 15, 2024

For example, tsconfigRaw will replace compilerOptions with the configuration in tsconfig.json by default

Vite should automatically read user's tsconfig.json for esbuild's tsconfigRaw and it looks working for me here https://stackblitz.com/edit/vitejs-vite-tqphgw?file=tsconfig.json

One caveat is that, when you don't have useDefineForClassFields explicitly, Vite will use useDefineForClassFields: false #13708 but this is to align with tsc instead of esbuild as a default behavior.

Feel your reply

That's because esbuild in your vite.config.ts contains target:es2020. If you delete target:es2020, the execution error occurs because this code in your vite sets useDefineForClassFields to false when both target and useDefineForClassFields do not exist. It does not read the configuration of tsconfig.json.

image

image

@hi-ogawa
Copy link
Collaborator

If you delete target:es2020, the execution error occurs

@alamhubb Right, I was merely suggesting that you can move esbuild.tsconfigRaw into tsconfig.json, but not esbuild.target. If this part is what's confusing, then this issue would be a duplicate of #13756 as you've commented there.


I experimented with tsc and esbuild on my own https://github.com/hi-ogawa/reproductions/tree/main/vite-18105-decorator-useDefineForClassFields and this might be still considered as esbuild's issue.
It looks like tsc supports decorators on uninitialized class fields (namely @Resource testA in your example) even when useDefineForClassFields: false, but that doesn't seem to be the case on esbuild.

@alamhubb
Copy link
Author

alamhubb commented Sep 16, 2024

If you delete target:es2020, the execution error occurs

@alamhubb Right, I was merely suggesting that you can move esbuild.tsconfigRaw into tsconfig.json, but not esbuild.target.

I understand why deleting es2020 will cause an execution error.

Because deleting es2020 will cause esbuild's default{target:esnext}.

Esnext is based on the tc39 decorator specification and does not support automatic conversion of decorators, while es2020 does.

Therefore, even if useDefineForClassFields: true is set, there will be a runtime error. However, this has nothing to do with useDefineForClassFields, but is caused by the decorator.

@alamhubb
Copy link
Author

I experimented with tsc and esbuild on my own https://github.com/hi-ogawa/reproductions/tree/main/vite-18105-decorator-useDefineForClassFields and this might be still considered as esbuild's issue. It looks like tsc supports decorators on uninitialized class fields (namely @Resource testA in your example) even when useDefineForClassFields: false, but that doesn't seem to be the case on esbuild.

I think tsc is wrong, and esbuild is right.

According to my understanding, when useDefineForClassFields: false, decNoInit should not be a property in DecClass,

It should only be a property of DecClass when useDefineForClassFields: true.

esbuild does this, while tsc ignores useDefineForClassFields: false

I looked up the relevant documentation of ts and did not find any explanation for this part

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#the-usedefineforclassfields-flag-and-the-declare-property-modifier

@alamhubb
Copy link
Author

alamhubb commented Sep 16, 2024

For example, tsconfigRaw will replace compilerOptions with the configuration in tsconfig.json by default

Vite should automatically read user's tsconfig.json for esbuild's tsconfigRaw and it looks working for me here https://stackblitz.com/edit/vitejs-vite-tqphgw?file=tsconfig.json

One caveat is that, when you don't have useDefineForClassFields explicitly, Vite will use useDefineForClassFields: false #13708 but this is to align with tsc instead of esbuild as a default behavior.

Thank you very much. I understand the problem. Vite can read the configuration in the tsconfig.json file correctly. The problem is vite:vue vitejs/vite-plugin-vue#444 , not vite. There is a problem with my previous test method. I did not correctly distinguish vite from vite:vue. Thank you for your answer.

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

No branches or pull requests

2 participants