diff options
| author | Philipp Tanlak <philipp.tanlak@gmail.com> | 2025-11-24 20:54:57 +0100 |
|---|---|---|
| committer | Philipp Tanlak <philipp.tanlak@gmail.com> | 2025-11-24 20:57:48 +0100 |
| commit | b1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c (patch) | |
| tree | 49d360fd6cbc6a2754efe93524ac47ff0fbe0f7d /node_modules/tailwindcss/lib/oxide/cli/build/watching.js | |
Docs
Diffstat (limited to 'node_modules/tailwindcss/lib/oxide/cli/build/watching.js')
| -rw-r--r-- | node_modules/tailwindcss/lib/oxide/cli/build/watching.js | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/node_modules/tailwindcss/lib/oxide/cli/build/watching.js b/node_modules/tailwindcss/lib/oxide/cli/build/watching.js new file mode 100644 index 0000000..e8321d9 --- /dev/null +++ b/node_modules/tailwindcss/lib/oxide/cli/build/watching.js @@ -0,0 +1,179 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "createWatcher", { + enumerable: true, + get: function() { + return createWatcher; + } +}); +const _chokidar = /*#__PURE__*/ _interop_require_default(require("chokidar")); +const _fs = /*#__PURE__*/ _interop_require_default(require("fs")); +const _micromatch = /*#__PURE__*/ _interop_require_default(require("micromatch")); +const _normalizepath = /*#__PURE__*/ _interop_require_default(require("normalize-path")); +const _path = /*#__PURE__*/ _interop_require_default(require("path")); +const _utils = require("./utils"); +function _interop_require_default(obj) { + return obj && obj.__esModule ? obj : { + default: obj + }; +} +function createWatcher(args, { state , rebuild }) { + let shouldPoll = args["--poll"]; + let shouldCoalesceWriteEvents = shouldPoll || process.platform === "win32"; + // Polling interval in milliseconds + // Used only when polling or coalescing add/change events on Windows + let pollInterval = 10; + let watcher = _chokidar.default.watch([], { + // Force checking for atomic writes in all situations + // This causes chokidar to wait up to 100ms for a file to re-added after it's been unlinked + // This only works when watching directories though + atomic: true, + usePolling: shouldPoll, + interval: shouldPoll ? pollInterval : undefined, + ignoreInitial: true, + awaitWriteFinish: shouldCoalesceWriteEvents ? { + stabilityThreshold: 50, + pollInterval: pollInterval + } : false + }); + // A queue of rebuilds, file reads, etc… to run + let chain = Promise.resolve(); + /** + * A list of files that have been changed since the last rebuild + * + * @type {{file: string, content: () => Promise<string>, extension: string}[]} + */ let changedContent = []; + /** + * A list of files for which a rebuild has already been queued. + * This is used to prevent duplicate rebuilds when multiple events are fired for the same file. + * The rebuilt file is cleared from this list when it's associated rebuild has _started_ + * This is because if the file is changed during a rebuild it won't trigger a new rebuild which it should + **/ let pendingRebuilds = new Set(); + let _timer; + let _reject; + /** + * Rebuilds the changed files and resolves when the rebuild is + * complete regardless of whether it was successful or not + */ async function rebuildAndContinue() { + let changes = changedContent.splice(0); + // There are no changes to rebuild so we can just do nothing + if (changes.length === 0) { + return Promise.resolve(); + } + // Clear all pending rebuilds for the about-to-be-built files + changes.forEach((change)=>pendingRebuilds.delete(change.file)); + // Resolve the promise even when the rebuild fails + return rebuild(changes).then(()=>{}, ()=>{}); + } + /** + * + * @param {*} file + * @param {(() => Promise<string>) | null} content + * @param {boolean} skipPendingCheck + * @returns {Promise<void>} + */ function recordChangedFile(file, content = null, skipPendingCheck = false) { + file = _path.default.resolve(file); + // Applications like Vim/Neovim fire both rename and change events in succession for atomic writes + // In that case rebuild has already been queued by rename, so can be skipped in change + if (pendingRebuilds.has(file) && !skipPendingCheck) { + return Promise.resolve(); + } + // Mark that a rebuild of this file is going to happen + // It MUST happen synchronously before the rebuild is queued for this to be effective + pendingRebuilds.add(file); + changedContent.push({ + file, + content: content !== null && content !== void 0 ? content : ()=>_fs.default.promises.readFile(file, "utf8"), + extension: _path.default.extname(file).slice(1) + }); + if (_timer) { + clearTimeout(_timer); + _reject(); + } + // If a rebuild is already in progress we don't want to start another one until the 10ms timer has expired + chain = chain.then(()=>new Promise((resolve, reject)=>{ + _timer = setTimeout(resolve, 10); + _reject = reject; + })); + // Resolves once this file has been rebuilt (or the rebuild for this file has failed) + // This queues as many rebuilds as there are changed files + // But those rebuilds happen after some delay + // And will immediately resolve if there are no changes + chain = chain.then(rebuildAndContinue, rebuildAndContinue); + return chain; + } + watcher.on("change", (file)=>recordChangedFile(file)); + watcher.on("add", (file)=>recordChangedFile(file)); + // Restore watching any files that are "removed" + // This can happen when a file is pseudo-atomically replaced (a copy is created, overwritten, the old one is unlinked, and the new one is renamed) + // TODO: An an optimization we should allow removal when the config changes + watcher.on("unlink", (file)=>{ + file = (0, _normalizepath.default)(file); + // Only re-add the file if it's not covered by a dynamic pattern + if (!_micromatch.default.some([ + file + ], state.contentPatterns.dynamic)) { + watcher.add(file); + } + }); + // Some applications such as Visual Studio (but not VS Code) + // will only fire a rename event for atomic writes and not a change event + // This is very likely a chokidar bug but it's one we need to work around + // We treat this as a change event and rebuild the CSS + watcher.on("raw", (evt, filePath, meta)=>{ + if (evt !== "rename") { + return; + } + let watchedPath = meta.watchedPath; + // Watched path might be the file itself + // Or the directory it is in + filePath = watchedPath.endsWith(filePath) ? watchedPath : _path.default.join(watchedPath, filePath); + // Skip this event since the files it is for does not match any of the registered content globs + if (!_micromatch.default.some([ + filePath + ], state.contentPatterns.all)) { + return; + } + // Skip since we've already queued a rebuild for this file that hasn't happened yet + if (pendingRebuilds.has(filePath)) { + return; + } + // We'll go ahead and add the file to the pending rebuilds list here + // It'll be removed when the rebuild starts unless the read fails + // which will be taken care of as well + pendingRebuilds.add(filePath); + async function enqueue() { + try { + // We need to read the file as early as possible outside of the chain + // because it may be gone by the time we get to it. doing the read + // immediately increases the chance that the file is still there + let content = await (0, _utils.readFileWithRetries)(_path.default.resolve(filePath)); + if (content === undefined) { + return; + } + // This will push the rebuild onto the chain + // We MUST skip the rebuild check here otherwise the rebuild will never happen on Linux + // This is because the order of events and timing is different on Linux + // @ts-ignore: TypeScript isn't picking up that content is a string here + await recordChangedFile(filePath, ()=>content, true); + } catch { + // If reading the file fails, it's was probably a deleted temporary file + // So we can ignore it and no rebuild is needed + } + } + enqueue().then(()=>{ + // If the file read fails we still need to make sure the file isn't stuck in the pending rebuilds list + pendingRebuilds.delete(filePath); + }); + }); + return { + fswatcher: watcher, + refreshWatchedFiles () { + watcher.add(Array.from(state.contextDependencies)); + watcher.add(Array.from(state.configBag.dependencies)); + watcher.add(state.contentPatterns.all); + } + }; +} |