diff --git a/.gitignore b/.gitignore index 53f7466..dea8f96 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules .DS_Store dist dist-ssr -*.local \ No newline at end of file +*.local +.test/ \ No newline at end of file diff --git a/src/benchmark.test.js b/src/benchmark.test.js new file mode 100644 index 0000000..905cee9 --- /dev/null +++ b/src/benchmark.test.js @@ -0,0 +1,82 @@ +const prettier = require('prettier') +const diff = require('jest-diff').default +const postcss = require('postcss') +const tailwind = require('./index.js') +const fs = require('fs/promises') +const path = require('path') +const _ = require('lodash') + +function run(input, config = {}) { + return postcss([tailwind(config)]).process(input, { from: undefined }) +} + +/** + * + * @param {number} count + */ +async function createTestFiles(count) { + const ns = Math.random().toString(16).substr(2, 8) + const testDir = path.normalize(path.join(__dirname, `/../.test/${ns}`)) + + const utilities = (() => { + const prefixes = _.shuffle(["", "sm:", "hover:", "active:"]) + const types = _.shuffle(["h-", "mt-", "max-w-", "foobar-"]) + const suffixes = _.shuffle([_.range(0, 1000), "full", "screen"]) + const utilities = [] + + for (let prefix in prefixes) for (let type in types) for (let suffix in suffixes) { + utilities.push(`${prefix}${type}${suffix}`) + } + + return utilities + })() + + async function createCandidateFile(id) { + const content = utilities.slice( + Math.floor(Math.random() * utilities.length / 4), + Math.floor(Math.random() * utilities.length) + ).join("\n") + + await fs.writeFile(path.resolve(testDir, `bench_${id}`), content, "utf-8") + } + + await fs.mkdir(testDir, { recursive: true }) + await Promise.all(_.range(0, count).map(n => createCandidateFile(n))) + + return testDir +} + +async function bench(count, callback) { + const start = performance.now() + + for (let i = 0; i < count; i++) { + await callback(); + } + + const end = performance.now() + + return (end-start) / count; +} + +test('bechmark', async () => { + const runPerfTest = async (dir) => { + const config = { purge: [`${dir}/*.css`] } + const content = '@tailwind utilities;' + + try { + return await bench(100, () => run(content, config)) + } finally { + await fs.rm(dir, { recursive: true, force: true }) + } + } + + const small = await runPerfTest(await createTestFiles(1e1)) + const medium = await runPerfTest(await createTestFiles(1e3)) + const large = await runPerfTest(await createTestFiles(1e4)) + + console.log({small, medium, large}) + + expect(small).toBeLessThan(20) + expect(medium).toBeLessThan(20) + expect(large).toBeLessThan(20) +}) diff --git a/src/index.js b/src/index.js index 8d512dd..a2c5989 100644 --- a/src/index.js +++ b/src/index.js @@ -318,6 +318,9 @@ function cleanupContext(context) { if (context.watcher) { context.watcher.close() } + if (context.touchFile) { + context.touchFile.removeCallback() + } contextMap.delete(context.configHash) contextSources.delete(context) } @@ -332,6 +335,7 @@ function rebootTemplateWatcher(context) { (env.TAILWIND_MODE === undefined && env.NODE_ENV === 'development') ) { context.touchFile = context.touchFile !== null ? context.touchFile : tmp.fileSync() + env.DEBUG && console.log("Touch file path:", context.touchFile) Promise.resolve(context.watcher ? context.watcher.close() : null).then(() => { context.watcher = chokidar.watch(context.candidateFiles, { @@ -746,6 +750,7 @@ function setupContext(tailwindConfig, configHash, configPath) { } let context = { + dependencies: new Set(), changedFiles: new Set(), utilityRuleCache: new Set(), componentRuleCache: new Set(), @@ -958,7 +963,7 @@ module.exports = (pluginOptions = {}) => { // the work of grabbing all of the template files for candidate // detection. if (!context.scannedContent) { - let files = fastGlob.sync(context.candidateFiles) + let files = fastGlob.sync(context.candidateFiles, { absolute: true }) for (let file of files) { context.changedFiles.add(file) } @@ -973,6 +978,8 @@ module.exports = (pluginOptions = {}) => { env.DEBUG && console.time('Reading changed files') for (let file of context.changedFiles) { + context.dependencies.add(file) + let content = fs.readFileSync(file, 'utf8') getClassCandidates(content, contentMatchCache, candidates, seen) } @@ -1042,6 +1049,7 @@ module.exports = (pluginOptions = {}) => { if (env.DEBUG) { console.log('Changed files: ', context.changedFiles.size) + console.log('Dependencies:', context.dependencies.size) console.log('Potential classes: ', candidates.size) console.log('Active contexts: ', contextMap.size) console.log('Active sources:', sourceContextMap.size) @@ -1051,6 +1059,7 @@ module.exports = (pluginOptions = {}) => { // Clear the cache for the changed files context.changedFiles.clear() + context.dependencies.forEach(registerDependency) }, function (root) { if (!foundTailwind) {