licss
Version:
gulp plugin. bundles, compiles, minimizes, rename and cleans up sass, scss, css, and postcss style sheets. use it for bootsrtap 5.3.6 (for the stop warning, sass v.1.78.0 was used here). made by pasmurno from llcawc
212 lines (211 loc) • 8.12 kB
JavaScript
import browserslist from 'browserslist';
import * as glob from 'glob';
import { browserslistToTargets, bundle, transform } from 'lightningcss';
import { Buffer } from 'node:buffer';
import { dirname, join, relative } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import PluginError from 'plugin-error';
import { PurgeCSS } from 'purgecss';
import { compileString } from 'sass';
import through2 from 'through2';
import rename from './rename.js';
export { rename };
/**
* Gulp plugin for style transformation - bundles, compiles, minimizes, and cleans up sass, scss, css, and postcss style sheets.
* @param options
*/
export default function licss(options = {}) {
return through2.obj(async function (file, _, cb) {
// empty
if (file.isNull()) {
return cb(null, file);
}
// exclude chunk files
if (file.stem.startsWith('_')) {
cb();
return;
}
if (file.isBuffer()) {
try {
const minify = options.minify ?? true; // normalize boolean
const sourceMap = !!file.sourceMap; // normalize boolean
// compile scss or sass files
if (file.extname === '.css' || file.extname === '.sass' || file.extname === '.scss') {
const syntax = file.extname === '.sass' ? 'indented' : 'scss';
const style = minify ? 'compressed' : 'expanded';
const loadPaths = options.loadPaths ?? [dirname(file.path), join(file.cwd, 'node_modules')];
const sassOptions = {
url: pathToFileURL(file.path),
loadPaths,
syntax,
style,
sourceMap,
sourceMapIncludeSources: true,
};
let fileContents = String(file.contents).toString();
// fix imports
fileContents = fileContents.replace(/@import +([url(]*)["']([./]*)([a-z-_/]+)\.?(.*)['"]\)?/gi, '@import "$2$3"');
// sass compile
const result = compileString(fileContents, sassOptions);
file.contents = Buffer.from(result.css);
file.extname = '.css';
const sassMap = result.sourceMap;
// add map in to file
if (sourceMap && !!sassMap) {
addSourceMap(file, sassMap, true);
}
// transform code with lightningcss for production
if (!sourceMap && minify) {
licssTransform(file, minify, sourceMap);
}
}
else {
// compile css and postcss file
licssBundle(file, minify, sourceMap);
}
// purgecss
if (options.purgeOptions && !sourceMap) {
await purge(file, options.purgeOptions);
}
}
catch (err) {
const opts = Object.assign({}, options, { fileName: file.path });
const error = new PluginError('licss', err, opts);
cb(error);
}
}
cb(null, file);
});
}
// transform string css code with lightningcss
function licssTransform(file, minify, sourceMap) {
if (file.isBuffer()) {
// compile css files
const targets = browserslist.loadConfig({ path: file.cwd })
? browserslistToTargets(browserslist(browserslist.loadConfig({ path: file.cwd }) ?? browserslist.defaults))
: browserslistToTargets(browserslist('> 0.2%, last 2 major versions, not dead'));
const filename = file.path;
const inputSourceMap = sourceMap ? JSON.stringify(file.sourceMap) : undefined;
const compileOptions = {
targets,
filename,
minify,
inputSourceMap,
sourceMap,
code: Buffer.from(file.contents.toString()),
projectRoot: file.base,
};
// compile css file with use lightningcss
const { code, map } = transform(compileOptions);
file.contents = Buffer.from(code.toString());
// if need map
if (sourceMap && !!map) {
const mapContent = JSON.parse(map.toString());
// mapContent.file = file.relative
// mapContent.sourceRoot = ''
// file.sourceMap = mapContent
// add map in to file
addSourceMap(file, mapContent, true);
}
}
}
// bundle and transforn css or postcss files with lightningcss
function licssBundle(file, minify, sourceMap) {
if (file.isBuffer()) {
const targets = browserslist.loadConfig({ path: file.cwd })
? browserslistToTargets(browserslist(browserslist.loadConfig({ path: file.cwd }) ?? browserslist.defaults))
: browserslistToTargets(browserslist('> 0.2%, last 2 major versions, not dead'));
const filename = file.path;
const compileOptions = {
targets,
filename,
minify,
sourceMap,
projectRoot: file.base,
};
// compile css file with use lightningcss
const { code, map } = bundle(compileOptions);
file.contents = Buffer.from(code.toString());
// add map in file
if (sourceMap && !!map) {
const mapContent = JSON.parse(map.toString());
addSourceMap(file, mapContent, true);
}
}
}
// fix sources and insert map in to file after sass compiler
function addSourceMap(file, map, relativePath) {
// insert "file": in to map
if (file.isBuffer()) {
if (!map.file) {
if (relativePath) {
map.file = file.relative;
}
else {
map.file = file.path;
}
}
// fix "file:" paths
if (/file:/i.test(map.file)) {
map.file = fileURLToPath(map.file);
}
// fix paths if Windows style paths
if (/\\/.test(map.file)) {
map.file = map.file.replace(/\\/g, '/');
}
// and fix "sources:" paths too
map.sources.map((path, index) => {
if (/file:/i.test(path)) {
path = fileURLToPath(path);
}
// make relative path if need
if (relativePath) {
let base = file.base;
if (/^\/?/.test(base)) {
base = file.base.replace(/^\/?/, '');
}
path = relative(base, path);
}
if (/\\/.test(path)) {
path = path.replace(/\\/g, '/');
}
map.sources[index] = path;
});
if (!file.sourceMap) {
throw new Error('sourcemap not enable');
}
else {
file.sourceMap = map;
}
}
}
function getFiles(contentArray, ignore) {
return contentArray.reduce((acc, content) => {
return [...acc, ...glob.sync(content, { ignore })];
}, []);
}
async function purge(file, options) {
// buffer
if (file.isBuffer()) {
const content = options.content;
const optionsGulp = {
...options,
content: getFiles(content, options.skippedContentGlobs),
css: [
{
raw: file.contents.toString(),
},
],
stdin: true,
sourceMap: false,
};
const purgedCSSResults = await new PurgeCSS().purge(optionsGulp);
const purge = purgedCSSResults[0];
const result = optionsGulp.rejected && purge.rejected ? purge.rejected.join(' {}\n') + ' {}' : purge.css;
file.contents = Buffer.from(result, 'utf-8');
// apply source map to the chain
if (file.sourceMap && purge.sourceMap) {
file.sourceMap = purge.sourceMap;
}
}
}