HarmonyDetectionParserPlugin.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const EnvironmentNotSupportAsyncWarning = require("../EnvironmentNotSupportAsyncWarning");
  7. const { JAVASCRIPT_MODULE_TYPE_ESM } = require("../ModuleTypeConstants");
  8. const DynamicExports = require("./DynamicExports");
  9. const HarmonyCompatibilityDependency = require("./HarmonyCompatibilityDependency");
  10. const HarmonyExports = require("./HarmonyExports");
  11. /** @typedef {import("../Module").BuildMeta} BuildMeta */
  12. /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
  13. /** @typedef {import("./HarmonyModulesPlugin").HarmonyModulesPluginOptions} HarmonyModulesPluginOptions */
  14. const PLUGIN_NAME = "HarmonyDetectionParserPlugin";
  15. module.exports = class HarmonyDetectionParserPlugin {
  16. /**
  17. * @param {HarmonyModulesPluginOptions} options options
  18. */
  19. constructor(options) {
  20. const { topLevelAwait = false } = options || {};
  21. this.topLevelAwait = topLevelAwait;
  22. }
  23. /**
  24. * @param {JavascriptParser} parser the parser
  25. * @returns {void}
  26. */
  27. apply(parser) {
  28. parser.hooks.program.tap(PLUGIN_NAME, ast => {
  29. const isStrictHarmony =
  30. parser.state.module.type === JAVASCRIPT_MODULE_TYPE_ESM;
  31. const isHarmony =
  32. isStrictHarmony ||
  33. ast.body.some(
  34. statement =>
  35. statement.type === "ImportDeclaration" ||
  36. statement.type === "ExportDefaultDeclaration" ||
  37. statement.type === "ExportNamedDeclaration" ||
  38. statement.type === "ExportAllDeclaration"
  39. );
  40. if (isHarmony) {
  41. const module = parser.state.module;
  42. const compatDep = new HarmonyCompatibilityDependency();
  43. compatDep.loc = {
  44. start: {
  45. line: -1,
  46. column: 0
  47. },
  48. end: {
  49. line: -1,
  50. column: 0
  51. },
  52. index: -3
  53. };
  54. module.addPresentationalDependency(compatDep);
  55. DynamicExports.bailout(parser.state);
  56. HarmonyExports.enable(parser.state, isStrictHarmony);
  57. parser.scope.isStrict = true;
  58. }
  59. });
  60. parser.hooks.topLevelAwait.tap(PLUGIN_NAME, () => {
  61. const module = parser.state.module;
  62. if (!this.topLevelAwait) {
  63. throw new Error(
  64. "The top-level-await experiment is not enabled (set experiments.topLevelAwait: true to enable it)"
  65. );
  66. }
  67. if (!HarmonyExports.isEnabled(parser.state)) {
  68. throw new Error(
  69. "Top-level-await is only supported in EcmaScript Modules"
  70. );
  71. }
  72. /** @type {BuildMeta} */
  73. (module.buildMeta).async = true;
  74. EnvironmentNotSupportAsyncWarning.check(
  75. module,
  76. parser.state.compilation.runtimeTemplate,
  77. "topLevelAwait"
  78. );
  79. });
  80. /**
  81. * @returns {boolean | undefined} true if in harmony
  82. */
  83. const skipInHarmony = () => {
  84. if (HarmonyExports.isEnabled(parser.state)) {
  85. return true;
  86. }
  87. };
  88. /**
  89. * @returns {null | undefined} null if in harmony
  90. */
  91. const nullInHarmony = () => {
  92. if (HarmonyExports.isEnabled(parser.state)) {
  93. return null;
  94. }
  95. };
  96. const nonHarmonyIdentifiers = ["define", "exports"];
  97. for (const identifier of nonHarmonyIdentifiers) {
  98. parser.hooks.evaluateTypeof
  99. .for(identifier)
  100. .tap(PLUGIN_NAME, nullInHarmony);
  101. parser.hooks.typeof.for(identifier).tap(PLUGIN_NAME, skipInHarmony);
  102. parser.hooks.evaluate.for(identifier).tap(PLUGIN_NAME, nullInHarmony);
  103. parser.hooks.expression.for(identifier).tap(PLUGIN_NAME, skipInHarmony);
  104. parser.hooks.call.for(identifier).tap(PLUGIN_NAME, skipInHarmony);
  105. }
  106. }
  107. };