@stylable/core
Version:
CSS for Components
158 lines (142 loc) • 4.54 kB
text/typescript
import { murmurhash3_32_gc } from '../murmurhash';
export const namespaceDelimiter = '__';
export function namespace(name: string, namespace: string) {
return namespace ? namespace + namespaceDelimiter + name : name;
}
export interface PackageInfo {
name: string;
version: string;
dirPath: string;
}
export interface NamespaceBuilderParams {
hashSalt: string;
prefix: string;
namespace: string;
paths: {
file: string;
origin: string;
};
packageInfo: PackageInfo;
}
export interface NamespaceBuilder {
(options: NamespaceBuilderParams):
| {
namespace: string;
hashPart: string;
}
| undefined
| null;
}
export interface CreateNamespaceOptions {
prefix?: string;
hashSalt?: string;
hashFragment?: 'full' | 'minimal' | number;
buildNamespace?: NamespaceBuilder;
getPackageInfo?: (filePath: string) => PackageInfo;
handleNoMatch?: (
strict: boolean,
namespace: string,
filePath: string,
usedByMap: Map<string, string>
) => string;
hashSeparator?: string;
strict?: boolean;
hashFn?: (i: string) => string | number;
normalizePath: (dirPath: string, filePath: string) => string;
}
function defaultGetPackageInfo() {
return {
name: '',
version: '0.0.0',
dirPath: '',
};
}
export function defaultNamespaceBuilder({
prefix,
namespace,
hashSalt,
paths,
packageInfo,
}: NamespaceBuilderParams) {
return {
namespace: prefix + namespace,
hashPart: hashSalt + packageInfo.name + '@' + packageInfo.version + '/' + paths.origin,
};
}
export function defaultNoMatchHandler(
strict: boolean,
ns: string,
stylesheetPath: string,
usedByMap: Map<string, string>
): string {
const usedBy = usedByMap.get(ns);
throw new Error(
`Could not create namespace for:\n${stylesheetPath}\nthe last valid namespace tried was ${JSON.stringify(
ns
)}${usedBy ? ` that was used by:\n${usedBy}\n` : strict ? ' ' : ''}${
strict ? 'running on strict mode' : ''
}`
);
}
export function createNamespaceStrategy(options: CreateNamespaceOptions) {
const {
prefix = '',
hashSalt = '',
hashFragment = 'minimal',
buildNamespace = defaultNamespaceBuilder,
getPackageInfo = defaultGetPackageInfo,
handleNoMatch = defaultNoMatchHandler,
hashSeparator = '-',
strict = false,
hashFn = murmurhash3_32_gc,
normalizePath,
} = options;
const usedNamespaces = new Map<string, string>();
return (
namespace: string,
stylesheetOriginPath: string,
stylesheetPath: string = stylesheetOriginPath
) => {
const packageInfo = getPackageInfo(stylesheetPath);
const buildNamespaceParams = {
prefix,
hashSalt,
namespace,
paths: {
file: packageInfo.dirPath
? normalizePath(packageInfo.dirPath, stylesheetPath)
: stylesheetPath,
origin: packageInfo.dirPath
? normalizePath(packageInfo.dirPath, stylesheetOriginPath)
: stylesheetOriginPath,
},
packageInfo,
};
const { namespace: resultNs, hashPart } =
buildNamespace(buildNamespaceParams) ?? defaultNamespaceBuilder(buildNamespaceParams);
const hashStr = hashFn(hashPart).toString();
let i =
typeof hashFragment === 'number'
? hashFragment
: hashFragment === 'full'
? hashStr.length
: 0;
let finalNamespace = '';
while (i <= hashStr.length) {
const hashSlice = hashStr.slice(0, i);
finalNamespace = resultNs + (hashSlice ? hashSeparator + hashSlice : '');
const usedBy = usedNamespaces.get(finalNamespace);
if (!usedBy) {
usedNamespaces.set(finalNamespace, stylesheetPath);
return finalNamespace;
}
if (usedBy === stylesheetPath) {
return finalNamespace;
} else if (strict) {
return handleNoMatch(strict, finalNamespace, stylesheetPath, usedNamespaces);
}
i++;
}
return handleNoMatch(strict, finalNamespace, stylesheetPath, usedNamespaces);
};
}