HarmonyExportExpressionDependency.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const ConcatenationScope = require("../ConcatenationScope");
  7. const Dependency = require("../Dependency");
  8. const InitFragment = require("../InitFragment");
  9. const RuntimeGlobals = require("../RuntimeGlobals");
  10. const { InlinedUsedName } = require("../optimize/InlineExports");
  11. const makeSerializable = require("../util/makeSerializable");
  12. const { propertyAccess } = require("../util/property");
  13. const ExportBindingInitFragment = require("./ExportBindingInitFragment");
  14. const HarmonyExportInitFragment = require("./HarmonyExportInitFragment");
  15. const NullDependency = require("./NullDependency");
  16. const DEFAULT_EXPORT_NAME = "default";
  17. /** @import { ReplaceSource } from "webpack-sources" */
  18. /** @import { ExportsSpec, LazyUntil } from "../Dependency" */
  19. /** @import { DependencyTemplateContext } from "../DependencyTemplate" */
  20. /** @import ModuleGraph from "../ModuleGraph" */
  21. /** @import { ConnectionState } from "../ModuleGraphConnection" */
  22. /** @import { InlinedValue } from "../optimize/InlineExports" */
  23. /** @import { Range } from "../javascript/JavascriptParser" */
  24. /** @typedef {string | { id?: string | undefined, range: Range, prefix: string, suffix: string }} DeclarationId */
  25. /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[Range, Range, string, DeclarationId | undefined, boolean]>} ObjectDeserializerContext */
  26. /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[Range, Range, string, DeclarationId | undefined, boolean]>} ObjectSerializerContext */
  27. /** @import { ExportMap } from "./HarmonyExportInitFragment" */
  28. /**
  29. * @import {
  30. * JavascriptModuleBuildInfo
  31. * } from "../javascript/JavascriptModule"
  32. */
  33. class HarmonyExportExpressionDependency extends NullDependency {
  34. /**
  35. * Creates an instance of HarmonyExportExpressionDependency.
  36. * @param {Range} range range
  37. * @param {Range} rangeStatement range statement
  38. * @param {string} prefix prefix
  39. * @param {string | { id?: string | undefined, range: Range, prefix: string, suffix: string }=} declarationId declaration id
  40. * @param {InlinedValue=} constValue inlined primitive when `export default <const>` is inline-eligible
  41. */
  42. constructor(range, rangeStatement, prefix, declarationId, constValue) {
  43. super();
  44. this.range = range;
  45. this.rangeStatement = rangeStatement;
  46. /** @type {string} */
  47. this.prefix = prefix;
  48. this.declarationId = declarationId;
  49. this.constValue = constValue;
  50. /** @type {boolean} */
  51. this.isAnonymousDefault = false;
  52. }
  53. get type() {
  54. return "harmony export expression";
  55. }
  56. /**
  57. * Returns how this dependency may be deferred when its parent module is side-effect-free (lazy barrel optimization).
  58. * @returns {LazyUntil | null} lazy classification, null when it must be processed eagerly
  59. */
  60. getLazyUntil() {
  61. return Dependency.LAZY_UNTIL_LOCAL;
  62. }
  63. /**
  64. * Returns the export name for a `LAZY_UNTIL_LOCAL`/`LAZY_UNTIL_ID` classification (lazy barrel optimization).
  65. * @returns {string | null} export name, null when not applicable
  66. */
  67. getLazyName() {
  68. return DEFAULT_EXPORT_NAME;
  69. }
  70. /**
  71. * Returns the exported names
  72. * @param {ModuleGraph} moduleGraph module graph
  73. * @returns {ExportsSpec | undefined} export names
  74. */
  75. getExports(moduleGraph) {
  76. const module = moduleGraph.getParentModule(this);
  77. const pureFunctions =
  78. module &&
  79. module.buildInfo &&
  80. /** @type {JavascriptModuleBuildInfo} */ (module.buildInfo).pureFunctions;
  81. const isPure = Boolean(
  82. pureFunctions && pureFunctions.has(DEFAULT_EXPORT_NAME)
  83. );
  84. return {
  85. exports: [
  86. {
  87. name: DEFAULT_EXPORT_NAME,
  88. isPure: isPure || undefined,
  89. inlined: this.constValue
  90. }
  91. ],
  92. priority: 1,
  93. terminalBinding: true,
  94. dependencies: undefined
  95. };
  96. }
  97. /**
  98. * Gets module evaluation side effects state.
  99. * @param {ModuleGraph} moduleGraph the module graph
  100. * @returns {ConnectionState} how this dependency connects the module to referencing modules
  101. */
  102. getModuleEvaluationSideEffectsState(moduleGraph) {
  103. // The expression/declaration is already covered by SideEffectsFlagPlugin
  104. return false;
  105. }
  106. /**
  107. * Serializes this instance into the provided serializer context.
  108. * @param {ObjectSerializerContext} context context
  109. */
  110. serialize(context) {
  111. context
  112. .write(this.range)
  113. .write(this.rangeStatement)
  114. .write(this.prefix)
  115. .write(this.declarationId)
  116. .write(this.isAnonymousDefault)
  117. .write(this.constValue);
  118. super.serialize(context);
  119. }
  120. /**
  121. * Restores this instance from the provided deserializer context.
  122. * @param {ObjectDeserializerContext} context context
  123. */
  124. deserialize(context) {
  125. this.range = context.read();
  126. const c1 = context.rest;
  127. this.rangeStatement = c1.read();
  128. const c2 = c1.rest;
  129. this.prefix = c2.read();
  130. const c3 = c2.rest;
  131. this.declarationId = c3.read();
  132. const c4 = c3.rest;
  133. this.isAnonymousDefault = c4.read();
  134. const c5 = c4.rest;
  135. this.constValue = c5.read();
  136. super.deserialize(c5.rest);
  137. }
  138. }
  139. makeSerializable(
  140. HarmonyExportExpressionDependency,
  141. "webpack/lib/dependencies/HarmonyExportExpressionDependency"
  142. );
  143. HarmonyExportExpressionDependency.Template = class HarmonyExportDependencyTemplate extends (
  144. NullDependency.Template
  145. ) {
  146. /**
  147. * Applies the plugin by registering its hooks on the compiler.
  148. * @param {Dependency} dependency the dependency for which the template should be applied
  149. * @param {ReplaceSource} source the current replace source which can be modified
  150. * @param {DependencyTemplateContext} templateContext the context object
  151. * @returns {void}
  152. */
  153. apply(dependency, source, templateContext) {
  154. const {
  155. module,
  156. moduleGraph,
  157. runtimeTemplate,
  158. runtimeRequirements,
  159. initFragments,
  160. runtime
  161. } = templateContext;
  162. // a wrapped module's default binding is unreachable from the shared scope
  163. const concatenationScope =
  164. templateContext.concatenationScope &&
  165. !templateContext.concatenationScope.isWrapped()
  166. ? templateContext.concatenationScope
  167. : undefined;
  168. const dep = /** @type {HarmonyExportExpressionDependency} */ (dependency);
  169. const { declarationId } = dep;
  170. const exportsName = module.exportsArgument;
  171. if (declarationId) {
  172. /** @type {string} */
  173. let name;
  174. if (typeof declarationId === "string") {
  175. name = declarationId;
  176. } else {
  177. name = ConcatenationScope.DEFAULT_EXPORT;
  178. source.replace(
  179. declarationId.range[0],
  180. declarationId.range[1] - 1,
  181. `${declarationId.prefix}${name}${declarationId.suffix}`
  182. );
  183. }
  184. const used = concatenationScope
  185. ? undefined
  186. : moduleGraph
  187. .getExportsInfo(module)
  188. .getUsedName(DEFAULT_EXPORT_NAME, runtime);
  189. if (concatenationScope) {
  190. concatenationScope.registerExport(DEFAULT_EXPORT_NAME, name);
  191. } else if (used) {
  192. /** @type {ExportMap} */
  193. const map = new Map();
  194. map.set(used, `/* export default binding */ ${name}`);
  195. initFragments.push(new HarmonyExportInitFragment(exportsName, map));
  196. }
  197. source.replace(
  198. dep.rangeStatement[0],
  199. dep.range[0] - 1,
  200. `/* harmony default export */ ${dep.prefix}`
  201. );
  202. if (
  203. typeof declarationId !== "string" &&
  204. dep.isAnonymousDefault &&
  205. (concatenationScope || used)
  206. ) {
  207. // Fix .name for anonymous default export function declarations
  208. // see test/test262-cases/test/language/module-code/instn-named-bndng-dflt-fun-anon.js cspell:disable-line
  209. runtimeRequirements.add(RuntimeGlobals.setAnonymousDefaultName);
  210. initFragments.push(
  211. new InitFragment(
  212. `${RuntimeGlobals.setAnonymousDefaultName}(${name});\n`,
  213. InitFragment.STAGE_HARMONY_EXPORTS,
  214. 2
  215. )
  216. );
  217. }
  218. } else {
  219. /** @type {string} */
  220. let content;
  221. let name = ConcatenationScope.DEFAULT_EXPORT;
  222. let defaultIsUsed = Boolean(concatenationScope);
  223. if (runtimeTemplate.supportsConst()) {
  224. content = `/* harmony default export */ const ${name} = `;
  225. if (concatenationScope) {
  226. concatenationScope.registerExport(DEFAULT_EXPORT_NAME, name);
  227. } else {
  228. const used = moduleGraph
  229. .getExportsInfo(module)
  230. .getUsedName(DEFAULT_EXPORT_NAME, runtime);
  231. if (used instanceof InlinedUsedName) {
  232. content = `/* inlined harmony default export */ const ${name} = `;
  233. } else if (used) {
  234. defaultIsUsed = true;
  235. runtimeRequirements.add(RuntimeGlobals.exports);
  236. // `export default <expr>` binds an immutable const declaration
  237. const canUseValueBinding =
  238. /** @type {import("../Module").BuildInfo} */ (module.buildInfo)
  239. .isCircular === false;
  240. if (canUseValueBinding) {
  241. initFragments.push(
  242. new ExportBindingInitFragment(
  243. exportsName,
  244. [
  245. {
  246. name: used,
  247. value: `/* export default binding */ ${name}`,
  248. bindingType: "value"
  249. }
  250. ],
  251. true
  252. )
  253. );
  254. } else {
  255. /** @type {ExportMap} */
  256. const map = new Map();
  257. map.set(used, name);
  258. initFragments.push(
  259. new HarmonyExportInitFragment(exportsName, map)
  260. );
  261. }
  262. } else {
  263. content = `/* unused harmony default export */ var ${name} = `;
  264. }
  265. }
  266. } else if (concatenationScope) {
  267. content = `/* harmony default export */ var ${name} = `;
  268. concatenationScope.registerExport(DEFAULT_EXPORT_NAME, name);
  269. } else {
  270. const used = moduleGraph
  271. .getExportsInfo(module)
  272. .getUsedName(DEFAULT_EXPORT_NAME, runtime);
  273. if (used instanceof InlinedUsedName) {
  274. content = `/* inlined harmony default export */ var ${name} = `;
  275. } else if (used) {
  276. defaultIsUsed = true;
  277. runtimeRequirements.add(RuntimeGlobals.exports);
  278. // This is a little bit incorrect as TDZ is not correct, but we can't use const.
  279. // No local `__WEBPACK_DEFAULT_EXPORT__` binding is created in this path,
  280. // so the anonymous-default `.name` fix-up below must reference the actual
  281. // assignment target instead. See issue #20793.
  282. name = `${exportsName}${propertyAccess(
  283. typeof used === "string" ? [used] : used
  284. )}`;
  285. content = `/* harmony default export */ ${name} = `;
  286. } else {
  287. content = `/* unused harmony default export */ var ${name} = `;
  288. }
  289. }
  290. if (dep.range) {
  291. source.replace(
  292. dep.rangeStatement[0],
  293. dep.range[0] - 1,
  294. `${content}(${dep.prefix}`
  295. );
  296. if (dep.isAnonymousDefault && defaultIsUsed) {
  297. // Fix .name for anonymous default export expressions
  298. // see test/test262-cases/test/language/module-code/eval-export-dflt-cls-anon.js cspell:disable-line
  299. runtimeRequirements.add(RuntimeGlobals.setAnonymousDefaultName);
  300. source.replace(
  301. dep.range[1],
  302. dep.rangeStatement[1] - 0.5,
  303. `);\n${RuntimeGlobals.setAnonymousDefaultName}(${name});`
  304. );
  305. } else {
  306. source.replace(dep.range[1], dep.rangeStatement[1] - 0.5, ");");
  307. }
  308. return;
  309. }
  310. source.replace(dep.rangeStatement[0], dep.rangeStatement[1] - 1, content);
  311. }
  312. }
  313. };
  314. module.exports = HarmonyExportExpressionDependency;