DllReferencePlugin.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const ExternalModuleFactoryPlugin = require("../ExternalModuleFactoryPlugin");
  7. const DelegatedSourceDependency = require("../dependencies/DelegatedSourceDependency");
  8. const WebpackError = require("../errors/WebpackError");
  9. const { makePathsRelative } = require("../util/identifier");
  10. const parseJson = require("../util/parseJson");
  11. const DelegatedModuleFactoryPlugin = require("./DelegatedModuleFactoryPlugin");
  12. /** @import { Externals } from "../../declarations/WebpackOptions" */
  13. /**
  14. * @import {
  15. * DllReferencePluginOptions,
  16. * DllReferencePluginOptionsContent,
  17. * DllReferencePluginOptionsManifest
  18. * } from "../../declarations/plugins/dll/DllReferencePlugin"
  19. */
  20. /** @import Compiler, { CompilationParams } from "../Compiler" */
  21. /** @import { InputFileSystem } from "../util/fs" */
  22. /** @typedef {{ path: string, data: DllReferencePluginOptionsManifest | undefined, error: Error | undefined }} CompilationDataItem */
  23. const PLUGIN_NAME = "DllReferencePlugin";
  24. class DllReferencePlugin {
  25. /**
  26. * Creates an instance of DllReferencePlugin.
  27. * @param {DllReferencePluginOptions} options options object
  28. */
  29. constructor(options) {
  30. /** @type {DllReferencePluginOptions} */
  31. this.options = options;
  32. }
  33. /**
  34. * Applies the plugin by registering its hooks on the compiler.
  35. * @param {Compiler} compiler the compiler instance
  36. * @returns {void}
  37. */
  38. apply(compiler) {
  39. compiler.hooks.validate.tap(PLUGIN_NAME, () => {
  40. compiler.validate(
  41. () => require("../../schemas/plugins/dll/DllReferencePlugin.json"),
  42. this.options,
  43. {
  44. name: "Dll Reference Plugin",
  45. baseDataPath: "options"
  46. },
  47. (options) =>
  48. require("../../schemas/plugins/dll/DllReferencePlugin.check")(options)
  49. );
  50. });
  51. compiler.hooks.compilation.tap(
  52. PLUGIN_NAME,
  53. (compilation, { normalModuleFactory }) => {
  54. compilation.dependencyFactories.set(
  55. DelegatedSourceDependency,
  56. normalModuleFactory
  57. );
  58. }
  59. );
  60. /** @type {WeakMap<CompilationParams, CompilationDataItem>} */
  61. const compilationData = new WeakMap();
  62. compiler.hooks.beforeCompile.tapAsync(PLUGIN_NAME, (params, callback) => {
  63. if ("manifest" in this.options) {
  64. const manifest = this.options.manifest;
  65. if (typeof manifest === "string") {
  66. /** @type {InputFileSystem} */
  67. (compiler.inputFileSystem).readFile(manifest, (err, result) => {
  68. if (err) return callback(err);
  69. /** @type {CompilationDataItem} */
  70. const data = {
  71. path: manifest,
  72. data: undefined,
  73. error: undefined
  74. };
  75. // Catch errors parsing the manifest so that blank
  76. // or malformed manifest files don't kill the process.
  77. try {
  78. data.data =
  79. /** @type {DllReferencePluginOptionsManifest} */
  80. (
  81. /** @type {unknown} */
  82. (parseJson(/** @type {Buffer} */ (result).toString("utf8")))
  83. );
  84. } catch (parseErr) {
  85. // Store the error in the params so that it can
  86. // be added as a compilation error later on.
  87. const manifestPath = makePathsRelative(
  88. compiler.context,
  89. manifest,
  90. compiler.root
  91. );
  92. data.error = new DllManifestError(
  93. manifestPath,
  94. /** @type {Error} */ (parseErr).message
  95. );
  96. }
  97. compilationData.set(params, data);
  98. return callback();
  99. });
  100. return;
  101. }
  102. }
  103. return callback();
  104. });
  105. compiler.hooks.compile.tap(PLUGIN_NAME, (params) => {
  106. let name = this.options.name;
  107. let sourceType = this.options.sourceType;
  108. let resolvedContent =
  109. "content" in this.options ? this.options.content : undefined;
  110. if ("manifest" in this.options) {
  111. const manifestParameter = this.options.manifest;
  112. /** @type {undefined | DllReferencePluginOptionsManifest} */
  113. let manifest;
  114. if (typeof manifestParameter === "string") {
  115. const data =
  116. /** @type {CompilationDataItem} */
  117. (compilationData.get(params));
  118. // If there was an error parsing the manifest
  119. // file, exit now because the error will be added
  120. // as a compilation error in the "compilation" hook.
  121. if (data.error) {
  122. return;
  123. }
  124. manifest = data.data;
  125. } else {
  126. manifest = manifestParameter;
  127. }
  128. if (manifest) {
  129. if (!name) name = manifest.name;
  130. if (!sourceType) sourceType = manifest.type;
  131. if (!resolvedContent) resolvedContent = manifest.content;
  132. }
  133. }
  134. /** @type {Externals} */
  135. const externals = {};
  136. const source = `dll-reference ${name}`;
  137. externals[source] = /** @type {string} */ (name);
  138. const normalModuleFactory = params.normalModuleFactory;
  139. new ExternalModuleFactoryPlugin(sourceType || "var", externals).apply(
  140. normalModuleFactory
  141. );
  142. new DelegatedModuleFactoryPlugin({
  143. source,
  144. type: this.options.type,
  145. scope: this.options.scope,
  146. context: this.options.context || compiler.context,
  147. content:
  148. /** @type {DllReferencePluginOptionsContent} */
  149. (resolvedContent),
  150. extensions: this.options.extensions,
  151. associatedObjectForCache: compiler.root
  152. }).apply(normalModuleFactory);
  153. });
  154. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, params) => {
  155. if ("manifest" in this.options) {
  156. const manifest = this.options.manifest;
  157. if (typeof manifest === "string") {
  158. const data =
  159. /** @type {CompilationDataItem} */
  160. (compilationData.get(params));
  161. // If there was an error parsing the manifest file, add the
  162. // error as a compilation error to make the compilation fail.
  163. if (data.error) {
  164. compilation.errors.push(
  165. /** @type {DllManifestError} */ (data.error)
  166. );
  167. }
  168. compilation.fileDependencies.add(manifest);
  169. }
  170. }
  171. });
  172. }
  173. }
  174. class DllManifestError extends WebpackError {
  175. /**
  176. * Creates an instance of DllManifestError.
  177. * @param {string} filename filename of the manifest
  178. * @param {string} message error message
  179. */
  180. constructor(filename, message) {
  181. super();
  182. /** @type {string} */
  183. this.name = "DllManifestError";
  184. /** @type {string} */
  185. this.message = `Dll manifest ${filename}\n${message}`;
  186. }
  187. }
  188. module.exports = DllReferencePlugin;