no-unwanted-polyfillio.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. // Keep in sync with next.js polyfills file : https://github.com/vercel/next.js/blob/master/packages/next-polyfill-nomodule/src/index.js
  2. const NEXT_POLYFILLED_FEATURES = [
  3. 'Array.prototype.@@iterator',
  4. 'Array.prototype.copyWithin',
  5. 'Array.prototype.fill',
  6. 'Array.prototype.find',
  7. 'Array.prototype.findIndex',
  8. 'Array.prototype.flatMap',
  9. 'Array.prototype.flat',
  10. 'Array.from',
  11. 'Array.prototype.includes',
  12. 'Array.of',
  13. 'Function.prototype.name',
  14. 'fetch',
  15. 'Map',
  16. 'Number.EPSILON',
  17. 'Number.Epsilon',
  18. 'Number.isFinite',
  19. 'Number.isNaN',
  20. 'Number.isInteger',
  21. 'Number.isSafeInteger',
  22. 'Number.MAX_SAFE_INTEGER',
  23. 'Number.MIN_SAFE_INTEGER',
  24. 'Number.parseFloat',
  25. 'Number.parseInt',
  26. 'Object.assign',
  27. 'Object.entries',
  28. 'Object.fromEntries',
  29. 'Object.getOwnPropertyDescriptor',
  30. 'Object.getOwnPropertyDescriptors',
  31. 'Object.is',
  32. 'Object.keys',
  33. 'Object.values',
  34. 'Reflect',
  35. 'Set',
  36. 'Symbol',
  37. 'Symbol.asyncIterator',
  38. 'String.prototype.codePointAt',
  39. 'String.prototype.endsWith',
  40. 'String.fromCodePoint',
  41. 'String.prototype.includes',
  42. 'String.prototype.@@iterator',
  43. 'String.prototype.padEnd',
  44. 'String.prototype.padStart',
  45. 'String.prototype.repeat',
  46. 'String.raw',
  47. 'String.prototype.startsWith',
  48. 'String.prototype.trimEnd',
  49. 'String.prototype.trimStart',
  50. 'URL',
  51. 'URL.prototype.toJSON',
  52. 'URLSearchParams',
  53. 'WeakMap',
  54. 'WeakSet',
  55. 'Promise',
  56. 'Promise.prototype.finally',
  57. 'es2015', // Should be covered by babel-preset-env instead.
  58. 'es2016', // contains polyfilled 'Array.prototype.includes', 'String.prototype.padEnd' and 'String.prototype.padStart'
  59. 'es2017', // contains polyfilled 'Object.entries', 'Object.getOwnPropertyDescriptors', 'Object.values', 'String.prototype.padEnd' and 'String.prototype.padStart'
  60. 'es2018', // contains polyfilled 'Promise.prototype.finally' and ''Symbol.asyncIterator'
  61. 'es2019', // Contains polyfilled 'Object.fromEntries' and polyfilled 'Array.prototype.flat', 'Array.prototype.flatMap', 'String.prototype.trimEnd' and 'String.prototype.trimStart'
  62. 'es5', // Should be covered by babel-preset-env instead.
  63. 'es6', // Should be covered by babel-preset-env instead.
  64. 'es7', // contains polyfilled 'Array.prototype.includes', 'String.prototype.padEnd' and 'String.prototype.padStart'
  65. ]
  66. const url = 'https://nextjs.org/docs/messages/no-unwanted-polyfillio'
  67. //------------------------------------------------------------------------------
  68. // Rule Definition
  69. //------------------------------------------------------------------------------
  70. module.exports = {
  71. meta: {
  72. docs: {
  73. description: 'Prevent duplicate polyfills from Polyfill.io.',
  74. category: 'HTML',
  75. recommended: true,
  76. url,
  77. },
  78. type: 'problem',
  79. schema: [],
  80. },
  81. create: function (context) {
  82. let scriptImport = null
  83. return {
  84. ImportDeclaration(node) {
  85. if (node.source && node.source.value === 'next/script') {
  86. scriptImport = node.specifiers[0].local.name
  87. }
  88. },
  89. JSXOpeningElement(node) {
  90. if (
  91. node.name &&
  92. node.name.name !== 'script' &&
  93. node.name.name !== scriptImport
  94. ) {
  95. return
  96. }
  97. if (node.attributes.length === 0) {
  98. return
  99. }
  100. const srcNode = node.attributes.find(
  101. (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'src'
  102. )
  103. if (!srcNode || srcNode.value.type !== 'Literal') {
  104. return
  105. }
  106. const src = srcNode.value.value
  107. if (
  108. src.startsWith('https://cdn.polyfill.io/v2/') ||
  109. src.startsWith('https://polyfill.io/v3/')
  110. ) {
  111. const featureQueryString = new URL(src).searchParams.get('features')
  112. const featuresRequested = (featureQueryString || '').split(',')
  113. const unwantedFeatures = featuresRequested.filter((feature) =>
  114. NEXT_POLYFILLED_FEATURES.includes(feature)
  115. )
  116. if (unwantedFeatures.length > 0) {
  117. context.report({
  118. node,
  119. message: `No duplicate polyfills from Polyfill.io are allowed. ${unwantedFeatures.join(
  120. ', '
  121. )} ${
  122. unwantedFeatures.length > 1 ? 'are' : 'is'
  123. } already shipped with Next.js. See: ${url}`,
  124. })
  125. }
  126. }
  127. },
  128. }
  129. },
  130. }