UmdMainTemplatePlugin.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { ConcatSource, OriginalSource } = require("webpack-sources");
  7. const Template = require("./Template");
  8. /** @typedef {import("./Compilation")} Compilation */
  9. /**
  10. * @param {string[]} accessor the accessor to convert to path
  11. * @returns {string} the path
  12. */
  13. const accessorToObjectAccess = accessor => {
  14. return accessor.map(a => `[${JSON.stringify(a)}]`).join("");
  15. };
  16. /**
  17. * @param {string=} base the path prefix
  18. * @param {string|string[]} accessor the accessor
  19. * @param {string=} joinWith the element separator
  20. * @returns {string} the path
  21. */
  22. const accessorAccess = (base, accessor, joinWith = ", ") => {
  23. const accessors = Array.isArray(accessor) ? accessor : [accessor];
  24. return accessors
  25. .map((_, idx) => {
  26. const a = base
  27. ? base + accessorToObjectAccess(accessors.slice(0, idx + 1))
  28. : accessors[0] + accessorToObjectAccess(accessors.slice(1, idx + 1));
  29. if (idx === accessors.length - 1) return a;
  30. if (idx === 0 && typeof base === "undefined")
  31. return `${a} = typeof ${a} === "object" ? ${a} : {}`;
  32. return `${a} = ${a} || {}`;
  33. })
  34. .join(joinWith);
  35. };
  36. /** @typedef {string | string[] | Record<string, string | string[]>} UmdMainTemplatePluginName */
  37. /**
  38. * @typedef {Object} AuxiliaryCommentObject
  39. * @property {string} root
  40. * @property {string} commonjs
  41. * @property {string} commonjs2
  42. * @property {string} amd
  43. */
  44. /**
  45. * @typedef {Object} UmdMainTemplatePluginOption
  46. * @property {boolean=} optionalAmdExternalAsGlobal
  47. * @property {boolean} namedDefine
  48. * @property {string | AuxiliaryCommentObject} auxiliaryComment
  49. */
  50. class UmdMainTemplatePlugin {
  51. /**
  52. * @param {UmdMainTemplatePluginName} name the name of the UMD library
  53. * @param {UmdMainTemplatePluginOption} options the plugin option
  54. */
  55. constructor(name, options) {
  56. if (typeof name === "object" && !Array.isArray(name)) {
  57. this.name = name.root || name.amd || name.commonjs;
  58. this.names = name;
  59. } else {
  60. this.name = name;
  61. this.names = {
  62. commonjs: name,
  63. root: name,
  64. amd: name
  65. };
  66. }
  67. this.optionalAmdExternalAsGlobal = options.optionalAmdExternalAsGlobal;
  68. this.namedDefine = options.namedDefine;
  69. this.auxiliaryComment = options.auxiliaryComment;
  70. }
  71. /**
  72. * @param {Compilation} compilation the compilation instance
  73. * @returns {void}
  74. */
  75. apply(compilation) {
  76. const { mainTemplate, chunkTemplate, runtimeTemplate } = compilation;
  77. const onRenderWithEntry = (source, chunk, hash) => {
  78. let externals = chunk
  79. .getModules()
  80. .filter(
  81. m =>
  82. m.external &&
  83. (m.externalType === "umd" || m.externalType === "umd2")
  84. );
  85. const optionalExternals = [];
  86. let requiredExternals = [];
  87. if (this.optionalAmdExternalAsGlobal) {
  88. for (const m of externals) {
  89. if (m.optional) {
  90. optionalExternals.push(m);
  91. } else {
  92. requiredExternals.push(m);
  93. }
  94. }
  95. externals = requiredExternals.concat(optionalExternals);
  96. } else {
  97. requiredExternals = externals;
  98. }
  99. const replaceKeys = str => {
  100. return mainTemplate.getAssetPath(str, {
  101. hash,
  102. chunk
  103. });
  104. };
  105. const externalsDepsArray = modules => {
  106. return `[${replaceKeys(
  107. modules
  108. .map(m =>
  109. JSON.stringify(
  110. typeof m.request === "object" ? m.request.amd : m.request
  111. )
  112. )
  113. .join(", ")
  114. )}]`;
  115. };
  116. const externalsRootArray = modules => {
  117. return replaceKeys(
  118. modules
  119. .map(m => {
  120. let request = m.request;
  121. if (typeof request === "object") request = request.root;
  122. return `root${accessorToObjectAccess([].concat(request))}`;
  123. })
  124. .join(", ")
  125. );
  126. };
  127. const externalsRequireArray = type => {
  128. return replaceKeys(
  129. externals
  130. .map(m => {
  131. let expr;
  132. let request = m.request;
  133. if (typeof request === "object") {
  134. request = request[type];
  135. }
  136. if (typeof request === "undefined") {
  137. throw new Error(
  138. "Missing external configuration for type:" + type
  139. );
  140. }
  141. if (Array.isArray(request)) {
  142. expr = `require(${JSON.stringify(
  143. request[0]
  144. )})${accessorToObjectAccess(request.slice(1))}`;
  145. } else {
  146. expr = `require(${JSON.stringify(request)})`;
  147. }
  148. if (m.optional) {
  149. expr = `(function webpackLoadOptionalExternalModule() { try { return ${expr}; } catch(e) {} }())`;
  150. }
  151. return expr;
  152. })
  153. .join(", ")
  154. );
  155. };
  156. const externalsArguments = modules => {
  157. return modules
  158. .map(
  159. m =>
  160. `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier(`${m.id}`)}__`
  161. )
  162. .join(", ");
  163. };
  164. const libraryName = library => {
  165. return JSON.stringify(replaceKeys([].concat(library).pop()));
  166. };
  167. let amdFactory;
  168. if (optionalExternals.length > 0) {
  169. const wrapperArguments = externalsArguments(requiredExternals);
  170. const factoryArguments =
  171. requiredExternals.length > 0
  172. ? externalsArguments(requiredExternals) +
  173. ", " +
  174. externalsRootArray(optionalExternals)
  175. : externalsRootArray(optionalExternals);
  176. amdFactory =
  177. `function webpackLoadOptionalExternalModuleAmd(${wrapperArguments}) {\n` +
  178. ` return factory(${factoryArguments});\n` +
  179. " }";
  180. } else {
  181. amdFactory = "factory";
  182. }
  183. const auxiliaryComment = this.auxiliaryComment;
  184. const getAuxilaryComment = type => {
  185. if (auxiliaryComment) {
  186. if (typeof auxiliaryComment === "string")
  187. return "\t//" + auxiliaryComment + "\n";
  188. if (auxiliaryComment[type])
  189. return "\t//" + auxiliaryComment[type] + "\n";
  190. }
  191. return "";
  192. };
  193. return new ConcatSource(
  194. new OriginalSource(
  195. "(function webpackUniversalModuleDefinition(root, factory) {\n" +
  196. getAuxilaryComment("commonjs2") +
  197. " if(typeof exports === 'object' && typeof module === 'object')\n" +
  198. " module.exports = factory(" +
  199. externalsRequireArray("commonjs2") +
  200. ");\n" +
  201. getAuxilaryComment("amd") +
  202. " else if(typeof define === 'function' && define.amd)\n" +
  203. (requiredExternals.length > 0
  204. ? this.names.amd && this.namedDefine === true
  205. ? " define(" +
  206. libraryName(this.names.amd) +
  207. ", " +
  208. externalsDepsArray(requiredExternals) +
  209. ", " +
  210. amdFactory +
  211. ");\n"
  212. : " define(" +
  213. externalsDepsArray(requiredExternals) +
  214. ", " +
  215. amdFactory +
  216. ");\n"
  217. : this.names.amd && this.namedDefine === true
  218. ? " define(" +
  219. libraryName(this.names.amd) +
  220. ", [], " +
  221. amdFactory +
  222. ");\n"
  223. : " define([], " + amdFactory + ");\n") +
  224. (this.names.root || this.names.commonjs
  225. ? getAuxilaryComment("commonjs") +
  226. " else if(typeof exports === 'object')\n" +
  227. " exports[" +
  228. libraryName(this.names.commonjs || this.names.root) +
  229. "] = factory(" +
  230. externalsRequireArray("commonjs") +
  231. ");\n" +
  232. getAuxilaryComment("root") +
  233. " else\n" +
  234. " " +
  235. replaceKeys(
  236. accessorAccess("root", this.names.root || this.names.commonjs)
  237. ) +
  238. " = factory(" +
  239. externalsRootArray(externals) +
  240. ");\n"
  241. : " else {\n" +
  242. (externals.length > 0
  243. ? " var a = typeof exports === 'object' ? factory(" +
  244. externalsRequireArray("commonjs") +
  245. ") : factory(" +
  246. externalsRootArray(externals) +
  247. ");\n"
  248. : " var a = factory();\n") +
  249. " for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];\n" +
  250. " }\n") +
  251. `})(${
  252. runtimeTemplate.outputOptions.globalObject
  253. }, function(${externalsArguments(externals)}) {\nreturn `,
  254. "webpack/universalModuleDefinition"
  255. ),
  256. source,
  257. ";\n})"
  258. );
  259. };
  260. for (const template of [mainTemplate, chunkTemplate]) {
  261. template.hooks.renderWithEntry.tap(
  262. "UmdMainTemplatePlugin",
  263. onRenderWithEntry
  264. );
  265. }
  266. mainTemplate.hooks.globalHashPaths.tap("UmdMainTemplatePlugin", paths => {
  267. if (this.names.root) paths = paths.concat(this.names.root);
  268. if (this.names.amd) paths = paths.concat(this.names.amd);
  269. if (this.names.commonjs) paths = paths.concat(this.names.commonjs);
  270. return paths;
  271. });
  272. mainTemplate.hooks.hash.tap("UmdMainTemplatePlugin", hash => {
  273. hash.update("umd");
  274. hash.update(`${this.names.root}`);
  275. hash.update(`${this.names.amd}`);
  276. hash.update(`${this.names.commonjs}`);
  277. });
  278. }
  279. }
  280. module.exports = UmdMainTemplatePlugin;