@amaui/style
Version:
CSS in JS styling solution
257 lines (214 loc) • 11.8 kB
JavaScript
import path from 'path';
import fg from 'fast-glob';
import fs from 'fs-extra';
import is from '@amaui/utils/is';
import getEnvironment from '@amaui/utils/getEnvironment';
import hash from '@amaui/utils/hash';
import merge from '@amaui/utils/merge';
import stringify from '@amaui/utils/stringify';
import to from '@amaui/utils/to';
import Try from '@amaui/utils/try';
import unique from '@amaui/utils/unique';
import AmauiNode from '@amaui/node';
import AmauiStyle from './AmauiStyle';
import AmauiTheme from './AmauiTheme';
import AmauiStyleSheetManager from './AmauiStyleSheetManager';
import { resetDefault, normalize } from './reset';
const optionsDefault = {
mode: 'regular',
amaui_style: {
get: AmauiStyle.first.bind(AmauiStyle)
},
amaui_theme: {
get: AmauiTheme.first.bind(AmauiTheme)
},
css: {
file: {
hash: true
},
minify: true
},
html: {
insert: {
comment: '<!-- amaui style insert -->'
},
add: true
},
rule: {
rtl: false,
prefix: true,
sort: true
},
log: true
};
const env = getEnvironment();
const getValuePaths = async function (value) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
onlyDirectories: true
};
const wd = process.cwd();
let paths = (is('array', value) ? value : [value]).map(item => (item === null || item === void 0 ? void 0 : item.url) || item).filter(Boolean).filter(item => is('string', item)).map(item => path.isAbsolute(item) ? item : path.join(wd, item));
paths = (await fg(paths, options)).map(filePath => path.resolve(filePath));
return unique(paths);
};
const minify = value => {
if (is('string', value)) return value.replace(/\n/g, '').replace(/ ?(\{|:|,|>|~) ?/g, '$1').replace(/;(\})/g, '$1');
return value;
};
function css(value__) {
let options_ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const options = merge(options_, optionsDefault, {
copy: true
}); // Amaui style
let amauiStyle = options.amaui_style.value || is('function', options.amaui_style.get) && options.amaui_style.get();
if (amauiStyle === undefined) amauiStyle = new AmauiStyle(); // Amaui theme
const amauiTheme = options.amaui_theme.value || is('function', options.amaui_theme.get) && options.amaui_theme.get(); // Make value if it's a function
let value_ = is('function', value__) ? Try(() => value__(amauiTheme)) : value__; // For reset, add reset to value
let method = 'css';
let priority = 'upper';
if (options.reset) {
var _options$resetProps;
method = 'reset'; // Default
const valueDefault = merge(resetDefault, normalize); // Add reset defaults
// user provided values override reset default values
if ((_options$resetProps = options.resetProps) !== null && _options$resetProps !== void 0 && _options$resetProps.override) value_ = { ...valueDefault,
...value_
};else value_ = merge(value_, valueDefault);
} // reset or pure update priority
if (options.pure) method = 'pure';
if (options.reset || options.pure) priority = 'lower'; // Make an instance of amauiStyleSheetManager
const amauiStyleSheetManager = new AmauiStyleSheetManager(value_, {
mode: options.mode,
pure: options.reset || options.pure,
priority,
amauiTheme,
amauiStyle,
rule: options.rule,
style: {
attributes: {
method
}
}
});
const responseManager = {
ids: amauiStyleSheetManager.ids,
amaui_style_sheet_manager: amauiStyleSheetManager,
sheets: amauiStyleSheetManager.sheets,
add: amauiStyleSheetManager.add.bind(amauiStyleSheetManager),
props: amauiStyleSheetManager.props,
update: amauiStyleSheetManager.update.bind(amauiStyleSheetManager),
remove: amauiStyleSheetManager.remove.bind(amauiStyleSheetManager),
addRule: amauiStyleSheetManager.sheets.static[0] && amauiStyleSheetManager.sheets.static[0].addRule.bind(amauiStyleSheetManager.sheets.static[0])
};
const response = {
make: async () => {
var _paths$folders$css;
const wd = process.cwd(); // add
const responseManagerValue = responseManager.add(); // All css
let value = amauiStyleSheetManager.css;
const minified = minify(value);
if (options.css.minify) value = minified; // CSS
// Make file name, only if at least 1 folder is provided
// and it exists
let made = false;
const paths = {
folders: {
css: await getValuePaths(options.css.folders)
},
files: {
html: await getValuePaths(options.html.files, {
onlyFiles: true
})
}
};
const madeFiles = {
css: []
};
const fileHash = hash(minified, {
withPrefix: false
});
if ((_paths$folders$css = paths.folders.css) !== null && _paths$folders$css !== void 0 && _paths$folders$css.length) {
var _options$css, _options$css$file, _options$css2, _options$css2$file;
const folders = paths.folders.css;
const name = "".concat(((_options$css = options.css) === null || _options$css === void 0 ? void 0 : (_options$css$file = _options$css.file) === null || _options$css$file === void 0 ? void 0 : _options$css$file.name) || env.amaui_methods.makeName.next().value).concat((_options$css2 = options.css) !== null && _options$css2 !== void 0 && (_options$css2$file = _options$css2.file) !== null && _options$css2$file !== void 0 && _options$css2$file.hash ? ".".concat(fileHash) : '', ".css"); // Clear folder if clear
const make = async (path_, index) => {
const folder = options.css.folders.find(item => path_ === (path.isAbsolute(item.url) ? item.url : path.join(wd, item.url)));
if (folder) {
const clear = folder.clear !== undefined ? folder.clear : options.css.clear; // Empty folder if clear
if (clear) await fs.emptyDir(path_); // Make the file
const nameWithPath = path.join(path_, name);
await AmauiNode.file.add(nameWithPath, value); // Add to files
if (index === 0 || !madeFiles.css.length) madeFiles.css.push(nameWithPath);
if (options.log) console.log("Made \x1B[34m".concat(nameWithPath, "\x1B[0m \x1B[32m").concat(to(value, 'size'), "\x1B[0m"));
}
}; // Make files in all the folders
if (options.log) console.log();
await Promise.all(folders.map((item, index) => make(item, index)));
if (options.log) console.log();
made = true;
} // HTML
// Only if css is made and in folders
// and html at least 1 file is provided and it exists
// and html add: true
if (made && paths.files.html.length && options.html.add) {
const files = paths.files.html;
const add = async path_ => {
const file = options.html.files.find(item => path_ === (path.isAbsolute(item.url) ? item.url : path.join(wd, item.url)));
if (file) {
var _file$insert, _options$html, _options$html$insert, _options$html2;
// Add path relative from the css file to the index path
const valueHTML = await AmauiNode.file.get(path_, false);
const values = valueHTML.split('\n');
const filesRelative = madeFiles.css.map(item => path.relative(path.dirname(path_), item)); // Add css files to folders
// Start from insert or file.insert comment or if none exists
// start from the first group of amaui inserted links, or
// start from first the bottom of head
const insert = ((_file$insert = file.insert) === null || _file$insert === void 0 ? void 0 : _file$insert.comment) || ((_options$html = options.html) === null || _options$html === void 0 ? void 0 : (_options$html$insert = _options$html.insert) === null || _options$html$insert === void 0 ? void 0 : _options$html$insert.comment);
const indexInsertComment = values.findIndex(item => item.indexOf(insert) > -1);
const indexAmauiInserts = values.findIndex(item => item.indexOf('<link') > -1 && item.indexOf("data-amaui='true'") > -1);
const indexBottomHead = values.findIndex(item => item.indexOf('</head>') > -1);
const indexBottomBody = values.findIndex(item => item.indexOf('</body>') > -1);
const indexStart = indexInsertComment > -1 && indexInsertComment + 1 || indexAmauiInserts > -1 && indexAmauiInserts || indexBottomHead;
const inBody = indexStart > indexBottomHead; // Adding order depends on if it's reset, pure or regular style
const allLinks = values.slice(indexStart, inBody ? indexBottomBody : indexBottomHead).filter(item => item.indexOf('<link') > -1);
const amauiLinks = allLinks.filter(item => item.indexOf("data-amaui='true'") > -1);
const amauiLinksPure = amauiLinks.filter(item => item.indexOf("data-pure='true'") > -1);
const amauiLinksNoReset = amauiLinks.filter(item => item.indexOf("data-reset='true'") === -1);
const amauiLinkReset = values.find(item => item.indexOf('<link') > -1 && item.indexOf("data-reset='true'") > -1);
const amauiLinkResetIndex = values.findIndex(item => item === amauiLinkReset);
let indexInsert = amauiLinkResetIndex > -1 ? amauiLinkResetIndex + 1 : indexStart;
if (!!amauiLinksNoReset.length && !options.reset) {
if (!options.pure) {
const amauiLinkLastIndex = values.findIndex(item => item === amauiLinksNoReset[amauiLinksNoReset.length - 1]);
indexInsert = amauiLinkLastIndex + 1;
} else {
const amauiLinkFirstIndex = values.findIndex(item => item === amauiLinksNoReset[0]);
if (!amauiLinksPure.length) indexInsert = amauiLinkFirstIndex;else {
const amauiLinkPureLast = values.findIndex(item => item === amauiLinksPure[amauiLinksPure.length - 1]);
indexInsert = amauiLinkPureLast + 1;
}
}
} // Take whitespace padding from other styles or links, or title or body + tab space
let padding = '';
const element = values[(inBody ? indexBottomBody : indexBottomHead) - 1];
const main = values[inBody ? indexBottomBody : indexBottomHead];
padding = element.split(/(?! )/)[0];
if (padding[0] !== ' ') padding = main.split(/(?! )/)[0];
if (padding[0] !== ' ') padding = ' ';
filesRelative.forEach(file_ => values.splice(indexInsert, 0, "".concat(padding, "<link rel='stylesheet' href='").concat(file_, "' data-amaui='true'").concat(options.reset ? " data-reset='true'" : '').concat(options.pure ? " data-pure='true'" : '', " />"))); // if not pure and reset
// get names and add (update) as a script global variable to window
// add at the bottom of head
if ((_options$html2 = options.html) !== null && _options$html2 !== void 0 && _options$html2.addNames && !options.pure && !options.reset) {
delete responseManagerValue.ids;
values.splice(indexBottomHead + 1, 0, "".concat(padding, "<script>\n").concat(padding, " if (!window.amauiStyleNames) window.amauiStyleNames = {};\n").concat(padding, "\n").concat(padding, " window.amauiStyleNames['").concat(fileHash, "'] = ").concat(stringify(responseManagerValue, padding.length + 2), ";\n").concat(padding, "</script>"));
} // Update the file
await AmauiNode.file.update(path_, values.join('\n'));
}
};
await Promise.all(files.map(item => add(item)));
}
}
}; // Response
return response;
}
export default css;