ng-devui
Version:
DevUI components based on Angular
188 lines (176 loc) • 6.26 kB
JavaScript
const { getOptions } = require('loader-utils');
const validateOptions = require('schema-utils');
const postcss = require('postcss');
const loaderName = 'devui-custom-theme-loader';
const optionsSchema = {
type: 'object',
properties: {
loaderType: {
anyOf: [{
enum: ['other', 'ng-lib-js', 'css', 'custom']
}]
},
theme: {
anyOf: [
{
instanceof: 'Object'
},
{
type: 'string'
}
]
},
themeType: {
anyOf: [
{
enum: ['json', 'string']
}
]
},
handleFallBack: {
anyOf: [
{
type: 'boolean'
},
{
enum: [
'enforce', // equal true, if no fallback, insert one
'replace', // if fallback exist, process it, otherwise, do nothing
'nothing', // equal false, handle var default value only
'remove', // remove fallback only exist, better work with handleVarValue = 'replace-var-expression'
'only' // remove var expression
]
}
]
}
},
additionalProperties: false
}
const defaultOptions = {
loaderType: 'other',
theme: {},
themeType: 'json',
handleFallBack: true
}
function getOptionsFromConfig(config) {
const rawOptions = getOptions(config)
if (rawOptions) {
validateOptions(optionsSchema, rawOptions, loaderName);
}
return Object.assign({}, defaultOptions, rawOptions);
}
/**
* 定位ng-packagr打包出来的库代码component装饰器元数据描述的styles位置
*
* @param {*} source 文件内容
* @returns 含有css的片段
*/
function locateNgComponentDecoratorStyleString(source) {
// TODO: 正则待进一步优化, 部分会过渡扫描跨多行
const reg = /(?<=\{\s*?type\:\s*?(?:core\.)?Component,\s*?args\:\s*?\[(?:\s|\S)*?\n\s*?styles\:\s*?)\[\"(?:.)*?\"\],?\n/g;
const regNg9 = /(?<=__decorate\(\[\s*?(?:core\.)?Component\(\{(?:\s|\S)*?\n\s*?styles\:\s*?)\[\"(?:.)*?\"\],?\n/g;
return (source.match(reg) || []).concat(source.match(regNg9) || []);
}
/**
* 获取主题数据
* @param {*} fileContent 文件内容
* @param {*} fileType json | string
*/
function getThemeDefinition(fileContent, fileType) {
if (fileType === 'string') {
return JSON.parse(fileContent);
} else {
return fileContent;
}
}
/**
*
* @param {*} css css文本内容
* @param {*} reg 匹配的正则
* @param {*} replaceFn 正则的替换函数
* @param {*} fallBackHandler 处理回落的正则和替换函数
* @param {} fallbackStrategy 策略
*/
function cssReplaceVarValue(css, reg, replaceFn, fallBackHandler = null, fallbackStrategy = 'enforce') {
const replaceOriginValue = (reg,replaceFn,fallBackHandler,fallbackStrategy) => {
return {
postcssPlugin: 'postcss-plugin-replace-var-value',
Once(root, { result }) {
root.walkDecls(decl => {
if (decl.type !== 'comment' && decl.value && decl.value.match(reg)) {
var newDecl = decl.clone({ value: decl.value.replace(reg, replaceFn) });
decl.replaceWith(newDecl);
if (fallBackHandler) {
var declPrev = newDecl.prev();
var regFallback = fallBackHandler.reg;
var replaceFnFallBack = fallBackHandler.replaceFn;
if (declPrev && declPrev.type !== 'comment' && declPrev.value
&& declPrev.toString() === decl.clone({ value: decl.value.replace(regFallback, replaceFnFallBack) }).toString()) {
if ('remove' === fallbackStrategy) {
declPrev.remove();
} else {
declPrev.replaceWith(newDecl.clone({ value: newDecl.value.replace(regFallback, replaceFnFallBack) }));
if ('only' === fallbackStrategy) {
newDecl.remove();
}
}
} else {
if ('enforce' === fallbackStrategy || true === fallbackStrategy || 'only' === fallbackStrategy) {
newDecl.cloneBefore({ value: newDecl.value.replace(regFallback, replaceFnFallBack) });
if ('only' === fallbackStrategy) {
newDecl.remove();
}
}
}
}
}
});
}
}
}
return postcss([replaceOriginValue(reg,replaceFn,fallBackHandler,fallbackStrategy)]).process(css).css;
}
function process(source, map) {
this.cacheable && this.cacheable();
// 获取配置文件里的主题数据
const option = getOptionsFromConfig(this);
const themeData = getThemeDefinition(option.theme, option.themeType);
let newSource = source;
if (Object.keys(themeData).length > 0) {
const themeDataVar = Object.keys(themeData).join('|');
const reg = new RegExp(String.raw`(?<=var\(\-\-(${themeDataVar}),)(?:.*?)(?=\))`, 'g');
const replaceFn = (match, varName, value) => themeData[varName];
const fallbackReg = new RegExp(String.raw`var\(\-\-(?:${themeDataVar}),(.*?)\)`, 'g');
const fallbackFn = (match, item) => item;
const fallbackHandler = option.handleFallBack && option.handleFallBack !== 'nothing'
? { reg: fallbackReg, replaceFn: fallbackFn }
: null;
// 处理针对文件类型定位处理的位置
switch (option.loaderType) {
case 'ng-lib-js':
const styleGroup = locateNgComponentDecoratorStyleString(source);
if (styleGroup) {
styleGroup.map(content => ({
content,
newContent: JSON.stringify(
JSON.parse(content).map(partCss => cssReplaceVarValue(partCss, reg, replaceFn, fallbackHandler, option.handleFallBack))
)
})).forEach(({ content, newContent }) => {
newSource = newSource.replace(content, newContent)
});
}
break;
case 'css':
newSource = cssReplaceVarValue(source, reg, replaceFn, fallbackHandler, option.handleFallBack);
break;
case 'other': // 不管源码内容直接替换, 暂不支持降级方案的处理
default:
newSource = source.replace(reg, replaceFn);
}
}
}
// 返回结果
this.callback(null, newSource, map);
return newSource;
}
exports.default = process;