import crypto from 'crypto'; import path from 'path'; import autoprefixer from 'autoprefixer'; import postcss from 'postcss'; import postcssModules from 'postcss-modules'; import findUp from 'find-up'; import sass from 'sass'; import { Optimizer } from '../../@types/optimizer'; import type { TemplateNode } from '../../parser/interfaces'; type StyleType = 'css' | 'scss' | 'sass' | 'postcss'; const getStyleType: Map = new Map([ ['.css', 'css'], ['.pcss', 'postcss'], ['.sass', 'sass'], ['.scss', 'scss'], ['css', 'css'], ['postcss', 'postcss'], ['sass', 'sass'], ['scss', 'scss'], ['text/css', 'css'], ['text/postcss', 'postcss'], ['text/sass', 'sass'], ['text/scss', 'scss'], ]); const SASS_OPTIONS: Partial = { outputStyle: 'compressed', }; /** Should be deterministic, given a unique filename */ function hashFromFilename(filename: string): string { const hash = crypto.createHash('sha256'); return hash .update(filename.replace(/\\/g, '/')) .digest('base64') .toString() .replace(/[^A-Za-z0-9-]/g, '') .substr(0, 8); } export interface StyleTransformResult { css: string; cssModules: Map; type: StyleType; } // cache node_modules resolutions for each run. saves looking up the same directory over and over again. blown away on exit. const nodeModulesMiniCache = new Map(); async function transformStyle(code: string, { type, filename, fileID }: { type?: string; filename: string; fileID: string }): Promise { let styleType: StyleType = 'css'; // important: assume CSS as default if (type) { styleType = getStyleType.get(type) || styleType; } // add file path to includePaths let includePaths: string[] = [path.dirname(filename)]; // include node_modules to includePaths (allows @use-ing node modules, if it can be located) const cachedNodeModulesDir = nodeModulesMiniCache.get(filename); if (cachedNodeModulesDir) { includePaths.push(cachedNodeModulesDir); } else { const nodeModulesDir = await findUp('node_modules', { type: 'directory', cwd: path.dirname(filename) }); if (nodeModulesDir) { nodeModulesMiniCache.set(filename, nodeModulesDir); includePaths.push(nodeModulesDir); } } let css = ''; switch (styleType) { case 'css': { css = code; break; } case 'sass': case 'scss': { css = sass.renderSync({ ...SASS_OPTIONS, data: code, includePaths }).css.toString('utf8'); break; } case 'postcss': { css = code; // TODO break; } default: { throw new Error(`Unsupported: