NodeStuffPlugin.js 8.5 KB

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