提交学习笔记专用
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.

279 lines
8.1 KiB

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