product-isolation.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. /** Vite adapters for recording product chunk, asset, CSS, and worker inputs. */
  2. import type { Plugin } from 'vite'
  3. import { WebProductBundleIsolation } from '../../scripts/web-product-bundle-isolation.ts'
  4. const DEPENDENCY_ANALYSIS_PLUGIN = 'dsh-browser-dependency-analysis'
  5. /**
  6. * Identify a dependency-disclosure walk that deliberately leaves third-party imports external.
  7. * @returns Analysis intent that refuses builds which can write output.
  8. */
  9. export function browserDependencyAnalysis(): Plugin {
  10. return {
  11. name: DEPENDENCY_ANALYSIS_PLUGIN,
  12. apply: 'build',
  13. configResolved(config) { requireNonWritingAnalysis(config.build.write) },
  14. }
  15. }
  16. function requireNonWritingAnalysis(write: boolean): void {
  17. if (write !== false) throw new Error('browser dependency analysis requires build.write: false')
  18. }
  19. /**
  20. * Reject experimental inputs reachable from the built default Web page.
  21. * @param repository - repository root supplying package ownership.
  22. * @param webRoot - Vite root containing index.html and public assets.
  23. * @returns Build plugins that preserve Vite's output and fail before publication on a violation.
  24. */
  25. export function productWebBundleIsolation(repository: string, webRoot: string): Plugin[] {
  26. const inputs = new WebProductBundleIsolation(repository, webRoot)
  27. let dependencyAnalysis = false
  28. return [{
  29. name: 'dsh-product-web-chunk-inputs',
  30. apply: 'build',
  31. generateBundle: {
  32. order: 'pre',
  33. handler(_options, bundle) {
  34. if (!dependencyAnalysis) inputs.captureChunks(bundle)
  35. },
  36. },
  37. }, {
  38. name: 'dsh-product-web-bundle-isolation',
  39. apply: 'build',
  40. enforce: 'post',
  41. config(config) {
  42. const renderBuiltUrl = config.experimental?.renderBuiltUrl
  43. const workerPlugins = config.worker?.plugins
  44. return {
  45. experimental: {
  46. renderBuiltUrl(filename, context) {
  47. inputs.assetReference(filename, context.hostId, context.type)
  48. return renderBuiltUrl?.(filename, context)
  49. },
  50. },
  51. worker: {
  52. plugins: () => [...workerPlugins?.() ?? [], {
  53. name: 'dsh-worker-build-inputs',
  54. generateBundle: {
  55. order: 'post',
  56. handler(_options, bundle) { inputs.workerBundle(bundle, this.getWatchFiles()) },
  57. },
  58. }],
  59. },
  60. }
  61. },
  62. configResolved(config) {
  63. dependencyAnalysis = config.plugins.some(plugin => plugin.name === DEPENDENCY_ANALYSIS_PLUGIN)
  64. if (dependencyAnalysis) {
  65. requireNonWritingAnalysis(config.build.write)
  66. return
  67. }
  68. const cssPlugins = config.plugins.filter(plugin => plugin.name === 'vite:css')
  69. const css = cssPlugins[0]
  70. if (cssPlugins.length !== 1 || css?.transform === undefined) {
  71. throw new Error('Web product isolation: Vite CSS input instrumentation is unavailable')
  72. }
  73. const transform = typeof css.transform === 'function' ? css.transform : css.transform.handler
  74. css.transform = {
  75. ...typeof css.transform === 'function' ? {} : css.transform,
  76. handler(code, id, options) {
  77. inputs.cssTransform(id)
  78. const context = new Proxy(this, {
  79. get(target, property) {
  80. if (property === 'addWatchFile') return (file: string): void => {
  81. inputs.cssDependency(id, file)
  82. target.addWatchFile(file)
  83. }
  84. const value: unknown = Reflect.get(target, property, target)
  85. return typeof value === 'function' ? value.bind(target) : value
  86. },
  87. })
  88. return transform.call(context, code, id, options)
  89. },
  90. }
  91. },
  92. buildStart() { inputs.reset() },
  93. generateBundle: {
  94. order: 'post',
  95. handler(_options, bundle) {
  96. if (!dependencyAnalysis) inputs.verify(bundle, id => this.getModuleInfo(id))
  97. },
  98. },
  99. }]
  100. }