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