tailwindcss/crates/node/src/lib.rs
Jordan Pittman 92a43d6904
Speed up template migrations (#14679)
This PR does two things:
- Computes UTF-16 string positions in Rust rather than in JS —
eliminating a significant number of traversals of the input string
- Applies replacements to the content in ascending order so we only ever
move forward through the source string — this lets v8 optimize string
concatenation
2024-10-16 13:13:48 +02:00

167 lines
3.4 KiB
Rust

use utf16::IndexConverter;
#[macro_use]
extern crate napi_derive;
mod utf16;
#[derive(Debug, Clone)]
#[napi(object)]
pub struct ChangedContent {
/// File path to the changed file
pub file: Option<String>,
/// Contents of the changed file
pub content: Option<String>,
/// File extension
pub extension: String,
}
#[derive(Debug, Clone)]
#[napi(object)]
pub struct DetectSources {
/// Base path to start scanning from
pub base: String,
}
#[derive(Debug, Clone)]
#[napi(object)]
pub struct GlobEntry {
/// Base path of the glob
pub base: String,
/// Glob pattern
pub pattern: String,
}
impl From<ChangedContent> for tailwindcss_oxide::ChangedContent {
fn from(changed_content: ChangedContent) -> Self {
Self {
file: changed_content.file.map(Into::into),
content: changed_content.content,
}
}
}
impl From<GlobEntry> for tailwindcss_oxide::GlobEntry {
fn from(glob: GlobEntry) -> Self {
Self {
base: glob.base,
pattern: glob.pattern,
}
}
}
impl From<tailwindcss_oxide::GlobEntry> for GlobEntry {
fn from(glob: tailwindcss_oxide::GlobEntry) -> Self {
Self {
base: glob.base,
pattern: glob.pattern,
}
}
}
impl From<DetectSources> for tailwindcss_oxide::scanner::detect_sources::DetectSources {
fn from(detect_sources: DetectSources) -> Self {
Self::new(detect_sources.base.into())
}
}
// ---
#[derive(Debug, Clone)]
#[napi(object)]
pub struct ScannerOptions {
/// Automatically detect sources in the base path
pub detect_sources: Option<DetectSources>,
/// Glob sources
pub sources: Option<Vec<GlobEntry>>,
}
#[derive(Debug, Clone)]
#[napi]
pub struct Scanner {
scanner: tailwindcss_oxide::Scanner,
}
#[derive(Debug, Clone)]
#[napi(object)]
pub struct CandidateWithPosition {
/// The candidate string
pub candidate: String,
/// The position of the candidate inside the content file
pub position: i64,
}
#[napi]
impl Scanner {
#[napi(constructor)]
pub fn new(opts: ScannerOptions) -> Self {
Self {
scanner: tailwindcss_oxide::Scanner::new(
opts.detect_sources.map(Into::into),
opts
.sources
.map(|x| x.into_iter().map(Into::into).collect()),
),
}
}
#[napi]
pub fn scan(&mut self) -> Vec<String> {
self.scanner.scan()
}
#[napi]
pub fn scan_files(&mut self, input: Vec<ChangedContent>) -> Vec<String> {
self
.scanner
.scan_content(input.into_iter().map(Into::into).collect())
}
#[napi]
pub fn get_candidates_with_positions(
&mut self,
input: ChangedContent,
) -> Vec<CandidateWithPosition> {
let content = input.content.unwrap_or_else(|| {
std::fs::read_to_string(&input.file.unwrap()).expect("Failed to read file")
});
let input = ChangedContent {
file: None,
content: Some(content.clone()),
extension: input.extension,
};
let mut utf16_idx = IndexConverter::new(&content[..]);
self
.scanner
.get_candidates_with_positions(input.into())
.into_iter()
.map(|(candidate, position)| CandidateWithPosition {
candidate,
position: utf16_idx.get(position),
})
.collect()
}
#[napi(getter)]
pub fn files(&mut self) -> Vec<String> {
self.scanner.get_files()
}
#[napi(getter)]
pub fn globs(&mut self) -> Vec<GlobEntry> {
self
.scanner
.get_globs()
.into_iter()
.map(Into::into)
.collect()
}
}