local-compile-context.tsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. import {
  2. FC,
  3. createContext,
  4. useCallback,
  5. useContext,
  6. useEffect,
  7. useMemo,
  8. useRef,
  9. useState,
  10. } from 'react'
  11. import useScopeValue from '../hooks/use-scope-value'
  12. import useScopeValueSetterOnly from '../hooks/use-scope-value-setter-only'
  13. import usePersistedState from '../hooks/use-persisted-state'
  14. import useAbortController from '../hooks/use-abort-controller'
  15. import DocumentCompiler from '../../features/pdf-preview/util/compiler'
  16. import {
  17. send,
  18. sendMB,
  19. sendMBOnce,
  20. sendMBSampled,
  21. } from '../../infrastructure/event-tracking'
  22. import {
  23. buildLogEntryAnnotations,
  24. buildRuleCounts,
  25. handleLogFiles,
  26. handleOutputFiles,
  27. } from '../../features/pdf-preview/util/output-files'
  28. import { useIdeContext } from './ide-context'
  29. import { useProjectContext } from './project-context'
  30. import { useEditorContext } from './editor-context'
  31. import { buildFileList } from '../../features/pdf-preview/util/file-list'
  32. import { useLayoutContext } from './layout-context'
  33. import { useUserContext } from './user-context'
  34. import { useFileTreeData } from '@/shared/context/file-tree-data-context'
  35. import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
  36. import { useUserSettingsContext } from '@/shared/context/user-settings-context'
  37. import { useFeatureFlag } from '@/shared/context/split-test-context'
  38. import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
  39. type PdfFile = Record<string, any>
  40. export type CompileContext = {
  41. autoCompile: boolean
  42. clearingCache: boolean
  43. clsiServerId?: string
  44. codeCheckFailed: boolean
  45. compiling: boolean
  46. deliveryLatencies: Record<string, any>
  47. draft: boolean
  48. error?: string
  49. fileList?: Record<string, any>
  50. hasChanges: boolean
  51. hasShortCompileTimeout: boolean
  52. highlights?: Record<string, any>[]
  53. isProjectOwner: boolean
  54. logEntries?: Record<string, any>
  55. logEntryAnnotations?: Record<string, any>
  56. outputFilesArchive?: string
  57. pdfDownloadUrl?: string
  58. pdfFile?: PdfFile
  59. pdfUrl?: string
  60. pdfViewer?: string
  61. position?: Record<string, any>
  62. rawLog?: string
  63. setAutoCompile: (value: boolean) => void
  64. setDraft: (value: any) => void
  65. setError: (value: any) => void
  66. setHasLintingError: (value: any) => void // only for storybook
  67. setHighlights: (value: any) => void
  68. setPosition: (value: any) => void
  69. setShowCompileTimeWarning: (value: any) => void
  70. setShowLogs: (value: boolean) => void
  71. toggleLogs: () => void
  72. setStopOnFirstError: (value: boolean) => void
  73. setStopOnValidationError: (value: boolean) => void
  74. showCompileTimeWarning: boolean
  75. showLogs: boolean
  76. stopOnFirstError: boolean
  77. stopOnValidationError: boolean
  78. stoppedOnFirstError: boolean
  79. uncompiled?: boolean
  80. validationIssues?: Record<string, any>
  81. firstRenderDone: (metrics: {
  82. latencyFetch: number
  83. latencyRender: number | undefined
  84. pdfCachingMetrics: { viewerId: string }
  85. }) => void
  86. cleanupCompileResult?: () => void
  87. animateCompileDropdownArrow: boolean
  88. editedSinceCompileStarted: boolean
  89. lastCompileOptions: any
  90. setAnimateCompileDropdownArrow: (value: boolean) => void
  91. recompileFromScratch: () => void
  92. setCompiling: (value: boolean) => void
  93. startCompile: (options?: any) => void
  94. stopCompile: () => void
  95. setChangedAt: (value: any) => void
  96. clearCache: () => void
  97. syncToEntry: (value: any) => void
  98. }
  99. export const LocalCompileContext = createContext<CompileContext | undefined>(
  100. undefined
  101. )
  102. export const LocalCompileProvider: FC = ({ children }) => {
  103. const ide = useIdeContext()
  104. const { hasPremiumCompile, isProjectOwner } = useEditorContext()
  105. const { openDocId } = useEditorManagerContext()
  106. const { _id: projectId, rootDocId } = useProjectContext()
  107. const { pdfPreviewOpen } = useLayoutContext()
  108. const { features, alphaProgram } = useUserContext()
  109. const { fileTreeData } = useFileTreeData()
  110. const { findEntityByPath } = useFileTreePathContext()
  111. // whether a compile is in progress
  112. const [compiling, setCompiling] = useState(false)
  113. // whether to show the compile time warning
  114. const [showCompileTimeWarning, setShowCompileTimeWarning] = useState(false)
  115. const [hasShortCompileTimeout, setHasShortCompileTimeout] = useState(false)
  116. // the log entries parsed from the compile output log
  117. const [logEntries, setLogEntries] = useScopeValueSetterOnly('pdf.logEntries')
  118. // annotations for display in the editor, built from the log entries
  119. const [logEntryAnnotations, setLogEntryAnnotations] = useScopeValue(
  120. 'pdf.logEntryAnnotations'
  121. )
  122. // the PDF viewer and whether syntax validation is enabled globally
  123. const { userSettings } = useUserSettingsContext()
  124. const { pdfViewer, syntaxValidation } = userSettings
  125. // the URL for downloading the PDF
  126. const [, setPdfDownloadUrl] =
  127. useScopeValueSetterOnly<string>('pdf.downloadUrl')
  128. // the URL for loading the PDF in the preview pane
  129. const [, setPdfUrl] = useScopeValueSetterOnly<string>('pdf.url')
  130. // low level details for metrics
  131. const [pdfFile, setPdfFile] = useState<PdfFile | undefined>()
  132. useEffect(() => {
  133. setPdfDownloadUrl(pdfFile?.pdfDownloadUrl)
  134. setPdfUrl(pdfFile?.pdfUrl)
  135. }, [pdfFile, setPdfDownloadUrl, setPdfUrl])
  136. // the project is considered to be "uncompiled" if a doc has changed, or finished saving, since the last compile started.
  137. const [uncompiled, setUncompiled] = useScopeValue('pdf.uncompiled')
  138. // whether a doc has been edited since the last compile started
  139. const [editedSinceCompileStarted, setEditedSinceCompileStarted] =
  140. useState(false)
  141. // the id of the CLSI server which ran the compile
  142. const [clsiServerId, setClsiServerId] = useState()
  143. // data received in response to a compile request
  144. const [data, setData] = useState<Record<string, any>>()
  145. // the rootDocId used in the most recent compile request, which may not be the
  146. // same as the project rootDocId. This is used to calculate correct paths when
  147. // parsing the compile logs
  148. const lastCompileRootDocId = data?.rootDocId
  149. // callback to be invoked for PdfJsMetrics
  150. const [firstRenderDone, setFirstRenderDone] = useState(() => () => {})
  151. // latencies of compile/pdf download/rendering
  152. const [deliveryLatencies, setDeliveryLatencies] = useState({})
  153. // whether the project has been compiled yet
  154. const [compiledOnce, setCompiledOnce] = useState(false)
  155. // whether the cache is being cleared
  156. const [clearingCache, setClearingCache] = useState(false)
  157. // whether the logs should be visible
  158. const [showLogs, setShowLogs] = useState(false)
  159. // whether the compile dropdown arrow should be animated
  160. const [animateCompileDropdownArrow, setAnimateCompileDropdownArrow] =
  161. useState(false)
  162. const toggleLogs = useCallback(() => {
  163. setShowLogs(prev => {
  164. if (!prev) {
  165. sendMBOnce('ide-open-logs-once')
  166. }
  167. return !prev
  168. })
  169. }, [setShowLogs])
  170. // an error that occurred
  171. const [error, setError] = useState<string>()
  172. // the list of files that can be downloaded
  173. const [fileList, setFileList] = useState<Record<string, any[]>>()
  174. // the raw contents of the log file
  175. const [rawLog, setRawLog] = useState<string>()
  176. // validation issues from CLSI
  177. const [validationIssues, setValidationIssues] = useState()
  178. // areas to highlight on the PDF, from synctex
  179. const [highlights, setHighlights] = useState()
  180. // scroll position of the PDF
  181. const [position, setPosition] = usePersistedState(`pdf.position.${projectId}`)
  182. // whether autocompile is switched on
  183. const [autoCompile, setAutoCompile] = usePersistedState(
  184. `autocompile_enabled:${projectId}`,
  185. false,
  186. true
  187. )
  188. // whether the compile should run in draft mode
  189. const [draft, setDraft] = usePersistedState(`draft:${projectId}`, false, true)
  190. // whether compiling should stop on first error
  191. const [stopOnFirstError, setStopOnFirstError] = usePersistedState(
  192. `stop_on_first_error:${projectId}`,
  193. false,
  194. true
  195. )
  196. // whether the last compiles stopped on first error
  197. const [stoppedOnFirstError, setStoppedOnFirstError] = useState(false)
  198. // whether compiling should be prevented if there are linting errors
  199. const [stopOnValidationError, setStopOnValidationError] = usePersistedState(
  200. `stop_on_validation_error:${projectId}`,
  201. true,
  202. true
  203. )
  204. // the Document currently open in the editor
  205. const [currentDoc] = useScopeValue('editor.sharejs_doc')
  206. // whether the editor linter found errors
  207. const [hasLintingError, setHasLintingError] = useScopeValue('hasLintingError')
  208. // the timestamp that a doc was last changed
  209. const [changedAt, setChangedAt] = useState(0)
  210. const { signal } = useAbortController()
  211. const cleanupCompileResult = useCallback(() => {
  212. setPdfFile(undefined)
  213. setLogEntries(null)
  214. setLogEntryAnnotations({})
  215. }, [setPdfFile, setLogEntries, setLogEntryAnnotations])
  216. const compilingRef = useRef(false)
  217. useEffect(() => {
  218. compilingRef.current = compiling
  219. }, [compiling])
  220. const _buildLogEntryAnnotations = useCallback(
  221. entries =>
  222. buildLogEntryAnnotations(entries, fileTreeData, lastCompileRootDocId),
  223. [fileTreeData, lastCompileRootDocId]
  224. )
  225. const buildLogEntryAnnotationsRef = useRef(_buildLogEntryAnnotations)
  226. useEffect(() => {
  227. buildLogEntryAnnotationsRef.current = _buildLogEntryAnnotations
  228. }, [_buildLogEntryAnnotations])
  229. // the document compiler
  230. const [compiler] = useState(() => {
  231. return new DocumentCompiler({
  232. projectId,
  233. setChangedAt,
  234. setCompiling,
  235. setData,
  236. setFirstRenderDone,
  237. setDeliveryLatencies,
  238. setError,
  239. cleanupCompileResult,
  240. compilingRef,
  241. signal,
  242. })
  243. })
  244. // keep currentDoc in sync with the compiler
  245. useEffect(() => {
  246. compiler.currentDoc = currentDoc
  247. }, [compiler, currentDoc])
  248. // keep the project rootDocId in sync with the compiler
  249. useEffect(() => {
  250. compiler.projectRootDocId = rootDocId
  251. }, [compiler, rootDocId])
  252. // keep draft setting in sync with the compiler
  253. useEffect(() => {
  254. compiler.setOption('draft', draft)
  255. }, [compiler, draft])
  256. // keep stop on first error setting in sync with the compiler
  257. useEffect(() => {
  258. compiler.setOption('stopOnFirstError', stopOnFirstError)
  259. }, [compiler, stopOnFirstError])
  260. useEffect(() => {
  261. setUncompiled(changedAt > 0)
  262. }, [setUncompiled, changedAt])
  263. useEffect(() => {
  264. setEditedSinceCompileStarted(changedAt > 0)
  265. }, [setEditedSinceCompileStarted, changedAt])
  266. // always compile the PDF once after opening the project, after the doc has loaded
  267. useEffect(() => {
  268. if (!compiledOnce && currentDoc) {
  269. setCompiledOnce(true)
  270. compiler.compile({ isAutoCompileOnLoad: true })
  271. }
  272. }, [compiledOnce, currentDoc, compiler])
  273. useEffect(() => {
  274. setHasShortCompileTimeout(
  275. features?.compileTimeout !== undefined && features.compileTimeout <= 60
  276. )
  277. }, [features])
  278. useEffect(() => {
  279. if (hasShortCompileTimeout && compiling && isProjectOwner) {
  280. const timeout = window.setTimeout(() => {
  281. setShowCompileTimeWarning(true)
  282. }, 30000)
  283. return () => {
  284. window.clearTimeout(timeout)
  285. }
  286. }
  287. }, [compiling, isProjectOwner, hasShortCompileTimeout])
  288. const hasCompileLogsEvents = useFeatureFlag('compile-log-events')
  289. // handle the data returned from a compile request
  290. // note: this should _only_ run when `data` changes,
  291. // the other dependencies must all be static
  292. useEffect(() => {
  293. const abortController = new AbortController()
  294. if (data) {
  295. if (data.clsiServerId) {
  296. setClsiServerId(data.clsiServerId) // set in scope, for PdfSynctexController
  297. }
  298. if (data.outputFiles) {
  299. const outputFiles = new Map()
  300. for (const outputFile of data.outputFiles) {
  301. outputFiles.set(outputFile.path, outputFile)
  302. }
  303. // set the PDF context
  304. if (data.status === 'success') {
  305. setPdfFile(handleOutputFiles(outputFiles, projectId, data))
  306. }
  307. setFileList(
  308. buildFileList(
  309. outputFiles,
  310. data.clsiServerId,
  311. data.compileGroup,
  312. data.outputFilesArchive
  313. )
  314. )
  315. // handle log files
  316. // asynchronous (TODO: cancel on new compile?)
  317. setLogEntryAnnotations(null)
  318. setLogEntries(null)
  319. setRawLog(undefined)
  320. handleLogFiles(outputFiles, data, abortController.signal).then(
  321. (result: Record<string, any>) => {
  322. setRawLog(result.log)
  323. setLogEntries(result.logEntries)
  324. setLogEntryAnnotations(
  325. buildLogEntryAnnotationsRef.current(result.logEntries.all)
  326. )
  327. // sample compile stats for real users
  328. if (!alphaProgram) {
  329. if (['success', 'stopped-on-first-error'].includes(data.status)) {
  330. sendMBSampled(
  331. 'compile-result',
  332. {
  333. errors: result.logEntries.errors.length,
  334. warnings: result.logEntries.warnings.length,
  335. typesetting: result.logEntries.typesetting.length,
  336. newPdfPreview: true, // TODO: is this useful?
  337. stopOnFirstError: data.options.stopOnFirstError,
  338. },
  339. 0.01
  340. )
  341. }
  342. if (hasCompileLogsEvents) {
  343. sendMB('compile-log-entries', {
  344. status: data.status,
  345. stopOnFirstError: data.options.stopOnFirstError,
  346. isAutoCompileOnLoad: !!data.options.isAutoCompileOnLoad,
  347. isAutoCompileOnChange: !!data.options.isAutoCompileOnChange,
  348. ...buildRuleCounts(result.logEntries.all),
  349. })
  350. }
  351. }
  352. }
  353. )
  354. }
  355. switch (data.status) {
  356. case 'success':
  357. setError(undefined)
  358. setShowLogs(false)
  359. break
  360. case 'stopped-on-first-error':
  361. setError(undefined)
  362. setShowLogs(true)
  363. break
  364. case 'clsi-maintenance':
  365. case 'compile-in-progress':
  366. case 'exited':
  367. case 'failure':
  368. case 'project-too-large':
  369. case 'rate-limited':
  370. case 'terminated':
  371. case 'too-recently-compiled':
  372. setError(data.status)
  373. break
  374. case 'timedout':
  375. setError('timedout')
  376. if (!hasPremiumCompile && isProjectOwner) {
  377. send(
  378. 'subscription-funnel',
  379. 'editor-click-feature',
  380. 'compile-timeout'
  381. )
  382. }
  383. break
  384. case 'autocompile-backoff':
  385. if (!data.options.isAutoCompileOnLoad) {
  386. setError('autocompile-disabled')
  387. setAutoCompile(false)
  388. }
  389. break
  390. case 'unavailable':
  391. setError('clsi-unavailable')
  392. break
  393. case 'validation-problems':
  394. setError('validation-problems')
  395. setValidationIssues(data.validationProblems)
  396. break
  397. default:
  398. setError('error')
  399. break
  400. }
  401. setStoppedOnFirstError(data.status === 'stopped-on-first-error')
  402. }
  403. return () => {
  404. abortController.abort()
  405. }
  406. }, [
  407. data,
  408. ide,
  409. alphaProgram,
  410. hasCompileLogsEvents,
  411. hasPremiumCompile,
  412. isProjectOwner,
  413. projectId,
  414. setAutoCompile,
  415. setClsiServerId,
  416. setLogEntries,
  417. setLogEntryAnnotations,
  418. setPdfFile,
  419. ])
  420. // switch to logs if there's an error
  421. useEffect(() => {
  422. if (error) {
  423. setShowLogs(true)
  424. }
  425. }, [error])
  426. // whether there has been an autocompile linting error, if syntax validation is switched on
  427. const autoCompileLintingError = Boolean(
  428. autoCompile && syntaxValidation && hasLintingError
  429. )
  430. const codeCheckFailed = stopOnValidationError && autoCompileLintingError
  431. // the project is available for auto-compiling
  432. // (autocompile is enabled, the PDF preview is open, and the code check (if enabled) hasn't failed)
  433. const canAutoCompile = Boolean(
  434. autoCompile && pdfPreviewOpen && !codeCheckFailed
  435. )
  436. // show that the project has pending changes
  437. const hasChanges = Boolean(canAutoCompile && uncompiled && compiledOnce)
  438. // call the debounced autocompile function if the project is available for auto-compiling and it has changed
  439. useEffect(() => {
  440. if (canAutoCompile) {
  441. if (changedAt > 0) {
  442. compiler.debouncedAutoCompile()
  443. }
  444. } else {
  445. compiler.debouncedAutoCompile.cancel()
  446. }
  447. }, [compiler, canAutoCompile, changedAt])
  448. // cancel debounced recompile on unmount
  449. useEffect(() => {
  450. return () => {
  451. compiler.debouncedAutoCompile.cancel()
  452. }
  453. }, [compiler])
  454. // start a compile manually
  455. const startCompile = useCallback(
  456. options => {
  457. compiler.compile(options)
  458. },
  459. [compiler]
  460. )
  461. // stop a compile manually
  462. const stopCompile = useCallback(() => {
  463. compiler.stopCompile()
  464. }, [compiler])
  465. // clear the compile cache
  466. const clearCache = useCallback(() => {
  467. setClearingCache(true)
  468. return compiler
  469. .clearCache()
  470. .then(() => {
  471. setFileList(undefined)
  472. setPdfFile(undefined)
  473. })
  474. .finally(() => {
  475. setClearingCache(false)
  476. })
  477. }, [compiler])
  478. const syncToEntry = useCallback(
  479. entry => {
  480. const result = findEntityByPath(entry.file)
  481. if (result && result.type === 'doc') {
  482. openDocId(result.entity._id, {
  483. gotoLine: entry.line ?? undefined,
  484. gotoColumn: entry.column ?? undefined,
  485. })
  486. }
  487. },
  488. [findEntityByPath, openDocId]
  489. )
  490. // clear the cache then run a compile, triggered by a menu item
  491. const recompileFromScratch = useCallback(() => {
  492. clearCache().then(() => {
  493. compiler.compile()
  494. })
  495. }, [clearCache, compiler])
  496. // After a compile, the compiler sets `data.options` to the options that were
  497. // used for that compile.
  498. const lastCompileOptions = useMemo(() => data?.options || {}, [data])
  499. useEffect(() => {
  500. const listener = (event: Event) => {
  501. setShowLogs((event as CustomEvent<boolean>).detail as boolean)
  502. }
  503. window.addEventListener('editor:show-logs', listener)
  504. return () => {
  505. window.removeEventListener('editor:show-logs', listener)
  506. }
  507. }, [])
  508. const value = useMemo(
  509. () => ({
  510. animateCompileDropdownArrow,
  511. autoCompile,
  512. clearCache,
  513. clearingCache,
  514. clsiServerId,
  515. codeCheckFailed,
  516. compiling,
  517. deliveryLatencies,
  518. draft,
  519. editedSinceCompileStarted,
  520. error,
  521. fileList,
  522. hasChanges,
  523. hasShortCompileTimeout,
  524. highlights,
  525. isProjectOwner,
  526. lastCompileOptions,
  527. logEntryAnnotations,
  528. logEntries,
  529. pdfDownloadUrl: pdfFile?.pdfDownloadUrl,
  530. pdfFile,
  531. pdfUrl: pdfFile?.pdfUrl,
  532. pdfViewer,
  533. position,
  534. rawLog,
  535. recompileFromScratch,
  536. setAnimateCompileDropdownArrow,
  537. setAutoCompile,
  538. setCompiling,
  539. setDraft,
  540. setError,
  541. setHasLintingError, // only for stories
  542. setHighlights,
  543. setPosition,
  544. showCompileTimeWarning,
  545. setShowCompileTimeWarning,
  546. setShowLogs,
  547. toggleLogs,
  548. setStopOnFirstError,
  549. setStopOnValidationError,
  550. showLogs,
  551. startCompile,
  552. stopCompile,
  553. stopOnFirstError,
  554. stopOnValidationError,
  555. stoppedOnFirstError,
  556. uncompiled,
  557. validationIssues,
  558. firstRenderDone,
  559. setChangedAt,
  560. cleanupCompileResult,
  561. syncToEntry,
  562. }),
  563. [
  564. animateCompileDropdownArrow,
  565. autoCompile,
  566. clearCache,
  567. clearingCache,
  568. clsiServerId,
  569. codeCheckFailed,
  570. compiling,
  571. deliveryLatencies,
  572. draft,
  573. editedSinceCompileStarted,
  574. error,
  575. fileList,
  576. hasChanges,
  577. hasShortCompileTimeout,
  578. highlights,
  579. isProjectOwner,
  580. lastCompileOptions,
  581. logEntries,
  582. logEntryAnnotations,
  583. position,
  584. pdfFile,
  585. pdfViewer,
  586. rawLog,
  587. recompileFromScratch,
  588. setAnimateCompileDropdownArrow,
  589. setAutoCompile,
  590. setDraft,
  591. setError,
  592. setHasLintingError, // only for stories
  593. setHighlights,
  594. setPosition,
  595. setShowCompileTimeWarning,
  596. setStopOnFirstError,
  597. setStopOnValidationError,
  598. showCompileTimeWarning,
  599. showLogs,
  600. startCompile,
  601. stopCompile,
  602. stopOnFirstError,
  603. stopOnValidationError,
  604. stoppedOnFirstError,
  605. uncompiled,
  606. validationIssues,
  607. firstRenderDone,
  608. setChangedAt,
  609. cleanupCompileResult,
  610. setShowLogs,
  611. toggleLogs,
  612. syncToEntry,
  613. ]
  614. )
  615. return (
  616. <LocalCompileContext.Provider value={value}>
  617. {children}
  618. </LocalCompileContext.Provider>
  619. )
  620. }
  621. export function useLocalCompileContext() {
  622. const context = useContext(LocalCompileContext)
  623. if (!context) {
  624. throw new Error(
  625. 'useLocalCompileContext is only available inside LocalCompileProvider'
  626. )
  627. }
  628. return context
  629. }