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

[bug] default function in deno bundle is not a function #4055

Closed
alexanderGalushka opened this issue Feb 20, 2020 · 11 comments · Fixed by #4124
Closed

[bug] default function in deno bundle is not a function #4055

alexanderGalushka opened this issue Feb 20, 2020 · 11 comments · Fixed by #4124

Comments

@alexanderGalushka
Copy link

v0.34.0

ERROR:

error: Uncaught Error: Uncaught TypeError: customFn_js_1.default is not a function
$deno$/workers.ts:169:17
at poll ($deno$/workers.ts:169:17)

single function in the .js file to bundle:

export default function cubeIt(req, res) {
  const result = 3*3*3;
  res.send({ date: new Date(), result });
}

works in v0.32.0 (System vs AMD for bundling)

@kitsonk the function identifier is no longer "file:///...", but doubt that change you have introduced yesterday has anything to do with described behavior, probably the offender is hiding here

@kitsonk
Copy link
Contributor

kitsonk commented Feb 20, 2020

I can't re-create this...

Given an input of:

export default function cubeIt(req: any, res: any) {
  const result = 3*3*3;
  res.send({ date: new Date(), result });
}

I get an output of:

// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.

// This is a specialised implementation of a System module loader.

// eslint-disable-next-line @typescript-eslint/no-unused-vars
let System;
let __inst;

(() => {
  const mMap = new Map();
  System = {
    register(id, deps, f) {
      mMap.set(id, {
        id,
        deps,
        f,
        exp: {}
      });
    }
  };

  const gC = (data, main) => {
    const { id } = data;
    return {
      id,
      import: async id => mMap.get(id)?.exp,
      meta: { url: id, main }
    };
  };

  const gE = data => {
    const { exp } = data;
    return (id, value) => {
      const values = typeof id === "string" ? { [id]: value } : id;
      for (const [id, value] of Object.entries(values)) {
        Object.defineProperty(exp, id, {
          value,
          writable: true,
          enumerable: true
        });
      }
    };
  };

  const iQ = [];

  const enq = ids => {
    for (const id of ids) {
      if (!iQ.includes(id)) {
        const { deps } = mMap.get(id);
        iQ.push(id);
        enq(deps);
      }
    }
  };

  const dr = async main => {
    const rQ = [];
    let id;
    while ((id = iQ.pop())) {
      const m = mMap.get(id);
      const { f } = m;
      if (!f) {
        return;
      }
      rQ.push([m.deps, f(gE(m), gC(m, id === main))]);
      m.f = undefined;
    }
    let r;
    while ((r = rQ.shift())) {
      const [deps, { execute, setters }] = r;
      for (let i = 0; i < deps.length; i++) setters[i](mMap.get(deps[i])?.exp);
      const e = execute();
      if (e) await e;
    }
  };

  __inst = async id => {
    System = undefined;
    __inst = undefined;
    enq([id]);
    await dr(id);
    return mMap.get(id)?.exp;
  };
})();

System.register("exp_def", [], function (exports_1, context_1) {
    "use strict";
    var __moduleName = context_1 && context_1.id;
    function cubeIt(req, res) {
        const result = 3 * 3 * 3;
        res.send({ date: new Date(), result });
    }
    exports_1("default", cubeIt);
    return {
        setters: [],
        execute: function () {
        }
    };
});

const __exp = await __inst("exp_def");
export default __exp["default"];

Which when I then create another file that imports it:

import fn from "./test.bundle.js";

console.log(fn);

I get the following:

[Function: cubeIt]

@alexanderGalushka
Copy link
Author

@kitsonk my bad, let me provide step by step guidance to reproduce, missing a second layer bundling

@alexanderGalushka
Copy link
Author

clientCode.js

export default function cubeIt(res) {
  const result = 3*3*3;
  res.send({ date: new Date(), result });
}

worker-fn.ts

import entryPoint from "./customFn.js";

interface Req {
    id: string;
    query: any;
    headers: any;
}

class Resp {
    statusCode: number;
    id: string;
    headers: object;

    constructor(id: string) {
        this.id = id;
        this.headers = {};
    }

    status(status: number) {
        this.statusCode = status;
        return this;
    }

    send(result: string | object | number) {
        const status = this.statusCode || 200;
        postMessage({
            headers: this.headers,
            type: typeof result,
            status,
            id: this.id,
            result
        });
    }
}


if (self.name !== "workerFn") throw Error("Bad name");

onmessage = function(e: { data: { id: any; }; }) {
    const { id } = e.data;
    const resp = new Resp(id);
    console.log("msg received: " + id);
    entryPoint(resp);
}

onerror = function(err: unknown) {
    // postmessage, 500
    console.log('on error', err);
    return false;
}

bundler.ts

