Improve Oxide scanner API (#14187)
This PR updates the API for interacting with the Oxide API. Until now,
we used the name `scanDir(…)` which is fine, but we do way more work
right now.
We now have features such as:
1. Auto source detection (can be turned off, e.g.: `@tailwindcss/vite`
doesn't need it)
2. Scan based on `@source`s found in CSS files
3. Do "incremental" rebuilds (which means that the `scanDir(…)` result
was stateful).
To solve these issues, this PR introduces a new `Scanner` class where
you can pass in the `detectSources` and `sources` options. E.g.:
```ts
let scanner = new Scanner({
// Optional, omitting `detectSources` field disables automatic source detection
detectSources: { base: __dirname },
// List of glob entries to scan. These come from `@source` directives in CSS.
sources: [
{ base: __dirname, pattern: "src/**/*.css" },
// …
],
});
```
The scanner object has the following API:
```ts
export interface ChangedContent {
/** File path to the changed file */
file?: string
/** Contents of the changed file */
content?: string
/** File extension */
extension: string
}
export interface DetectSources {
/** Base path to start scanning from */
base: string
}
export interface GlobEntry {
/** Base path of the glob */
base: string
/** Glob pattern */
pattern: string
}
export interface ScannerOptions {
/** Automatically detect sources in the base path */
detectSources?: DetectSources
/** Glob sources */
sources?: Array<GlobEntry>
}
export declare class Scanner {
constructor(opts: ScannerOptions)
scan(): Array<string>
scanFiles(input: Array<ChangedContent>): Array<string>
get files(): Array<string>
get globs(): Array<GlobEntry>
}
```
The `scanFiles(…)` method is used for incremental rebuilds. It takes the
`ChangedContent` array for all the new/changes files. It returns whether
we scanned any new candidates or not.
Note that the `scanner` object is stateful, this means that we don't
have to track candidates in a `Set` anymore. We can just call
`getCandidates()` when we need it.
This PR also removed some unused code that we had in the `scanDir(…)`
function to allow for sequential or parallel `IO`, and sequential or
parallel `Parsing`. We only used the same `IO` and `Parsing` strategies
for all files, so I just got rid of it.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-08-16 15:05:42 +02:00
|
|
|
import { Scanner } from '@tailwindcss/oxide'
|
2024-03-11 20:01:28 +01:00
|
|
|
import fs from 'fs'
|
2024-08-07 16:38:44 +02:00
|
|
|
import fixRelativePathsPlugin from 'internal-postcss-fix-relative-paths'
|
2024-03-13 17:25:16 +01:00
|
|
|
import { Features, transform } from 'lightningcss'
|
2024-08-08 17:49:06 +02:00
|
|
|
import { pathToFileURL } from 'node:url'
|
2024-07-11 09:47:26 -04:00
|
|
|
import path from 'path'
|
2024-08-07 16:38:44 +02:00
|
|
|
import postcss, { AtRule, type AcceptedPlugin, type PluginCreator } from 'postcss'
|
2024-03-05 14:23:26 +01:00
|
|
|
import postcssImport from 'postcss-import'
|
2024-03-13 17:25:16 +01:00
|
|
|
import { compile } from 'tailwindcss'
|
2024-03-05 14:23:26 +01:00
|
|
|
|
2024-03-11 20:01:28 +01:00
|
|
|
/**
|
|
|
|
|
* A Map that can generate default values for keys that don't exist.
|
|
|
|
|
* Generated default values are added to the map to avoid recomputation.
|
|
|
|
|
*/
|
|
|
|
|
class DefaultMap<T = string, V = any> extends Map<T, V> {
|
|
|
|
|
constructor(private factory: (key: T, self: DefaultMap<T, V>) => V) {
|
|
|
|
|
super()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get(key: T): V {
|
|
|
|
|
let value = super.get(key)
|
|
|
|
|
|
|
|
|
|
if (value === undefined) {
|
|
|
|
|
value = this.factory(key, this)
|
|
|
|
|
this.set(key, value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-05 14:23:26 +01:00
|
|
|
type PluginOptions = {
|
|
|
|
|
// The base directory to scan for class candidates.
|
|
|
|
|
base?: string
|
2024-03-08 18:36:07 +01:00
|
|
|
|
2024-03-11 18:29:21 +01:00
|
|
|
// Optimize and minify the output CSS.
|
2024-03-08 18:36:07 +01:00
|
|
|
optimize?: boolean | { minify?: boolean }
|
2024-03-05 14:23:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
|
|
|
|
|
let base = opts.base ?? process.cwd()
|
2024-03-08 18:36:07 +01:00
|
|
|
let optimize = opts.optimize ?? process.env.NODE_ENV === 'production'
|
2024-03-05 14:23:26 +01:00
|
|
|
|
2024-03-11 20:01:28 +01:00
|
|
|
let cache = new DefaultMap(() => {
|
|
|
|
|
return {
|
|
|
|
|
mtimes: new Map<string, number>(),
|
2024-08-08 17:49:06 +02:00
|
|
|
compiler: null as null | Awaited<ReturnType<typeof compile>>,
|
2024-03-11 20:01:28 +01:00
|
|
|
css: '',
|
|
|
|
|
optimizedCss: '',
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2024-08-07 16:38:44 +02:00
|
|
|
let hasApply: boolean, hasTailwind: boolean
|
|
|
|
|
|
2024-03-05 14:23:26 +01:00
|
|
|
return {
|
2024-03-11 20:01:28 +01:00
|
|
|
postcssPlugin: '@tailwindcss/postcss',
|
2024-03-05 14:23:26 +01:00
|
|
|
plugins: [
|
|
|
|
|
// We need to run `postcss-import` first to handle `@import` rules.
|
|
|
|
|
postcssImport(),
|
2024-08-07 16:38:44 +02:00
|
|
|
fixRelativePathsPlugin(),
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
postcssPlugin: 'tailwindcss',
|
|
|
|
|
Once() {
|
|
|
|
|
// Reset some state between builds
|
|
|
|
|
hasApply = false
|
|
|
|
|
hasTailwind = false
|
|
|
|
|
},
|
|
|
|
|
AtRule(rule: AtRule) {
|
2024-03-05 14:23:26 +01:00
|
|
|
if (rule.name === 'apply') {
|
|
|
|
|
hasApply = true
|
|
|
|
|
} else if (rule.name === 'tailwind') {
|
|
|
|
|
hasApply = true
|
|
|
|
|
hasTailwind = true
|
|
|
|
|
}
|
2024-08-07 16:38:44 +02:00
|
|
|
},
|
2024-08-08 17:49:06 +02:00
|
|
|
async OnceExit(root, { result }) {
|
2024-08-07 16:38:44 +02:00
|
|
|
let inputFile = result.opts.from ?? ''
|
|
|
|
|
let context = cache.get(inputFile)
|
|
|
|
|
let inputBasePath = path.dirname(path.resolve(inputFile))
|
|
|
|
|
|
|
|
|
|
function createCompiler() {
|
|
|
|
|
return compile(root.toString(), {
|
2024-08-08 17:49:06 +02:00
|
|
|
loadPlugin: async (pluginPath) => {
|
2024-08-07 16:38:44 +02:00
|
|
|
if (pluginPath[0] === '.') {
|
2024-08-08 17:49:06 +02:00
|
|
|
return import(pathToFileURL(path.resolve(inputBasePath, pluginPath)).href).then(
|
|
|
|
|
(m) => m.default ?? m,
|
|
|
|
|
)
|
2024-08-07 16:38:44 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-08 17:49:06 +02:00
|
|
|
return import(pluginPath).then((m) => m.default ?? m)
|
2024-08-07 16:38:44 +02:00
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
2024-03-05 14:23:26 +01:00
|
|
|
|
2024-08-07 16:38:44 +02:00
|
|
|
// Setup the compiler if it doesn't exist yet. This way we can
|
|
|
|
|
// guarantee a `build()` function is available.
|
2024-08-08 17:49:06 +02:00
|
|
|
context.compiler ??= await createCompiler()
|
2024-08-07 16:38:44 +02:00
|
|
|
|
|
|
|
|
let rebuildStrategy: 'full' | 'incremental' = 'incremental'
|
|
|
|
|
|
|
|
|
|
// Track file modification times to CSS files
|
|
|
|
|
{
|
|
|
|
|
let files = result.messages.flatMap((message) => {
|
|
|
|
|
if (message.type !== 'dependency') return []
|
|
|
|
|
return message.file
|
|
|
|
|
})
|
|
|
|
|
files.push(inputFile)
|
|
|
|
|
for (let file of files) {
|
|
|
|
|
let changedTime = fs.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? null
|
|
|
|
|
if (changedTime === null) {
|
|
|
|
|
if (file === inputFile) {
|
|
|
|
|
rebuildStrategy = 'full'
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
2024-03-05 14:23:26 +01:00
|
|
|
|
2024-08-07 16:38:44 +02:00
|
|
|
let prevTime = context.mtimes.get(file)
|
|
|
|
|
if (prevTime === changedTime) continue
|
2024-03-05 14:23:26 +01:00
|
|
|
|
2024-08-07 16:38:44 +02:00
|
|
|
rebuildStrategy = 'full'
|
|
|
|
|
context.mtimes.set(file, changedTime)
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-05 14:23:26 +01:00
|
|
|
|
2024-08-07 16:38:44 +02:00
|
|
|
// Do nothing if neither `@tailwind` nor `@apply` is used
|
|
|
|
|
if (!hasTailwind && !hasApply) return
|
2024-07-11 09:47:26 -04:00
|
|
|
|
2024-08-07 16:38:44 +02:00
|
|
|
let css = ''
|
|
|
|
|
|
|
|
|
|
// Look for candidates used to generate the CSS
|
Improve Oxide scanner API (#14187)
This PR updates the API for interacting with the Oxide API. Until now,
we used the name `scanDir(…)` which is fine, but we do way more work
right now.
We now have features such as:
1. Auto source detection (can be turned off, e.g.: `@tailwindcss/vite`
doesn't need it)
2. Scan based on `@source`s found in CSS files
3. Do "incremental" rebuilds (which means that the `scanDir(…)` result
was stateful).
To solve these issues, this PR introduces a new `Scanner` class where
you can pass in the `detectSources` and `sources` options. E.g.:
```ts
let scanner = new Scanner({
// Optional, omitting `detectSources` field disables automatic source detection
detectSources: { base: __dirname },
// List of glob entries to scan. These come from `@source` directives in CSS.
sources: [
{ base: __dirname, pattern: "src/**/*.css" },
// …
],
});
```
The scanner object has the following API:
```ts
export interface ChangedContent {
/** File path to the changed file */
file?: string
/** Contents of the changed file */
content?: string
/** File extension */
extension: string
}
export interface DetectSources {
/** Base path to start scanning from */
base: string
}
export interface GlobEntry {
/** Base path of the glob */
base: string
/** Glob pattern */
pattern: string
}
export interface ScannerOptions {
/** Automatically detect sources in the base path */
detectSources?: DetectSources
/** Glob sources */
sources?: Array<GlobEntry>
}
export declare class Scanner {
constructor(opts: ScannerOptions)
scan(): Array<string>
scanFiles(input: Array<ChangedContent>): Array<string>
get files(): Array<string>
get globs(): Array<GlobEntry>
}
```
The `scanFiles(…)` method is used for incremental rebuilds. It takes the
`ChangedContent` array for all the new/changes files. It returns whether
we scanned any new candidates or not.
Note that the `scanner` object is stateful, this means that we don't
have to track candidates in a `Set` anymore. We can just call
`getCandidates()` when we need it.
This PR also removed some unused code that we had in the `scanDir(…)`
function to allow for sequential or parallel `IO`, and sequential or
parallel `Parsing`. We only used the same `IO` and `Parsing` strategies
for all files, so I just got rid of it.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-08-16 15:05:42 +02:00
|
|
|
let scanner = new Scanner({
|
|
|
|
|
detectSources: { base },
|
2024-08-07 16:38:44 +02:00
|
|
|
sources: context.compiler.globs.map((pattern) => ({
|
|
|
|
|
base: inputBasePath, // Globs are relative to the input.css file
|
|
|
|
|
pattern,
|
|
|
|
|
})),
|
2024-03-11 20:01:28 +01:00
|
|
|
})
|
2024-08-07 16:38:44 +02:00
|
|
|
|
Improve Oxide scanner API (#14187)
This PR updates the API for interacting with the Oxide API. Until now,
we used the name `scanDir(…)` which is fine, but we do way more work
right now.
We now have features such as:
1. Auto source detection (can be turned off, e.g.: `@tailwindcss/vite`
doesn't need it)
2. Scan based on `@source`s found in CSS files
3. Do "incremental" rebuilds (which means that the `scanDir(…)` result
was stateful).
To solve these issues, this PR introduces a new `Scanner` class where
you can pass in the `detectSources` and `sources` options. E.g.:
```ts
let scanner = new Scanner({
// Optional, omitting `detectSources` field disables automatic source detection
detectSources: { base: __dirname },
// List of glob entries to scan. These come from `@source` directives in CSS.
sources: [
{ base: __dirname, pattern: "src/**/*.css" },
// …
],
});
```
The scanner object has the following API:
```ts
export interface ChangedContent {
/** File path to the changed file */
file?: string
/** Contents of the changed file */
content?: string
/** File extension */
extension: string
}
export interface DetectSources {
/** Base path to start scanning from */
base: string
}
export interface GlobEntry {
/** Base path of the glob */
base: string
/** Glob pattern */
pattern: string
}
export interface ScannerOptions {
/** Automatically detect sources in the base path */
detectSources?: DetectSources
/** Glob sources */
sources?: Array<GlobEntry>
}
export declare class Scanner {
constructor(opts: ScannerOptions)
scan(): Array<string>
scanFiles(input: Array<ChangedContent>): Array<string>
get files(): Array<string>
get globs(): Array<GlobEntry>
}
```
The `scanFiles(…)` method is used for incremental rebuilds. It takes the
`ChangedContent` array for all the new/changes files. It returns whether
we scanned any new candidates or not.
Note that the `scanner` object is stateful, this means that we don't
have to track candidates in a `Set` anymore. We can just call
`getCandidates()` when we need it.
This PR also removed some unused code that we had in the `scanDir(…)`
function to allow for sequential or parallel `IO`, and sequential or
parallel `Parsing`. We only used the same `IO` and `Parsing` strategies
for all files, so I just got rid of it.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-08-16 15:05:42 +02:00
|
|
|
//
|
|
|
|
|
let candidates = scanner.scan()
|
|
|
|
|
|
2024-08-07 16:38:44 +02:00
|
|
|
// Add all found files as direct dependencies
|
Improve Oxide scanner API (#14187)
This PR updates the API for interacting with the Oxide API. Until now,
we used the name `scanDir(…)` which is fine, but we do way more work
right now.
We now have features such as:
1. Auto source detection (can be turned off, e.g.: `@tailwindcss/vite`
doesn't need it)
2. Scan based on `@source`s found in CSS files
3. Do "incremental" rebuilds (which means that the `scanDir(…)` result
was stateful).
To solve these issues, this PR introduces a new `Scanner` class where
you can pass in the `detectSources` and `sources` options. E.g.:
```ts
let scanner = new Scanner({
// Optional, omitting `detectSources` field disables automatic source detection
detectSources: { base: __dirname },
// List of glob entries to scan. These come from `@source` directives in CSS.
sources: [
{ base: __dirname, pattern: "src/**/*.css" },
// …
],
});
```
The scanner object has the following API:
```ts
export interface ChangedContent {
/** File path to the changed file */
file?: string
/** Contents of the changed file */
content?: string
/** File extension */
extension: string
}
export interface DetectSources {
/** Base path to start scanning from */
base: string
}
export interface GlobEntry {
/** Base path of the glob */
base: string
/** Glob pattern */
pattern: string
}
export interface ScannerOptions {
/** Automatically detect sources in the base path */
detectSources?: DetectSources
/** Glob sources */
sources?: Array<GlobEntry>
}
export declare class Scanner {
constructor(opts: ScannerOptions)
scan(): Array<string>
scanFiles(input: Array<ChangedContent>): Array<string>
get files(): Array<string>
get globs(): Array<GlobEntry>
}
```
The `scanFiles(…)` method is used for incremental rebuilds. It takes the
`ChangedContent` array for all the new/changes files. It returns whether
we scanned any new candidates or not.
Note that the `scanner` object is stateful, this means that we don't
have to track candidates in a `Set` anymore. We can just call
`getCandidates()` when we need it.
This PR also removed some unused code that we had in the `scanDir(…)`
function to allow for sequential or parallel `IO`, and sequential or
parallel `Parsing`. We only used the same `IO` and `Parsing` strategies
for all files, so I just got rid of it.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-08-16 15:05:42 +02:00
|
|
|
for (let file of scanner.files) {
|
2024-08-07 16:38:44 +02:00
|
|
|
result.messages.push({
|
|
|
|
|
type: 'dependency',
|
|
|
|
|
plugin: '@tailwindcss/postcss',
|
|
|
|
|
file,
|
|
|
|
|
parent: result.opts.from,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Register dependencies so changes in `base` cause a rebuild while
|
|
|
|
|
// giving tools like Vite or Parcel a glob that can be used to limit
|
|
|
|
|
// the files that cause a rebuild to only those that match it.
|
Improve Oxide scanner API (#14187)
This PR updates the API for interacting with the Oxide API. Until now,
we used the name `scanDir(…)` which is fine, but we do way more work
right now.
We now have features such as:
1. Auto source detection (can be turned off, e.g.: `@tailwindcss/vite`
doesn't need it)
2. Scan based on `@source`s found in CSS files
3. Do "incremental" rebuilds (which means that the `scanDir(…)` result
was stateful).
To solve these issues, this PR introduces a new `Scanner` class where
you can pass in the `detectSources` and `sources` options. E.g.:
```ts
let scanner = new Scanner({
// Optional, omitting `detectSources` field disables automatic source detection
detectSources: { base: __dirname },
// List of glob entries to scan. These come from `@source` directives in CSS.
sources: [
{ base: __dirname, pattern: "src/**/*.css" },
// …
],
});
```
The scanner object has the following API:
```ts
export interface ChangedContent {
/** File path to the changed file */
file?: string
/** Contents of the changed file */
content?: string
/** File extension */
extension: string
}
export interface DetectSources {
/** Base path to start scanning from */
base: string
}
export interface GlobEntry {
/** Base path of the glob */
base: string
/** Glob pattern */
pattern: string
}
export interface ScannerOptions {
/** Automatically detect sources in the base path */
detectSources?: DetectSources
/** Glob sources */
sources?: Array<GlobEntry>
}
export declare class Scanner {
constructor(opts: ScannerOptions)
scan(): Array<string>
scanFiles(input: Array<ChangedContent>): Array<string>
get files(): Array<string>
get globs(): Array<GlobEntry>
}
```
The `scanFiles(…)` method is used for incremental rebuilds. It takes the
`ChangedContent` array for all the new/changes files. It returns whether
we scanned any new candidates or not.
Note that the `scanner` object is stateful, this means that we don't
have to track candidates in a `Set` anymore. We can just call
`getCandidates()` when we need it.
This PR also removed some unused code that we had in the `scanDir(…)`
function to allow for sequential or parallel `IO`, and sequential or
parallel `Parsing`. We only used the same `IO` and `Parsing` strategies
for all files, so I just got rid of it.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-08-16 15:05:42 +02:00
|
|
|
for (let { base, pattern } of scanner.globs) {
|
2024-08-07 16:38:44 +02:00
|
|
|
result.messages.push({
|
|
|
|
|
type: 'dir-dependency',
|
|
|
|
|
plugin: '@tailwindcss/postcss',
|
|
|
|
|
dir: base,
|
|
|
|
|
glob: pattern,
|
|
|
|
|
parent: result.opts.from,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rebuildStrategy === 'full') {
|
2024-08-08 17:49:06 +02:00
|
|
|
context.compiler = await createCompiler()
|
Improve Oxide scanner API (#14187)
This PR updates the API for interacting with the Oxide API. Until now,
we used the name `scanDir(…)` which is fine, but we do way more work
right now.
We now have features such as:
1. Auto source detection (can be turned off, e.g.: `@tailwindcss/vite`
doesn't need it)
2. Scan based on `@source`s found in CSS files
3. Do "incremental" rebuilds (which means that the `scanDir(…)` result
was stateful).
To solve these issues, this PR introduces a new `Scanner` class where
you can pass in the `detectSources` and `sources` options. E.g.:
```ts
let scanner = new Scanner({
// Optional, omitting `detectSources` field disables automatic source detection
detectSources: { base: __dirname },
// List of glob entries to scan. These come from `@source` directives in CSS.
sources: [
{ base: __dirname, pattern: "src/**/*.css" },
// …
],
});
```
The scanner object has the following API:
```ts
export interface ChangedContent {
/** File path to the changed file */
file?: string
/** Contents of the changed file */
content?: string
/** File extension */
extension: string
}
export interface DetectSources {
/** Base path to start scanning from */
base: string
}
export interface GlobEntry {
/** Base path of the glob */
base: string
/** Glob pattern */
pattern: string
}
export interface ScannerOptions {
/** Automatically detect sources in the base path */
detectSources?: DetectSources
/** Glob sources */
sources?: Array<GlobEntry>
}
export declare class Scanner {
constructor(opts: ScannerOptions)
scan(): Array<string>
scanFiles(input: Array<ChangedContent>): Array<string>
get files(): Array<string>
get globs(): Array<GlobEntry>
}
```
The `scanFiles(…)` method is used for incremental rebuilds. It takes the
`ChangedContent` array for all the new/changes files. It returns whether
we scanned any new candidates or not.
Note that the `scanner` object is stateful, this means that we don't
have to track candidates in a `Set` anymore. We can just call
`getCandidates()` when we need it.
This PR also removed some unused code that we had in the `scanDir(…)`
function to allow for sequential or parallel `IO`, and sequential or
parallel `Parsing`. We only used the same `IO` and `Parsing` strategies
for all files, so I just got rid of it.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-08-16 15:05:42 +02:00
|
|
|
css = context.compiler.build(hasTailwind ? candidates : [])
|
2024-08-07 16:38:44 +02:00
|
|
|
} else if (rebuildStrategy === 'incremental') {
|
Improve Oxide scanner API (#14187)
This PR updates the API for interacting with the Oxide API. Until now,
we used the name `scanDir(…)` which is fine, but we do way more work
right now.
We now have features such as:
1. Auto source detection (can be turned off, e.g.: `@tailwindcss/vite`
doesn't need it)
2. Scan based on `@source`s found in CSS files
3. Do "incremental" rebuilds (which means that the `scanDir(…)` result
was stateful).
To solve these issues, this PR introduces a new `Scanner` class where
you can pass in the `detectSources` and `sources` options. E.g.:
```ts
let scanner = new Scanner({
// Optional, omitting `detectSources` field disables automatic source detection
detectSources: { base: __dirname },
// List of glob entries to scan. These come from `@source` directives in CSS.
sources: [
{ base: __dirname, pattern: "src/**/*.css" },
// …
],
});
```
The scanner object has the following API:
```ts
export interface ChangedContent {
/** File path to the changed file */
file?: string
/** Contents of the changed file */
content?: string
/** File extension */
extension: string
}
export interface DetectSources {
/** Base path to start scanning from */
base: string
}
export interface GlobEntry {
/** Base path of the glob */
base: string
/** Glob pattern */
pattern: string
}
export interface ScannerOptions {
/** Automatically detect sources in the base path */
detectSources?: DetectSources
/** Glob sources */
sources?: Array<GlobEntry>
}
export declare class Scanner {
constructor(opts: ScannerOptions)
scan(): Array<string>
scanFiles(input: Array<ChangedContent>): Array<string>
get files(): Array<string>
get globs(): Array<GlobEntry>
}
```
The `scanFiles(…)` method is used for incremental rebuilds. It takes the
`ChangedContent` array for all the new/changes files. It returns whether
we scanned any new candidates or not.
Note that the `scanner` object is stateful, this means that we don't
have to track candidates in a `Set` anymore. We can just call
`getCandidates()` when we need it.
This PR also removed some unused code that we had in the `scanDir(…)`
function to allow for sequential or parallel `IO`, and sequential or
parallel `Parsing`. We only used the same `IO` and `Parsing` strategies
for all files, so I just got rid of it.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-08-16 15:05:42 +02:00
|
|
|
css = context.compiler.build!(candidates)
|
2024-08-07 16:38:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Replace CSS
|
|
|
|
|
if (css !== context.css && optimize) {
|
|
|
|
|
context.optimizedCss = optimizeCss(css, {
|
|
|
|
|
minify: typeof optimize === 'object' ? optimize.minify : true,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
context.css = css
|
|
|
|
|
root.removeAll()
|
|
|
|
|
root.append(postcss.parse(optimize ? context.optimizedCss : context.css, result.opts))
|
|
|
|
|
},
|
2024-03-05 14:23:26 +01:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-13 17:25:16 +01:00
|
|
|
function optimizeCss(
|
|
|
|
|
input: string,
|
|
|
|
|
{ file = 'input.css', minify = false }: { file?: string; minify?: boolean } = {},
|
|
|
|
|
) {
|
|
|
|
|
return transform({
|
|
|
|
|
filename: file,
|
|
|
|
|
code: Buffer.from(input),
|
|
|
|
|
minify,
|
|
|
|
|
sourceMap: false,
|
|
|
|
|
drafts: {
|
|
|
|
|
customMedia: true,
|
|
|
|
|
},
|
|
|
|
|
nonStandard: {
|
|
|
|
|
deepSelectorCombinator: true,
|
|
|
|
|
},
|
|
|
|
|
include: Features.Nesting,
|
|
|
|
|
exclude: Features.LogicalProperties,
|
|
|
|
|
targets: {
|
|
|
|
|
safari: (16 << 16) | (4 << 8),
|
|
|
|
|
},
|
|
|
|
|
errorRecovery: true,
|
|
|
|
|
}).code.toString()
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-08 17:49:06 +02:00
|
|
|
// This is used instead of `export default` to work around a bug in `postcss-load-config`
|
|
|
|
|
module.exports = Object.assign(tailwindcss, { postcss: true }) as PluginCreator<PluginOptions>
|