diff --git a/package-lock.json b/package-lock.json index 67bb6015f1bc..35edc8014a67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,8 +28,7 @@ "postcss-selector-parser": "^6.0.7", "postcss-value-parser": "^4.2.0", "quick-lru": "^5.1.1", - "resolve": "^1.20.0", - "tmp": "^0.2.1" + "resolve": "^1.20.0" }, "bin": { "tailwind": "lib/cli.js", @@ -1784,7 +1783,8 @@ "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "node_modules/binary-extensions": { "version": "2.2.0", @@ -1804,6 +1804,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2087,7 +2088,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "node_modules/convert-source-map": { "version": "1.8.0", @@ -3408,7 +3410,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "node_modules/fsevents": { "version": "2.3.2", @@ -3489,6 +3492,7 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3727,6 +3731,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3735,7 +3740,8 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/is-arrayish": { "version": "0.2.1", @@ -4948,6 +4954,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5075,6 +5082,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "dependencies": { "wrappy": "1" } @@ -5215,6 +5223,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -6084,6 +6093,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -6435,17 +6445,6 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "dev": true }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -6702,7 +6701,8 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "node_modules/write-file-atomic": { "version": "3.0.3", @@ -8079,7 +8079,8 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "binary-extensions": { "version": "2.2.0", @@ -8096,6 +8097,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8316,7 +8318,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "convert-source-map": { "version": "1.8.0", @@ -9250,7 +9253,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "fsevents": { "version": "2.3.2", @@ -9303,6 +9307,7 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9476,6 +9481,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -9484,7 +9490,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "is-arrayish": { "version": "0.2.1", @@ -10435,6 +10442,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -10529,6 +10537,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -10631,7 +10640,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-key": { "version": "3.1.1", @@ -11185,6 +11195,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -11443,14 +11454,6 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "dev": true }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "requires": { - "rimraf": "^3.0.0" - } - }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -11654,7 +11657,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write-file-atomic": { "version": "3.0.3", diff --git a/package.json b/package.json index 393395203fc8..6edea5c8273f 100644 --- a/package.json +++ b/package.json @@ -85,8 +85,7 @@ "postcss-selector-parser": "^6.0.7", "postcss-value-parser": "^4.2.0", "quick-lru": "^5.1.1", - "resolve": "^1.20.0", - "tmp": "^0.2.1" + "resolve": "^1.20.0" }, "browserslist": [ "> 1%", diff --git a/src/index.js b/src/index.js index 08a709a6577b..0cba8bd75a73 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,4 @@ import setupTrackingContext from './lib/setupTrackingContext' -import setupWatchingContext from './lib/setupWatchingContext' import processTailwindFeatures from './processTailwindFeatures' import { env } from './lib/sharedState' @@ -14,12 +13,7 @@ module.exports = function tailwindcss(configOrPath) { return root }, function (root, result) { - let setupContext = - env.TAILWIND_MODE === 'watch' - ? setupWatchingContext(configOrPath) - : setupTrackingContext(configOrPath) - - processTailwindFeatures(setupContext)(root, result) + processTailwindFeatures(setupTrackingContext(configOrPath))(root, result) }, env.DEBUG && function (root) { diff --git a/src/lib/setupWatchingContext.js b/src/lib/setupWatchingContext.js deleted file mode 100644 index 1ee1eaf3094e..000000000000 --- a/src/lib/setupWatchingContext.js +++ /dev/null @@ -1,311 +0,0 @@ -import fs from 'fs' -import path from 'path' - -import tmp from 'tmp' -import chokidar from 'chokidar' -import fastGlob from 'fast-glob' -import LRU from 'quick-lru' -import normalizePath from 'normalize-path' - -import hash from '../util/hashConfig' -import log from '../util/log' -import getModuleDependencies from '../lib/getModuleDependencies' -import resolveConfig from '../public/resolve-config' -import resolveConfigPath from '../util/resolveConfigPath' -import { getContext } from './setupContextUtils' - -// This is used to trigger rebuilds. Just updating the timestamp -// is significantly faster than actually writing to the file (10x). - -function touch(filename) { - let time = new Date() - - try { - fs.utimesSync(filename, time, time) - } catch (err) { - fs.closeSync(fs.openSync(filename, 'w')) - } -} - -let watchers = new WeakMap() - -function getWatcher(context) { - if (watchers.has(context)) { - return watchers.get(context) - } - - return null -} - -function setWatcher(context, watcher) { - return watchers.set(context, watcher) -} - -let touchFiles = new WeakMap() - -function getTouchFile(context) { - if (touchFiles.has(context)) { - return touchFiles.get(context) - } - - return null -} - -function setTouchFile(context, touchFile) { - return touchFiles.set(context, touchFile) -} - -let configPaths = new WeakMap() - -function getConfigPath(context, configOrPath) { - if (!configPaths.has(context)) { - configPaths.set(context, resolveConfigPath(configOrPath)) - } - - return configPaths.get(context) -} - -function rebootWatcher(context, configPath, configDependencies, candidateFiles) { - let touchFile = getTouchFile(context) - - if (touchFile === null) { - touchFile = tmp.fileSync().name - setTouchFile(context, touchFile) - touch(touchFile) - } - - let watcher = getWatcher(context) - - Promise.resolve(watcher ? watcher.close() : null).then(() => { - log.info([ - 'Tailwind CSS is watching for changes...', - 'https://tailwindcss.com/docs/just-in-time-mode#watch-mode-and-one-off-builds', - ]) - - watcher = chokidar.watch([...candidateFiles, ...configDependencies], { - ignoreInitial: true, - awaitWriteFinish: - process.platform === 'win32' - ? { - stabilityThreshold: 50, - pollInterval: 10, - } - : false, - }) - - setWatcher(context, watcher) - - watcher.on('add', (file) => { - let changedFile = path.resolve('.', file) - let content = fs.readFileSync(changedFile, 'utf8') - let extension = path.extname(changedFile).slice(1) - context.changedContent.push({ content, extension }) - touch(touchFile) - }) - - watcher.on('change', (file) => { - // If it was a config dependency, touch the config file to trigger a new context. - // This is not really that clean of a solution but it's the fastest, because we - // can do a very quick check on each build to see if the config has changed instead - // of having to get all of the module dependencies and check every timestamp each - // time. - if (configDependencies.has(file)) { - for (let dependency of configDependencies) { - delete require.cache[require.resolve(dependency)] - } - touch(configPath) - } else { - let changedFile = path.resolve('.', file) - let content = fs.readFileSync(changedFile, 'utf8') - let extension = path.extname(changedFile).slice(1) - context.changedContent.push({ content, extension }) - touch(touchFile) - } - }) - - watcher.on('unlink', (file) => { - // Touch the config file if any of the dependencies are deleted. - if (configDependencies.has(file)) { - for (let dependency of configDependencies) { - delete require.cache[require.resolve(dependency)] - } - touch(configPath) - } - }) - }) -} - -let configPathCache = new LRU({ maxSize: 100 }) - -let configDependenciesCache = new WeakMap() - -function getConfigDependencies(context) { - if (!configDependenciesCache.has(context)) { - configDependenciesCache.set(context, new Set()) - } - - return configDependenciesCache.get(context) -} - -let candidateFilesCache = new WeakMap() - -function getCandidateFiles(context, tailwindConfig) { - if (candidateFilesCache.has(context)) { - return candidateFilesCache.get(context) - } - - let candidateFiles = tailwindConfig.content.files - .filter((item) => typeof item === 'string') - .map((contentPath) => normalizePath(contentPath)) - - return candidateFilesCache.set(context, candidateFiles).get(context) -} - -// Get the config object based on a path -function getTailwindConfig(configOrPath) { - let userConfigPath = resolveConfigPath(configOrPath) - - if (userConfigPath !== null) { - let [prevConfig, prevModified = -Infinity, prevConfigHash] = - configPathCache.get(userConfigPath) || [] - let modified = fs.statSync(userConfigPath).mtimeMs - - // It hasn't changed (based on timestamp) - if (modified <= prevModified) { - return [prevConfig, userConfigPath, prevConfigHash, [userConfigPath]] - } - - // It has changed (based on timestamp), or first run - delete require.cache[userConfigPath] - let newConfig = resolveConfig(require(userConfigPath)) - let newHash = hash(newConfig) - configPathCache.set(userConfigPath, [newConfig, modified, newHash]) - return [newConfig, userConfigPath, newHash, [userConfigPath]] - } - - // It's a plain object, not a path - let newConfig = resolveConfig( - configOrPath.config === undefined ? configOrPath : configOrPath.config - ) - - return [newConfig, null, hash(newConfig), []] -} - -function resolvedChangedContent(context, candidateFiles) { - let changedContent = context.tailwindConfig.content.files - .filter((item) => typeof item.raw === 'string') - .map(({ raw, extension = 'html' }) => ({ content: raw, extension })) - - for (let changedFile of resolveChangedFiles(context, candidateFiles)) { - let content = fs.readFileSync(changedFile, 'utf8') - let extension = path.extname(changedFile).slice(1) - changedContent.push({ content, extension }) - } - return changedContent -} - -let scannedContentCache = new WeakMap() - -function resolveChangedFiles(context, candidateFiles) { - let changedFiles = new Set() - - // If we're not set up and watching files ourselves, we need to do - // the work of grabbing all of the template files for candidate - // detection. - if (!scannedContentCache.has(context)) { - let files = fastGlob.sync(candidateFiles) - for (let file of files) { - changedFiles.add(file) - } - scannedContentCache.set(context, true) - } - - return changedFiles -} - -// DISABLE_TOUCH = FALSE - -// Retrieve an existing context from cache if possible (since contexts are unique per -// source path), or set up a new one (including setting up watchers and registering -// plugins) then return it -export default function setupWatchingContext(configOrPath) { - return ({ tailwindDirectives, registerDependency }) => { - return (root, result) => { - let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] = - getTailwindConfig(configOrPath) - - let contextDependencies = new Set(configDependencies) - - // If there are no @tailwind rules, we don't consider this CSS file or it's dependencies - // to be dependencies of the context. Can reuse the context even if they change. - // We may want to think about `@layer` being part of this trigger too, but it's tough - // because it's impossible for a layer in one file to end up in the actual @tailwind rule - // in another file since independent sources are effectively isolated. - if (tailwindDirectives.size > 0) { - // Add current css file as a context dependencies. - contextDependencies.add(result.opts.from) - - // Add all css @import dependencies as context dependencies. - for (let message of result.messages) { - if (message.type === 'dependency') { - contextDependencies.add(message.file) - } - } - } - - let [context, isNewContext] = getContext( - root, - result, - tailwindConfig, - userConfigPath, - tailwindConfigHash, - contextDependencies - ) - - let candidateFiles = getCandidateFiles(context, tailwindConfig) - let contextConfigDependencies = getConfigDependencies(context) - - for (let file of configDependencies) { - registerDependency({ type: 'dependency', file }) - } - - context.disposables.push((oldContext) => { - let watcher = getWatcher(oldContext) - if (watcher !== null) { - watcher.close() - } - }) - - let configPath = getConfigPath(context, configOrPath) - - if (configPath !== null) { - for (let dependency of getModuleDependencies(configPath)) { - if (dependency.file === configPath) { - continue - } - - contextConfigDependencies.add(dependency.file) - } - } - - if (isNewContext) { - rebootWatcher(context, configPath, contextConfigDependencies, candidateFiles) - } - - // Register our temp file as a dependency — we write to this file - // to trigger rebuilds. - let touchFile = getTouchFile(context) - if (touchFile) { - registerDependency({ type: 'dependency', file: touchFile }) - } - - if (tailwindDirectives.size > 0) { - for (let changedContent of resolvedChangedContent(context, candidateFiles)) { - context.changedContent.push(changedContent) - } - } - - return context - } - } -}