NodeStuffPlugin.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const {
  7. JAVASCRIPT_MODULE_TYPE_AUTO,
  8. JAVASCRIPT_MODULE_TYPE_DYNAMIC
  9. } = require("./ModuleTypeConstants");
  10. const NodeStuffInWebError = require("./NodeStuffInWebError");
  11. const RuntimeGlobals = require("./RuntimeGlobals");
  12. const CachedConstDependency = require("./dependencies/CachedConstDependency");
  13. const ConstDependency = require("./dependencies/ConstDependency");
  14. const ExternalModuleDependency = require("./dependencies/ExternalModuleDependency");
  15. const {
  16. evaluateToString,
  17. expressionIsUnsupported
  18. } = require("./javascript/JavascriptParserHelpers");
  19. const { relative } = require("./util/fs");
  20. const { parseResource } = require("./util/identifier");
  21. /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
  22. /** @typedef {import("../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
  23. /** @typedef {import("../declarations/WebpackOptions").NodeOptions} NodeOptions */
  24. /** @typedef {import("./Compiler")} Compiler */
  25. /** @typedef {import("./Dependency")} Dependency */
  26. /** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
  27. /** @typedef {import("./DependencyTemplates")} DependencyTemplates */
  28. /** @typedef {import("./NormalModule")} NormalModule */
  29. /** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
  30. /** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
  31. /** @typedef {import("./javascript/JavascriptParser").Range} Range */
  32. const PLUGIN_NAME = "NodeStuffPlugin";
  33. class NodeStuffPlugin {
  34. /**
  35. * @param {NodeOptions} options options
  36. */
  37. constructor(options) {
  38. this.options = options;
  39. }
  40. /**
  41. * Apply the plugin
  42. * @param {Compiler} compiler the compiler instance
  43. * @returns {void}
  44. */
  45. apply(compiler) {
  46. const options = this.options;
  47. compiler.hooks.compilation.tap(
  48. PLUGIN_NAME,
  49. (compilation, { normalModuleFactory }) => {
  50. compilation.dependencyTemplates.set(
  51. ExternalModuleDependency,
  52. new ExternalModuleDependency.Template()
  53. );
  54. /**
  55. * @param {JavascriptParser} parser the parser
  56. * @param {JavascriptParserOptions} parserOptions options
  57. * @returns {void}
  58. */
  59. const handler = (parser, parserOptions) => {
  60. if (parserOptions.node === false) return;
  61. let localOptions = options;
  62. if (parserOptions.node) {
  63. localOptions = { ...localOptions, ...parserOptions.node };
  64. }
  65. if (localOptions.global !== false) {
  66. const withWarning = localOptions.global === "warn";
  67. parser.hooks.expression.for("global").tap(PLUGIN_NAME, expr => {
  68. const dep = new ConstDependency(
  69. RuntimeGlobals.global,
  70. /** @type {Range} */ (expr.range),
  71. [RuntimeGlobals.global]
  72. );
  73. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  74. parser.state.module.addPresentationalDependency(dep);
  75. // TODO webpack 6 remove
  76. if (withWarning) {
  77. parser.state.module.addWarning(
  78. new NodeStuffInWebError(
  79. dep.loc,
  80. "global",
  81. "The global namespace object is a Node.js feature and isn't available in browsers."
  82. )
  83. );
  84. }
  85. });
  86. parser.hooks.rename.for("global").tap(PLUGIN_NAME, expr => {
  87. const dep = new ConstDependency(
  88. RuntimeGlobals.global,
  89. /** @type {Range} */ (expr.range),
  90. [RuntimeGlobals.global]
  91. );
  92. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  93. parser.state.module.addPresentationalDependency(dep);
  94. return false;
  95. });
  96. }
  97. /**
  98. * @param {string} expressionName expression name
  99. * @param {(module: NormalModule) => string} fn function
  100. * @param {string=} warning warning
  101. * @returns {void}
  102. */
  103. const setModuleConstant = (expressionName, fn, warning) => {
  104. parser.hooks.expression
  105. .for(expressionName)
  106. .tap(PLUGIN_NAME, expr => {
  107. const dep = new CachedConstDependency(
  108. JSON.stringify(fn(parser.state.module)),
  109. /** @type {Range} */ (expr.range),
  110. expressionName
  111. );
  112. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  113. parser.state.module.addPresentationalDependency(dep);
  114. // TODO webpack 6 remove
  115. if (warning) {
  116. parser.state.module.addWarning(
  117. new NodeStuffInWebError(dep.loc, expressionName, warning)
  118. );
  119. }
  120. return true;
  121. });
  122. };
  123. /**
  124. * @param {string} expressionName expression name
  125. * @param {(value: string) => string} fn function
  126. * @returns {void}
  127. */
  128. const setUrlModuleConstant = (expressionName, fn) => {
  129. parser.hooks.expression
  130. .for(expressionName)
  131. .tap(PLUGIN_NAME, expr => {
  132. const dep = new ExternalModuleDependency(
  133. "url",
  134. [
  135. {
  136. name: "fileURLToPath",
  137. value: "__webpack_fileURLToPath__"
  138. }
  139. ],
  140. undefined,
  141. fn("__webpack_fileURLToPath__"),
  142. /** @type {Range} */ (expr.range),
  143. expressionName
  144. );
  145. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  146. parser.state.module.addPresentationalDependency(dep);
  147. return true;
  148. });
  149. };
  150. /**
  151. * @param {string} expressionName expression name
  152. * @param {string} value value
  153. * @param {string=} warning warning
  154. * @returns {void}
  155. */
  156. const setConstant = (expressionName, value, warning) =>
  157. setModuleConstant(expressionName, () => value, warning);
  158. const context = compiler.context;
  159. if (localOptions.__filename) {
  160. switch (localOptions.__filename) {
  161. case "mock":
  162. setConstant("__filename", "/index.js");
  163. break;
  164. case "warn-mock":
  165. setConstant(
  166. "__filename",
  167. "/index.js",
  168. "__filename is a Node.js feature and isn't available in browsers."
  169. );
  170. break;
  171. case "node-module":
  172. setUrlModuleConstant(
  173. "__filename",
  174. functionName => `${functionName}(import.meta.url)`
  175. );
  176. break;
  177. case true:
  178. setModuleConstant("__filename", module =>
  179. relative(compiler.inputFileSystem, context, module.resource)
  180. );
  181. break;
  182. }
  183. parser.hooks.evaluateIdentifier
  184. .for("__filename")
  185. .tap(PLUGIN_NAME, expr => {
  186. if (!parser.state.module) return;
  187. const resource = parseResource(parser.state.module.resource);
  188. return evaluateToString(resource.path)(expr);
  189. });
  190. }
  191. if (localOptions.__dirname) {
  192. switch (localOptions.__dirname) {
  193. case "mock":
  194. setConstant("__dirname", "/");
  195. break;
  196. case "warn-mock":
  197. setConstant(
  198. "__dirname",
  199. "/",
  200. "__dirname is a Node.js feature and isn't available in browsers."
  201. );
  202. break;
  203. case "node-module":
  204. setUrlModuleConstant(
  205. "__dirname",
  206. functionName =>
  207. `${functionName}(import.meta.url + "/..").slice(0, -1)`
  208. );
  209. break;
  210. case true:
  211. setModuleConstant("__dirname", module =>
  212. relative(compiler.inputFileSystem, context, module.context)
  213. );
  214. break;
  215. }
  216. parser.hooks.evaluateIdentifier
  217. .for("__dirname")
  218. .tap(PLUGIN_NAME, expr => {
  219. if (!parser.state.module) return;
  220. return evaluateToString(parser.state.module.context)(expr);
  221. });
  222. }
  223. parser.hooks.expression
  224. .for("require.extensions")
  225. .tap(
  226. PLUGIN_NAME,
  227. expressionIsUnsupported(
  228. parser,
  229. "require.extensions is not supported by webpack. Use a loader instead."
  230. )
  231. );
  232. };
  233. normalModuleFactory.hooks.parser
  234. .for(JAVASCRIPT_MODULE_TYPE_AUTO)
  235. .tap(PLUGIN_NAME, handler);
  236. normalModuleFactory.hooks.parser
  237. .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
  238. .tap(PLUGIN_NAME, handler);
  239. }
  240. );
  241. }
  242. }
  243. module.exports = NodeStuffPlugin;