// bundle client code
var [diagnostics, emit] = await Deno.bundle('./clientCode.js');
if (diagnostics != null) {
    throw "diagnostics is not null";
}

const encoder = new TextEncoder();

// bundle custom worker
Deno.writeFileSync('./customFn.js', encoder.encode(emit));

[diagnostics, emit] = await Deno.bundle('./worker-fn.ts');

Deno.writeFileSync('./customWorker.js', encoder.encode(emit));

main.ts

import { serve } from "https://deno.land/[email protected]/http/server.ts";
import { ServerRequest } from "https://deno.land/[email protected]/http/server.ts";

const defaultPort: number = 4000;

const server = serve({ port: defaultPort });
console.log("listening on", defaultPort);

(async function main() {
    for await (const req of server) {
        exec(req);
    }
})();

function exec(req: ServerRequest) {
    const { conn } = req;
    const id = `${conn.rid} ${conn.remoteAddr.port} ${conn.remoteAddr.hostname}`;
    const workerFn = new Worker(`customWorker.js`, { name: 'workerFn', type: "module" });
    console.log("about to postMessage with id:", id);
    workerFn.postMessage({ id });

@alexanderGalushka
Copy link
Author

  1. run deno --allow-write bundler.ts
  2. run deno --v8-flags=--disallow-code-generation-from-strings --allow-net=:4000 main.ts
  3. curl http://localhost:4000

@kitsonk
Copy link
Contributor

kitsonk commented Feb 21, 2020

Hmmmm... bundling bundles. We certainly didn't design for that.

Is there a specific reason why you are bundling worker-fn.ts instead of just passing it on new Worker()?

@alexanderGalushka
Copy link
Author

you'd see the following msg:

listening on 4000
about to postMessage with id: 4 57322 127.0.0.1
error: Uncaught Error: Uncaught SyntaxError: Unexpected reserved word

it's a known issue and hopefully it will be fixed with TS 3.8

take out await from generated bundled code of customerWorker.js

@alexanderGalushka
Copy link
Author

you get this:

// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.

// This is a specialised implementation of a System module loader.

// eslint-disable-next-line @typescript-eslint/no-unused-vars
let System;
let __inst;

(() => {
  const mMap = new Map();
  System = {
    register(id, deps, f) {
      mMap.set(id, {
        id,
        deps,
        f,
        exp: {}
      });
    }
  };

  const gC = (data, main) => {
    const { id } = data;
    return {
      id,
      import: async id => mMap.get(id)?.exp,
      meta: { url: id, main }
    };
  };

  const gE = data => {
    const { exp } = data;
    return (id, value) => {
      const values = typeof id === "string" ? { [id]: value } : id;
      for (const [id, value] of Object.entries(values)) {
        Object.defineProperty(exp, id, {
          value,
          writable: true,
          enumerable: true
        });
      }
    };
  };

  const iQ = [];

  const enq = ids => {
    for (const id of ids) {
      if (!iQ.includes(id)) {
        const { deps } = mMap.get(id);
        iQ.push(id);
        enq(deps);
      }
    }
  };

  const dr = async main => {
    const rQ = [];
    let id;
    while ((id = iQ.pop())) {
      const m = mMap.get(id);
      const { f } = m;
      if (!f) {
        return;
      }
      rQ.push([m.deps, f(gE(m), gC(m, id === main))]);
      m.f = undefined;
    }
    let r;
    while ((r = rQ.shift())) {
      const [deps, { execute, setters }] = r;
      for (let i = 0; i < deps.length; i++) setters[i](mMap.get(deps[i])?.exp);
      const e = execute();
      if (e) e;
    }
  };

  __inst = async id => {
    System = undefined;
    __inst = undefined;
    enq([id]);
    dr(id);
    return mMap.get(id)?.exp;
  };
})();

// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
System.register("customFn", [], function (exports_2, context_2) {
    "use strict";
    var System, __inst, __exp;
    var __moduleName = context_2 && context_2.id;
    return {
        setters: [],
        execute: function () {
            (() => {
                const mMap = new Map();
                System = {
                    register(id, deps, f) {
                        mMap.set(id, {
                            id,
                            deps,
                            f,
                            exp: {}
                        });
                    }
                };
                const gC = (data, main) => {
                    const { id } = data;
                    return {
                        id,
                        import: async (id) => mMap.get(id)?.exp,
                        meta: { url: id, main }
                    };
                };
                const gE = data => {
                    const { exp } = data;
                    return (id, value) => {
                        const values = typeof id === "string" ? { [id]: value } : id;
                        for (const [id, value] of Object.entries(values)) {
                            Object.defineProperty(exp, id, {
                                value,
                                writable: true,
                                enumerable: true
                            });
                        }
                    };
                };
                const iQ = [];
                const enq = ids => {
                    for (const id of ids) {
                        if (!iQ.includes(id)) {
                            const { deps } = mMap.get(id);
                            iQ.push(id);
                            enq(deps);
                        }
                    }
                };
                const dr = async (main) => {
                    const rQ = [];
                    let id;
                    while ((id = iQ.pop())) {
                        const m = mMap.get(id);
                        const { f } = m;
                        if (!f) {
                            return;
                        }
                        rQ.push([m.deps, f(gE(m), gC(m, id === main))]);
                        m.f = undefined;
                    }
                    let r;
                    while ((r = rQ.shift())) {
                        const [deps, { execute, setters }] = r;
                        for (let i = 0; i < deps.length; i++)
                            setters[i](mMap.get(deps[i])?.exp);
                        const e = execute();
                        if (e)
                            e;
                    }
                };
                __inst = async (id) => {
                    System = undefined;
                    __inst = undefined;
                    enq([id]);
                    dr(id);
                    return mMap.get(id)?.exp;
                };
            })();
            System.register("clientCode", [], function (exports_1, context_1) {
                "use strict";
                var __moduleName = context_1 && context_1.id;
                function cubeIt(res) {
                    const result = 3 * 3 * 3;
                    res.send({ date: new Date(), result });
                }
                exports_1("default", cubeIt);
                return {
                    setters: [],
                    execute: function () {
                    }
                };
            });
            __exp = __inst("clientCode");
            exports_2("default", __exp["default"]);
        }
    };
});
System.register("worker-fn", ["customFn"], function (exports_1, context_1) {
    "use strict";
    var customFn_js_1, Resp;
    var __moduleName = context_1 && context_1.id;
    return {
        setters: [
            function (customFn_js_1_1) {
                customFn_js_1 = customFn_js_1_1;
            }
        ],
        execute: function () {
            Resp = class Resp {
                constructor(id) {
                    this.id = id;
                    this.headers = {};
                }
                status(status) {
                    this.statusCode = status;
                    return this;
                }
                send(result) {
                    const status = this.statusCode || 200;
                    postMessage({
                        headers: this.headers,
                        type: typeof result,
                        status,
                        id: this.id,
                        result
                    });
                }
            };
            if (self.name !== "workerFn")
                throw Error("Bad name");
            onmessage = function (e) {
                const { id } = e.data;
                const resp = new Resp(id);
                console.log("msg received: " + id);
                customFn_js_1.default(resp);
            };
            onerror = function (err) {
                // postmessage, 500
                console.log('on error', err);
                return false;
            };
        }
    };
});

__inst("worker-fn");

@alexanderGalushka
Copy link
Author

then rerun 2) and curl again

listening on 4000
about to postMessage with id: 4 48052 127.0.0.1
msg received: 4 48052 127.0.0.1
on error customFn_js_1.default is not a function
error: Uncaught Error: Uncaught TypeError: customFn_js_1.default is not a function
► $deno$/workers.ts:169:17
    at poll ($deno$/workers.ts:169:17)

@alexanderGalushka
Copy link
Author

@kitsonk and here is the real problem ^^ bundling bundles worked fine in v0.32.0 before the rework from AMD to System

@kitsonk
Copy link
Contributor

kitsonk commented Feb 21, 2020

I think bundling bundles is crazy personally... but hey, who am I to say.

The problem is that bundles require TLA, because System is needed to support TLA, the problem is TypeScript 3.7 isn't able to support TLA, so effectively we can't bundle a bundle under TS 3.7, because bundles now need TLA. TS 3.8 just came out, which I am working on a PR right now, which should restore the functionality.

@alexanderGalushka
Copy link
Author

@kitsonk thank you so much, you are amazing!

kitsonk added a commit to kitsonk/deno that referenced this issue Feb 25, 2020
Previously, bundles always utilised top level await, even if the bundled
modules didn't require top level await.  Now, analysis of the bundle is
done and if none of the bundled modules are asynchronously executed,
then the bundle as a whole will be synchronously executed.

Fixes denoland#4055
Fixes denoland#4123
kitsonk added a commit to kitsonk/deno that referenced this issue Feb 26, 2020
Previously, bundles always utilised top level await, even if the bundled
modules didn't require top level await.  Now, analysis of the bundle is
done and if none of the bundled modules are asynchronously executed,
then the bundle as a whole will be synchronously executed.

Fixes denoland#4055
Fixes denoland#4123
bartlomieju pushed a commit that referenced this issue Feb 26, 2020
Previously, bundles always utilised top level await, even if the bundled
modules didn't require top level await.  Now, analysis of the bundle is
done and if none of the bundled modules are asynchronously executed,
then the bundle as a whole will be synchronously executed.

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

Successfully merging a pull request may close this issue.

2 participants