You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

305 lines
8.6 KiB

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