Takeshi Tokugawa YD
Takeshi Tokugawa YD

Reputation: 913

How to build TypeScript/JavaScript code by Webpack which will be executed in the Pug pre-processor?

I want to build some TypeScript code by Webpack and make it available inside Pug inline code like:

-

  const { formatNumberWith4KetaKanji } = global.__EXPORTS_FROM_LIBRARY__;
 
  const test = 1;
  functionFromTheLibrary(test) 

Please note that this functinality must be distibuted via Pug files. It means that usage of locals of Pug API is not acceptable.

In the question Which JavaScript runtime the Pug 3.X using? I tried to get the info about JavaScript runtime of Pug, but for this moment, it still unanswered.

We can exclude the Pug from this problem and the question will become to "How to build importable bundle of Webpack from TypeScript which does not using neither window nor module objects"? I suppose, if I asked such as, I was given the instructions to explaing why I need it.

Experiments

Try to provide the function separateEach3DigitsGroupWithComma from the Yamato Daiwa ES Extensions library. The source TypeScript file is:

export { separateEach3DigitsGroupWithComma } from "@yamato-daiwa/es-extensions";

Once it will be compiled to JavaScript, we can write this code to .pug file with leading en dash (I already implemented this automation):

-
  // compiled JavaScript code here ...

Now, if to setup the Webpack correctly, we can take above function as:

include GeneratedExports.pug

-

  const { separateEach3DigitsGroupWithComma } = global.__PUG_EXTENSIONS_EXPORTS__;


.ProductCard
  .ProductCard-Title Lamborghini Urus
  .ProductCard-PriceLabel= separateEach3DigitsGroupWithComma(211321) 

Attempt 1

Generated code:

! function(e, t) {
  "object" == typeof exports && "object" == typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define([], t) : "object" == typeof exports ? exports.__PUG_EXTENSIONS_EXPORTS__ = t() : e.__PUG_EXTENSIONS_EXPORTS__ = t()
}(self, (() => (() => {
  "use strict";
  var e = {
      d: (t, o) => {
        for (var r in o) e.o(o, r) && !e.o(t, r) && Object.defineProperty(t, r, {
          enumerable: !0,
          get: o[r]
        })
      },
      o: (e, t) => Object.prototype.hasOwnProperty.call(e, t),
      r: e => {
        "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {
          value: "Module"
        }), Object.defineProperty(e, "__esModule", {
          value: !0
        })
      }
    },
    t = {};

  function o(e) {
    return String(e).replace(/\B(?=(?:\d{3})+(?!\d))/gu, ",")
  }
  return e.r(t), e.d(t, {
    separateEach3DigitsGroupWithComma: () => o
  }), t
})()));

Pug compilation error:

Cannot set properties of undefined (setting '__PUG_EXTENSIONS_EXPORTS__')

Maybe it refers to exports.__PUG_EXTENSIONS_EXPORTS__ or to e.__PUG_EXTENSIONS_EXPORTS__. Both exports and e are undefined (in the Pug runtime).

Attempt 2

Generated code:

!function(e, o) {
  "object" == typeof exports && "object" == typeof module ? module.exports = o() : "function" == typeof define && define.amd ? define([], o) : "object" == typeof exports ? exports.__PUG_EXTENSIONS_EXPORTS__ = o() : e.__PUG_EXTENSIONS_EXPORTS__ = o()
}(global, (() => (() => {
  "use strict";
  var e = {
      d: (o, t) => {
        for (var r in t) e.o(t, r) && !e.o(o, r) && Object.defineProperty(o, r, {
          enumerable: !0,
          get: t[r]
        })
      },
      o: (e, o) => Object.prototype.hasOwnProperty.call(e, o),
      r: e => {
        "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {
          value: "Module"
        }), Object.defineProperty(e, "__esModule", {
          value: !0
        })
      }
    },
    o = {};
  e.r(o), e.d(o, {
    separateEach3DigitsGroupWithComma: () => t.separateEach3DigitsGroupWithComma
  });
  const t = require("@yamato-daiwa/es-extensions");
  return o
})()));

TypeScript error:

require is not a function

Working code sample

I have working code sample which has been generated a log time ago. The first lines of the code are pretty similar...

Currently it's unclear which webpack config I have used, but it was actual for the old webpack.

