Detect changes to the config file and plugins for the CLI
This commit is contained in:
parent
3a7feb7930
commit
d607f0a40e
8 changed files with 238 additions and 19 deletions
|
|
@ -11,6 +11,7 @@ import atImport from 'postcss-import'
|
|||
import * as tailwindcss from 'tailwindcss'
|
||||
import type { Arg, Result } from '../../utils/args'
|
||||
import { Disposables } from '../../utils/disposables'
|
||||
import { getModuleDependencies } from '../../utils/get-module-dependencies'
|
||||
import {
|
||||
eprintln,
|
||||
formatDuration,
|
||||
|
|
@ -22,6 +23,10 @@ import {
|
|||
import { resolveCssId } from '../../utils/resolve'
|
||||
import { drainStdin, outputFile } from './utils'
|
||||
|
||||
import '@tailwindcss/cli/esm-hook'
|
||||
// @ts-ignore
|
||||
import clearRequireCache from '@tailwindcss/cli/require-cache'
|
||||
|
||||
const css = String.raw
|
||||
|
||||
export function options() {
|
||||
|
|
@ -128,27 +133,35 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
|
||||
let inputFile = args['--input'] && args['--input'] !== '-' ? args['--input'] : process.cwd()
|
||||
let inputBasePath = path.dirname(path.resolve(inputFile))
|
||||
let fullRebuildPaths = [...cssImportPaths]
|
||||
|
||||
function compile(css: string) {
|
||||
return tailwindcss.compile(css, {
|
||||
loadPlugin: async (pluginPath) => {
|
||||
if (pluginPath[0] === '.') {
|
||||
return import(pathToFileURL(path.resolve(inputBasePath, pluginPath)).href).then(
|
||||
(m) => m.default ?? m,
|
||||
)
|
||||
if (pluginPath[0] !== '.') {
|
||||
return import(pluginPath).then((m) => m.default ?? m)
|
||||
}
|
||||
|
||||
return import(pluginPath).then((m) => m.default ?? m)
|
||||
let resolvedPath = path.resolve(inputBasePath, pluginPath)
|
||||
fullRebuildPaths.push(resolvedPath)
|
||||
fullRebuildPaths.push(...getModuleDependencies(resolvedPath))
|
||||
return import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()).then(
|
||||
(m) => m.default ?? m,
|
||||
)
|
||||
},
|
||||
|
||||
loadConfig: async (configPath) => {
|
||||
if (configPath[0] === '.') {
|
||||
return import(pathToFileURL(path.resolve(inputBasePath, configPath)).href).then(
|
||||
(m) => m.default ?? m,
|
||||
)
|
||||
if (configPath[0] !== '.') {
|
||||
return import(configPath).then((m) => m.default ?? m)
|
||||
}
|
||||
|
||||
return import(configPath).then((m) => m.default ?? m)
|
||||
console.log('RELOAD')
|
||||
let resolvedPath = path.resolve(inputBasePath, configPath)
|
||||
fullRebuildPaths.push(resolvedPath)
|
||||
fullRebuildPaths.push(...getModuleDependencies(resolvedPath))
|
||||
return import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()).then(
|
||||
(m) => m.default ?? m,
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -177,9 +190,11 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
let rebuildStrategy: 'incremental' | 'full' = 'incremental'
|
||||
|
||||
for (let file of files) {
|
||||
// If one of the changed files is related to the input CSS files, then
|
||||
// we need to do a full rebuild because the theme might have changed.
|
||||
if (cssImportPaths.includes(file)) {
|
||||
// If one of the changed files is related to the input CSS or JS
|
||||
// config/plugin files, then we need to do a full rebuild because
|
||||
// the theme might have changed.
|
||||
if (fullRebuildPaths.includes(file)) {
|
||||
console.log('#############')
|
||||
rebuildStrategy = 'full'
|
||||
|
||||
// No need to check the rest of the events, because we already know we
|
||||
|
|
@ -214,6 +229,8 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
`,
|
||||
args['--input'] ?? base,
|
||||
)
|
||||
fullRebuildPaths = cssImportPaths
|
||||
clearRequireCache()
|
||||
|
||||
// Create a new compiler, given the new `input`
|
||||
compiler = await compile(input)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
let jsExtensions = ['.js', '.cjs', '.mjs']
|
||||
|
||||
// Given the current file `a.ts`, we want to make sure that when importing `b` that we resolve
|
||||
// `b.ts` before `b.js`
|
||||
//
|
||||
// E.g.:
|
||||
//
|
||||
// a.ts
|
||||
// b // .ts
|
||||
// c // .ts
|
||||
// a.js
|
||||
// b // .js or .ts
|
||||
|
||||
let jsResolutionOrder = ['', '.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.jsx', '.tsx']
|
||||
let tsResolutionOrder = ['', '.ts', '.cts', '.mts', '.tsx', '.js', '.cjs', '.mjs', '.jsx']
|
||||
|
||||
function resolveWithExtension(file: string, extensions: string[]) {
|
||||
// Try to find `./a.ts`, `./a.cts`, ... from `./a`
|
||||
for (let ext of extensions) {
|
||||
let full = `${file}${ext}`
|
||||
if (fs.existsSync(full) && fs.statSync(full).isFile()) {
|
||||
return full
|
||||
}
|
||||
}
|
||||
|
||||
// Try to find `./a/index.js` from `./a`
|
||||
for (let ext of extensions) {
|
||||
let full = `${file}/index${ext}`
|
||||
if (fs.existsSync(full)) {
|
||||
return full
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function* _getModuleDependencies(
|
||||
filename: string,
|
||||
base: string,
|
||||
seen: Set<string>,
|
||||
ext = path.extname(filename),
|
||||
): Iterable<string> {
|
||||
// Try to find the file
|
||||
let absoluteFile = resolveWithExtension(
|
||||
path.resolve(base, filename),
|
||||
jsExtensions.includes(ext) ? jsResolutionOrder : tsResolutionOrder,
|
||||
)
|
||||
if (absoluteFile === null) return // File doesn't exist
|
||||
|
||||
// Prevent infinite loops when there are circular dependencies
|
||||
if (seen.has(absoluteFile)) return // Already seen
|
||||
seen.add(absoluteFile)
|
||||
|
||||
// Mark the file as a dependency
|
||||
yield absoluteFile
|
||||
|
||||
// Resolve new base for new imports/requires
|
||||
base = path.dirname(absoluteFile)
|
||||
ext = path.extname(absoluteFile)
|
||||
|
||||
let contents = fs.readFileSync(absoluteFile, 'utf-8')
|
||||
|
||||
// Find imports/requires
|
||||
for (let match of [
|
||||
...contents.matchAll(/import[\s\S]*?['"](.{3,}?)['"]/gi),
|
||||
...contents.matchAll(/import[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi),
|
||||
...contents.matchAll(/export[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi),
|
||||
...contents.matchAll(/require\(['"`](.+)['"`]\)/gi),
|
||||
]) {
|
||||
// Bail out if it's not a relative file
|
||||
if (!match[1].startsWith('.')) continue
|
||||
|
||||
yield* _getModuleDependencies(match[1], base, seen, ext)
|
||||
}
|
||||
}
|
||||
|
||||
export function getModuleDependencies(absoluteFilePath: string) {
|
||||
if (absoluteFilePath === null) return new Set<string>()
|
||||
return new Set(
|
||||
_getModuleDependencies(absoluteFilePath, path.dirname(absoluteFilePath), new Set()),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue