UmdLibraryPlugin.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  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 ExternalModule = require("../ExternalModule");
  8. const Template = require("../Template");
  9. const AbstractLibraryPlugin = require("./AbstractLibraryPlugin");
  10. /** @import { Source } from "webpack-sources" */
  11. /**
  12. * @import {
  13. * LibraryCustomUmdCommentObject,
  14. * LibraryCustomUmdObject,
  15. * LibraryName,
  16. * LibraryOptions,
  17. * LibraryType
  18. * } from "../../declarations/WebpackOptions"
  19. */
  20. /** @import { RenderContext } from "../javascript/JavascriptModulesPlugin" */
  21. /** @import { RequestRecord } from "../ExternalModule" */
  22. /**
  23. * Defines the shared type used by this module.
  24. * @template T
  25. * @typedef {import("./AbstractLibraryPlugin").LibraryContext<T>} LibraryContext<T>
  26. */
  27. /**
  28. * Accessor to object access.
  29. * @param {string[]} accessor the accessor to convert to path
  30. * @returns {string} the path
  31. */
  32. const accessorToObjectAccess = (accessor) =>
  33. accessor.map((a) => `[${JSON.stringify(a)}]`).join("");
  34. /** @typedef {string | string[]} Accessor */
  35. /**
  36. * Returns the path.
  37. * @param {string | undefined} base the path prefix
  38. * @param {Accessor} accessor the accessor
  39. * @param {string=} joinWith the element separator
  40. * @returns {string} the path
  41. */
  42. const accessorAccess = (base, accessor, joinWith = ", ") => {
  43. const accessors = Array.isArray(accessor) ? accessor : [accessor];
  44. return accessors
  45. .map((_, idx) => {
  46. const a = base
  47. ? base + accessorToObjectAccess(accessors.slice(0, idx + 1))
  48. : accessors[0] + accessorToObjectAccess(accessors.slice(1, idx + 1));
  49. if (idx === accessors.length - 1) return a;
  50. if (idx === 0 && base === undefined) {
  51. return `${a} = typeof ${a} === "object" ? ${a} : {}`;
  52. }
  53. return `${a} = ${a} || {}`;
  54. })
  55. .join(joinWith);
  56. };
  57. /**
  58. * Builds the guard for an AMD-style loader on a container object, e.g. `sap.ui` becomes
  59. * `typeof sap !== 'undefined' && sap.ui && typeof sap.ui.define === 'function'`. The path is
  60. * walked step by step because `typeof sap.ui` alone throws when `sap` is undeclared, which is
  61. * exactly when the branch has to be skipped.
  62. * @param {string} container the container object holding `define`
  63. * @returns {string} the condition
  64. */
  65. const amdContainerCondition = (container) => {
  66. const path = container.split(".");
  67. const conditions = [`typeof ${path[0]} !== 'undefined'`];
  68. for (let i = 1; i < path.length; i++) {
  69. conditions.push(path.slice(0, i + 1).join("."));
  70. }
  71. conditions.push(`typeof ${container}.define === 'function'`);
  72. return conditions.join(" && ");
  73. };
  74. /**
  75. * Defines the umd library plugin options type used by this module.
  76. * @typedef {object} UmdLibraryPluginOptions
  77. * @property {LibraryType} type
  78. * @property {boolean=} optionalAmdExternalAsGlobal
  79. */
  80. /**
  81. * Defines the umd library plugin parsed type used by this module.
  82. * @typedef {object} UmdLibraryPluginParsed
  83. * @property {string | string[] | undefined} name
  84. * @property {LibraryCustomUmdObject} names
  85. * @property {string | LibraryCustomUmdCommentObject | undefined} auxiliaryComment
  86. * @property {boolean | undefined} namedDefine
  87. * @property {string | undefined} amdContainer
  88. */
  89. /**
  90. * Represents the umd library plugin runtime component.
  91. * @typedef {UmdLibraryPluginParsed} T
  92. * @extends {AbstractLibraryPlugin<UmdLibraryPluginParsed>}
  93. */
  94. class UmdLibraryPlugin extends AbstractLibraryPlugin {
  95. /**
  96. * Creates an instance of UmdLibraryPlugin.
  97. * @param {UmdLibraryPluginOptions} options the plugin option
  98. */
  99. constructor(options) {
  100. super({
  101. pluginName: "UmdLibraryPlugin",
  102. type: options.type
  103. });
  104. /** @type {UmdLibraryPluginOptions["optionalAmdExternalAsGlobal"]} */
  105. this.optionalAmdExternalAsGlobal = options.optionalAmdExternalAsGlobal;
  106. }
  107. /**
  108. * Returns preprocess as needed by overriding.
  109. * @param {LibraryOptions} library normalized library option
  110. * @returns {T} preprocess as needed by overriding
  111. */
  112. parseOptions(library) {
  113. /** @type {LibraryName | undefined} */
  114. let name;
  115. /** @type {LibraryCustomUmdObject} */
  116. let names;
  117. if (typeof library.name === "object" && !Array.isArray(library.name)) {
  118. name = library.name.root || library.name.amd || library.name.commonjs;
  119. names = library.name;
  120. } else {
  121. name = library.name;
  122. const singleName = Array.isArray(name) ? name[0] : name;
  123. names = {
  124. commonjs: singleName,
  125. root: library.name,
  126. amd: singleName
  127. };
  128. }
  129. return {
  130. name,
  131. names,
  132. auxiliaryComment: library.auxiliaryComment,
  133. namedDefine: library.umdNamedDefine,
  134. amdContainer: library.umdAmdContainer
  135. };
  136. }
  137. /**
  138. * Returns source with library export.
  139. * @param {Source} source source
  140. * @param {RenderContext} renderContext render context
  141. * @param {LibraryContext<T>} libraryContext context
  142. * @returns {Source} source with library export
  143. */
  144. render(
  145. source,
  146. { chunkGraph, runtimeTemplate, chunk, moduleGraph },
  147. { options, compilation }
  148. ) {
  149. const modules = chunkGraph
  150. .getChunkModules(chunk)
  151. .filter(
  152. (m) =>
  153. m instanceof ExternalModule &&
  154. (m.externalType === "umd" || m.externalType === "umd2")
  155. );
  156. let externals = /** @type {ExternalModule[]} */ (modules);
  157. /** @type {ExternalModule[]} */
  158. const optionalExternals = [];
  159. /** @type {ExternalModule[]} */
  160. let requiredExternals = [];
  161. if (this.optionalAmdExternalAsGlobal) {
  162. for (const m of externals) {
  163. if (m.isOptional(moduleGraph)) {
  164. optionalExternals.push(m);
  165. } else {
  166. requiredExternals.push(m);
  167. }
  168. }
  169. externals = [...requiredExternals, ...optionalExternals];
  170. } else {
  171. requiredExternals = externals;
  172. }
  173. /**
  174. * Returns the replaced keys.
  175. * @param {string} str the string to replace
  176. * @returns {string} the replaced keys
  177. */
  178. const replaceKeys = (str) =>
  179. compilation.getPath(str, {
  180. chunk
  181. });
  182. /**
  183. * Externals deps array.
  184. * @param {ExternalModule[]} modules external modules
  185. * @returns {string} result
  186. */
  187. const externalsDepsArray = (modules) =>
  188. `[${replaceKeys(
  189. modules
  190. .map((m) =>
  191. JSON.stringify(
  192. typeof m.request === "object"
  193. ? /** @type {RequestRecord} */
  194. (m.request).amd
  195. : m.request
  196. )
  197. )
  198. .join(", ")
  199. )}]`;
  200. /**
  201. * Externals root array.
  202. * @param {ExternalModule[]} modules external modules
  203. * @returns {string} result
  204. */
  205. const externalsRootArray = (modules) =>
  206. replaceKeys(
  207. modules
  208. .map((m) => {
  209. let request = m.request;
  210. if (typeof request === "object") {
  211. request =
  212. /** @type {RequestRecord} */
  213. (request).root;
  214. }
  215. return `root${accessorToObjectAccess([
  216. ...(Array.isArray(request) ? request : [request])
  217. ])}`;
  218. })
  219. .join(", ")
  220. );
  221. /**
  222. * Externals require array.
  223. * @param {string} type the type
  224. * @returns {string} external require array
  225. */
  226. const externalsRequireArray = (type) =>
  227. replaceKeys(
  228. externals
  229. .map((m) => {
  230. let request = m.request;
  231. if (typeof request === "object") {
  232. request =
  233. /** @type {RequestRecord} */
  234. (request)[type];
  235. }
  236. if (request === undefined) {
  237. throw new Error(
  238. `Missing external configuration for type:${type}`
  239. );
  240. }
  241. let expr = Array.isArray(request)
  242. ? `require(${JSON.stringify(request[0])})${accessorToObjectAccess(
  243. request.slice(1)
  244. )}`
  245. : `require(${JSON.stringify(request)})`;
  246. if (m.isOptional(moduleGraph)) {
  247. expr = `(function webpackLoadOptionalExternalModule() { try { return ${expr}; } catch(e) {} }())`;
  248. }
  249. return expr;
  250. })
  251. .join(", ")
  252. );
  253. /**
  254. * Externals arguments.
  255. * @param {ExternalModule[]} modules external modules
  256. * @returns {string} arguments
  257. */
  258. const externalsArguments = (modules) =>
  259. modules
  260. .map(
  261. (m) =>
  262. `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier(
  263. `${chunkGraph.getModuleId(m)}`
  264. )}__`
  265. )
  266. .join(", ");
  267. /**
  268. * Returns stringified library name.
  269. * @param {Accessor} library library name
  270. * @returns {string} stringified library name
  271. */
  272. const libraryName = (library) =>
  273. JSON.stringify(
  274. replaceKeys(
  275. /** @type {string} */
  276. ([...(Array.isArray(library) ? library : [library])].pop())
  277. )
  278. );
  279. /** @type {string} */
  280. let amdFactory;
  281. if (optionalExternals.length > 0) {
  282. const wrapperArguments = externalsArguments(requiredExternals);
  283. const factoryArguments =
  284. requiredExternals.length > 0
  285. ? `${externalsArguments(requiredExternals)}, ${externalsRootArray(
  286. optionalExternals
  287. )}`
  288. : externalsRootArray(optionalExternals);
  289. amdFactory =
  290. `function webpackLoadOptionalExternalModuleAmd(${wrapperArguments}) {\n` +
  291. ` return factory(${factoryArguments});\n` +
  292. " }";
  293. } else {
  294. amdFactory = "factory";
  295. }
  296. const { amdContainer, auxiliaryComment, namedDefine, names } = options;
  297. /**
  298. * Returns a call to an AMD-style define function.
  299. * @param {string} defineFunction the define function to call
  300. * @returns {string} the define call
  301. */
  302. const defineCall = (defineFunction) => {
  303. const deps =
  304. requiredExternals.length > 0
  305. ? externalsDepsArray(requiredExternals)
  306. : "[]";
  307. return names.amd && namedDefine === true
  308. ? ` ${defineFunction}(${libraryName(
  309. names.amd
  310. )}, ${deps}, ${amdFactory});\n`
  311. : ` ${defineFunction}(${deps}, ${amdFactory});\n`;
  312. };
  313. /**
  314. * Gets auxiliary comment.
  315. * @param {keyof LibraryCustomUmdCommentObject} type type
  316. * @returns {string} comment
  317. */
  318. const getAuxiliaryComment = (type) => {
  319. if (auxiliaryComment) {
  320. if (typeof auxiliaryComment === "string") {
  321. return `\t//${auxiliaryComment}\n`;
  322. }
  323. if (auxiliaryComment[type]) return `\t//${auxiliaryComment[type]}\n`;
  324. }
  325. return "";
  326. };
  327. return new ConcatSource(
  328. new OriginalSource(
  329. `(function webpackUniversalModuleDefinition(root, factory) {\n${getAuxiliaryComment(
  330. "commonjs2"
  331. )} if(typeof exports === 'object' && typeof module === 'object')\n` +
  332. ` module.exports = factory(${externalsRequireArray(
  333. "commonjs2"
  334. )});\n${getAuxiliaryComment(
  335. "amd"
  336. )} else if(typeof define === 'function' && define.amd)\n${defineCall(
  337. "define"
  338. )}${
  339. amdContainer
  340. ? ` else if(${amdContainerCondition(
  341. amdContainer
  342. )})\n${defineCall(`${amdContainer}.define`)}`
  343. : ""
  344. }${
  345. names.root || names.commonjs
  346. ? `${getAuxiliaryComment(
  347. "commonjs"
  348. )} else if(typeof exports === 'object')\n` +
  349. ` exports[${libraryName(
  350. /** @type {Accessor} */
  351. (names.commonjs || names.root)
  352. )}] = factory(${externalsRequireArray(
  353. "commonjs"
  354. )});\n${getAuxiliaryComment("root")} else\n` +
  355. ` ${replaceKeys(
  356. accessorAccess(
  357. "root",
  358. /** @type {Accessor} */
  359. (names.root || names.commonjs)
  360. )
  361. )} = factory(${externalsRootArray(externals)});\n`
  362. : ` else {\n${
  363. externals.length > 0
  364. ? ` var a = typeof exports === 'object' ? factory(${externalsRequireArray(
  365. "commonjs"
  366. )}) : factory(${externalsRootArray(externals)});\n`
  367. : " var a = factory();\n"
  368. } for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];\n` +
  369. " }\n"
  370. }})(${runtimeTemplate.globalObject}, ${
  371. runtimeTemplate.supportsArrowFunction()
  372. ? `(${externalsArguments(externals)}) =>`
  373. : `function(${externalsArguments(externals)})`
  374. } {\nreturn `,
  375. "webpack/universalModuleDefinition"
  376. ),
  377. source,
  378. ";\n})"
  379. );
  380. }
  381. }
  382. module.exports = UmdLibraryPlugin;