!function(e, t) {
  "object" == typeof exports && "object" == typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define([], t) : "object" == typeof exports ? exports.__PUG_EXTENSIONS_EXPORTS__ = t() : e.__PUG_EXTENSIONS_EXPORTS__ = t()
}(global, (function() {
  return (() => {
    "use strict";
    var e = {
        d: (t, r) => {
          for (var n in r) e.o(r, n) && !e.o(t, n) && Object.defineProperty(t, n, {
            enumerable: !0,
            get: r[n]
          })
        },
        o: (e, t) => Object.prototype.hasOwnProperty.call(e, t),
        r: e => {
          "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {
            value: "Module"
          }), Object.defineProperty(e, "__esModule", {
            value: !0
          })
        }
      },
      t = {};

    function r(e) {
      return function(e, t) {
        return Array.from(e)
      }(e).reverse().join("")
    }

    function n(e) {
      return void 0 === e
    }

    function o(e) {
      let t = e.toString();
      const o = t.startsWith("-"),
        u = t.split(".")[1];
      if (t.length < 5) return t;
      o && (t = t.substring(1)), n(u) || (t = t.replace(`.${u}`, ""));
      const i = r(t),
        a = r(i.substr(0, 4)),
        l = r(i.substr(4, 4)),
        s = r(i.substr(8, 4)),
        c = r(i.substr(12, 4)),
        f = r(i.substr(16, 4)),
        m = r(i.substr(20));
      return (o ? "-" : "") + (m.length > 0 ? `${m}` : "") + (f.length > 0 ? `${f}δΊ¬` : "") + (c.length > 0 ? `${c}ε…†` : "") + (s.length > 0 ? `${s}ε„„` : "") + (l.length > 0 ? `${l}δΈ‡` : "") + `${a}` + (n(u) ? "" : `.${u}`)
    }

    function u(e) {
      return /^[1-9][0-9]*$/u.test(e)
    }

    function i(e) {
      return String(e).replace(/\B(?=(?:\d{3})+(?!\d))/gu, ",")
    }

    function a(e) {
      return String(e).replace(/\B(?=(?:\d{4})+(?!\d))/gu, ",")
    }

    function l(e) {
      return e.replace(/\s{2,}/gmu, " ")
    }

    function s({
      minimalValue: e,
      maximalValue: t
    }) {
      return Math.floor(Math.random() * (t - e + 1) + e)
    }

    function c(e) {
      return e[s({
        minimalValue: 0,
        maximalValue: e.length - 1
      })]
    }

    function f() {
      return Math.random() >= .5
    }

    function m(e) {
      return `mailto:${e}`
    }

    function g(e) {
      return `tel:${e}`
    }
    return e.r(t), e.d(t, {
      buildEmailLinkHREF_ArttributeValue: () => m,
      buildPhoneNumberLinkHREF_ArttributeValue: () => g,
      formatNumberWith4KetaKanji: () => o,
      getRandomArrayElement: () => c,
      getRandomBoolean: () => f,
      getRandomInteger: () => s,
      isStringifiedNonNegativeIntegerOfRegularNotation: () => u,
      replace2OrMoreSpacesTo1: () => l,
      separateEach3DigitsGroupWithComma: () => i,
      separateEach4DigitsGroupWithComma: () => a
    }), t
  })()
}));

Upvotes: 0

Views: 306

Answers (1)

hackape
hackape

Reputation: 19957

TL;DR: According to the doc, you need to set output.globalObject. This config should work:

output.target = "web" // default
output.library.type = "umd"
output.library.name = "PUG_EXTENSIONS_EXPORTS"
output.globalObject = "this" // or "globalThis"

A closer look to your 2 attempts I find them differ in 2 places.

  1. For global object, (a) "web" target uses self, (b) "node" uses global
  2. (c) "web" target inlines your package code in the output, while (d) "node" uses require to link package.

You want combination of (b) and (c), that's the config I provide above. Problem solved.

Below is me sharing a detour during investigation, which uncovers some technical details in Pug engine, and eventually winds up at a piece of probably lesser known info.

TL;DR: require is NOT global variable in Node.js (nor are module, exports, __dirname, __filename).


I know for a fact that Pug is just using Node.js as runtime, that explains 1st error:

Cannot set properties of undefined (setting '__PUG_EXTENSIONS_EXPORTS__')

Cus in Node.js self does not point to global object. However I don't understand the 2nd error:

require is not a function

Why? πŸ€” I thought require is global variable in Node.js.

Out of curiosity I did a bit of code forensics and find out the reason. Check here link.

In essence, Pug works like this:

var pugTemplate = fs.readFileSync('./template.pug', 'utf8');
var renderer = pug.compile(pugTemplate, options);
var html = renderer(locals);

Tricky thing is how Pug compile. It use Function constructor to create the renderer function.

// slightly simplified, not actual implementation
var js = pug.compileBody(pugTemplate, options);
var renderer = new Function('', js);

Which, if applied to your code, would look sth like:

var js = `
var pug_html = "";

!function(e, t) {
  e.__PUG_EXTENSIONS_EXPORTS__ = t();
}(global, (() => {
  "use strict";

  var t = require("@yamato-daiwa/es-extensions");
  var _exports = {
    separateEach3DigitsGroupWithComma: t.separateEach3DigitsGroupWithComma;
  };

  return _exports;
}));

return pug_html;
`

var renderer = new Function('', js);

renderer(); // πŸ’₯ ReferenceError: require is not defined

So I checked MDN and it says:

Function constructor creates functions which execute in the global scope only.

But it looks fine! Isn't require in the global scope? ...unless IT IS NOT! πŸ€¦β€β™‚οΈ

So I went to check Node.js doc:

These objects are available in all modules. 
The following variables may appear to be global but are not. 
They exist only in the scope of modules, see the module system documentation:

  * __dirname
  * __filename
  * exports
  * module
  * require()

OK, today I learned, require is not global variable. πŸ˜‚

In fact each top level JS file, aka module, before executed in Node.js, is first wrapped in a function like below (link):

(function (exports, require, module, __filename, __dirname) {
  // module code here
});

which means you can even console.log(arguments) right at top level. require lives in the local scope of this wrapper function, aka module scope, not in global scope.

Upvotes: 1

Related Questions