UNPKG

@stylable/core

Version:

CSS for Components

694 lines 31.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.process = exports.processNamespace = exports.createEmptyMeta = exports.validateScopingSelector = exports.StylableProcessor = exports.processorWarnings = void 0; const murmurhash_1 = __importDefault(require("murmurhash")); const path_1 = __importDefault(require("path")); const postcss_1 = __importDefault(require("postcss")); const postcss_value_parser_1 = __importDefault(require("postcss-value-parser")); const diagnostics_1 = require("./diagnostics"); const selector_utils_1 = require("./selector-utils"); const stylable_assets_1 = require("./stylable-assets"); const stylable_meta_1 = require("./stylable-meta"); const stylable_utils_1 = require("./stylable-utils"); const stylable_value_parsers_1 = require("./stylable-value-parsers"); const utils_1 = require("./utils"); __exportStar(require("./stylable-meta"), exports); /* TEMP EXPORT */ const parseNamed = stylable_value_parsers_1.SBTypesParsers[stylable_value_parsers_1.valueMapping.named]; const parseMixin = stylable_value_parsers_1.SBTypesParsers[stylable_value_parsers_1.valueMapping.mixin]; const parseStates = stylable_value_parsers_1.SBTypesParsers[stylable_value_parsers_1.valueMapping.states]; const parseGlobal = stylable_value_parsers_1.SBTypesParsers[stylable_value_parsers_1.valueMapping.global]; const parseExtends = stylable_value_parsers_1.SBTypesParsers[stylable_value_parsers_1.valueMapping.extends]; exports.processorWarnings = { UNSCOPED_CLASS(name) { return `unscoped class "${name}" will affect all elements of the same type in the document`; }, UNSCOPED_ELEMENT(name) { return `unscoped element "${name}" will affect all elements of the same type in the document`; }, FORBIDDEN_DEF_IN_COMPLEX_SELECTOR(name) { return `cannot define "${name}" inside a complex selector`; }, ROOT_AFTER_SPACING() { return '".root" class cannot be used after native elements or selectors external to the stylesheet'; }, DEFAULT_IMPORT_IS_LOWER_CASE() { return 'Default import of a Stylable stylesheet must start with an upper-case letter'; }, ILLEGAL_PROP_IN_IMPORT(propName) { return `"${propName}" css attribute cannot be used inside ${stylable_value_parsers_1.rootValueMapping.import} block`; }, STATE_DEFINITION_IN_ELEMENT() { return 'cannot define pseudo states inside element selectors'; }, STATE_DEFINITION_IN_COMPLEX() { return 'cannot define pseudo states inside complex selectors'; }, REDECLARE_SYMBOL(name) { return `redeclare symbol "${name}"`; }, REDECLARE_SYMBOL_KEYFRAMES(name) { return `redeclare keyframes symbol "${name}"`; }, CANNOT_RESOLVE_EXTEND(name) { return `cannot resolve '${stylable_value_parsers_1.valueMapping.extends}' type for '${name}'`; }, CANNOT_EXTEND_IN_COMPLEX() { return `cannot define "${stylable_value_parsers_1.valueMapping.extends}" inside a complex selector`; }, UNKNOWN_MIXIN(name) { return `unknown mixin: "${name}"`; }, OVERRIDE_MIXIN() { return `override mixin on same rule`; }, OVERRIDE_TYPED_RULE(key, name) { return `override "${key}" on typed rule "${name}"`; }, FROM_PROP_MISSING_IN_IMPORT() { return `"${stylable_value_parsers_1.valueMapping.from}" is missing in ${stylable_value_parsers_1.rootValueMapping.import} block`; }, INVALID_NAMESPACE_DEF() { return 'invalid @namespace'; }, EMPTY_NAMESPACE_DEF() { return '@namespace must contain at least one character or digit'; }, EMPTY_IMPORT_FROM() { return '"-st-from" cannot be empty'; }, MULTIPLE_FROM_IN_IMPORT() { return `cannot define multiple "${stylable_value_parsers_1.valueMapping.from}" declarations in a single import`; }, NO_VARS_DEF_IN_ST_SCOPE() { return `cannot define "${stylable_value_parsers_1.rootValueMapping.vars}" inside of "@st-scope"`; }, NO_IMPORT_IN_ST_SCOPE() { return `cannot use "${stylable_value_parsers_1.rootValueMapping.import}" inside of "@st-scope"`; }, NO_KEYFRAMES_IN_ST_SCOPE() { return `cannot use "@keyframes" inside of "@st-scope"`; }, SCOPE_PARAM_NOT_SIMPLE_SELECTOR(selector) { return `"@st-scope" must receive a simple selector, but instead got: "${selector}"`; }, MISSING_SCOPING_PARAM() { return '"@st-scope" must receive a simple selector or stylesheet "root" as its scoping parameter'; }, ILLEGAL_GLOBAL_CSS_VAR(name) { return `"@st-global-custom-property" received the value "${name}", but it must begin with "--" (double-dash)`; }, GLOBAL_CSS_VAR_MISSING_COMMA(name) { return `"@st-global-custom-property" received the value "${name}", but its values must be comma separated`; }, ILLEGAL_CSS_VAR_USE(name) { return `a custom css property must begin with "--" (double-dash), but received "${name}"`; }, ILLEGAL_CSS_VAR_ARGS(name) { return `css variable "${name}" usage (var()) must receive comma separated values`; }, }; class StylableProcessor { constructor(diagnostics = new diagnostics_1.Diagnostics(), resolveNamespace = processNamespace) { this.diagnostics = diagnostics; this.resolveNamespace = resolveNamespace; } process(root) { this.meta = new stylable_meta_1.StylableMeta(root, this.diagnostics); this.handleAtRules(root); const stubs = this.insertCustomSelectorsStubs(); root.walkRules((rule) => { if (!selector_utils_1.isChildOfAtRule(rule, 'keyframes')) { this.handleCustomSelectors(rule); this.handleRule(rule, selector_utils_1.isChildOfAtRule(rule, stylable_value_parsers_1.rootValueMapping.stScope)); } }); root.walkDecls((decl) => { if (stylable_value_parsers_1.stValuesMap[decl.prop]) { this.handleDirectives(decl.parent, decl); } else if (stylable_utils_1.isCSSVarProp(decl.prop)) { this.addCSSVarFromProp(decl); } if (decl.value.includes('var(')) { this.handleCSSVarUse(decl); } stylable_assets_1.processDeclarationUrls(decl, (node) => { this.meta.urls.push(node.url); }, false); }); this.meta.scopes.forEach((atRule) => { const scopingRule = postcss_1.default.rule({ selector: atRule.params }); this.handleRule(scopingRule, true); validateScopingSelector(atRule, scopingRule, this.diagnostics); if (scopingRule.selector) { atRule.walkRules((rule) => { const scopedRule = rule.clone({ selector: stylable_utils_1.scopeSelector(scopingRule.selector, rule.selector, false) .selector, }); scopedRule.stScopeSelector = atRule.params; rule.replaceWith(scopedRule); }); } atRule.replaceWith(atRule.nodes || []); }); stubs.forEach((s) => s && s.remove()); return this.meta; } insertCustomSelectorsStubs() { return Object.keys(this.meta.customSelectors).map((selector) => { if (this.meta.customSelectors[selector]) { const rule = postcss_1.default.rule({ selector }); this.meta.ast.append(rule); return rule; } return null; }); } handleCustomSelectors(rule) { stylable_utils_1.expandCustomSelectors(rule, this.meta.customSelectors, this.meta.diagnostics); } handleAtRules(root) { let namespace = ''; const toRemove = []; root.walkAtRules((atRule) => { switch (atRule.name) { case 'namespace': { const match = atRule.params.match(/["'](.*?)['"]/); if (match) { if (match[1].trim()) { namespace = match[1]; } else { this.diagnostics.error(atRule, exports.processorWarnings.EMPTY_NAMESPACE_DEF()); } toRemove.push(atRule); } else { this.diagnostics.error(atRule, exports.processorWarnings.INVALID_NAMESPACE_DEF()); } break; } case 'keyframes': if (!selector_utils_1.isChildOfAtRule(atRule, stylable_value_parsers_1.rootValueMapping.stScope)) { this.meta.keyframes.push(atRule); const { params: name } = atRule; this.checkRedeclareKeyframes(name, atRule); this.meta.mappedKeyframes[name] = { _kind: 'keyframes', alias: name, name: name, }; } else { this.diagnostics.warn(atRule, exports.processorWarnings.NO_KEYFRAMES_IN_ST_SCOPE()); } break; case 'custom-selector': { const params = atRule.params.split(/\s/); const customName = params.shift(); toRemove.push(atRule); if (customName && customName.match(stylable_utils_1.CUSTOM_SELECTOR_RE)) { this.meta.customSelectors[customName] = atRule.params .replace(customName, '') .trim(); } else { // TODO: add warn there are two types one is not valid name and the other is empty name. } break; } case 'st-scope': this.meta.scopes.push(atRule); break; case 'st-global-custom-property': { const cssVars = atRule.params.split(','); if (atRule.params.trim().split(/\s+/g).length > cssVars.length) { this.diagnostics.warn(atRule, exports.processorWarnings.GLOBAL_CSS_VAR_MISSING_COMMA(atRule.params), { word: atRule.params }); break; } for (const entry of cssVars) { const cssVar = entry.trim(); if (stylable_utils_1.isCSSVarProp(cssVar)) { if (!this.meta.cssVars[cssVar]) { this.meta.cssVars[cssVar] = { _kind: 'cssVar', name: cssVar, global: true, }; this.meta.mappedSymbols[cssVar] = this.meta.cssVars[cssVar]; } } else { this.diagnostics.warn(atRule, exports.processorWarnings.ILLEGAL_GLOBAL_CSS_VAR(cssVar), { word: cssVar }); } } toRemove.push(atRule); break; } } }); toRemove.forEach((node) => node.remove()); namespace = namespace || utils_1.filename2varname(path_1.default.basename(this.meta.source)) || 's'; this.meta.namespace = this.handleNamespaceReference(namespace); } handleNamespaceReference(namespace) { let pathToSource; this.meta.ast.walkComments((comment) => { if (comment.text.includes('st-namespace-reference')) { const namespaceReferenceParts = comment.text.split('='); pathToSource = utils_1.stripQuotation(namespaceReferenceParts[namespaceReferenceParts.length - 1]); return false; } return undefined; }); return this.resolveNamespace(namespace, pathToSource ? path_1.default.resolve(path_1.default.dirname(this.meta.source), pathToSource) : this.meta.source); } handleRule(rule, inStScope = false) { rule.selectorAst = selector_utils_1.parseSelector(rule.selector); const checker = selector_utils_1.createSimpleSelectorChecker(); const validRoot = selector_utils_1.isRootValid(rule.selectorAst, 'root'); let locallyScoped = false; selector_utils_1.traverseNode(rule.selectorAst, (node, _index, _nodes) => { if (!checker(node)) { rule.isSimpleSelector = false; } const { name, type } = node; if (type === 'pseudo-class') { if (name === 'import') { if (rule.selector === stylable_value_parsers_1.rootValueMapping.import) { if (selector_utils_1.isChildOfAtRule(rule, stylable_value_parsers_1.rootValueMapping.stScope)) { this.diagnostics.warn(rule, exports.processorWarnings.NO_IMPORT_IN_ST_SCOPE()); rule.remove(); return false; } const _import = this.handleImport(rule); this.meta.imports.push(_import); this.addImportSymbols(_import); return false; } else { this.diagnostics.warn(rule, exports.processorWarnings.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR(stylable_value_parsers_1.rootValueMapping.import)); } } else if (name === 'vars') { if (rule.selector === stylable_value_parsers_1.rootValueMapping.vars) { if (selector_utils_1.isChildOfAtRule(rule, stylable_value_parsers_1.rootValueMapping.stScope)) { this.diagnostics.warn(rule, exports.processorWarnings.NO_VARS_DEF_IN_ST_SCOPE()); rule.remove(); return false; } this.addVarSymbols(rule); return false; } else { this.diagnostics.warn(rule, exports.processorWarnings.FORBIDDEN_DEF_IN_COMPLEX_SELECTOR(stylable_value_parsers_1.rootValueMapping.vars)); } } } else if (type === 'class') { this.addClassSymbolOnce(name, rule); if (this.meta.classes[name]) { if (!this.meta.classes[name].alias) { locallyScoped = true; } else if (locallyScoped === false && !inStScope) { this.diagnostics.warn(rule, exports.processorWarnings.UNSCOPED_CLASS(name), { word: name, }); } } } else if (type === 'element') { this.addElementSymbolOnce(name, rule); if (locallyScoped === false && !inStScope) { this.diagnostics.warn(rule, exports.processorWarnings.UNSCOPED_ELEMENT(name), { word: name, }); } } else if (type === 'nested-pseudo-class' && name === 'global') { return true; } return void 0; }); if (rule.isSimpleSelector !== false) { rule.isSimpleSelector = true; rule.selectorType = rule.selector.match(/^\./) ? 'class' : 'element'; } else { rule.selectorType = 'complex'; } if (!validRoot) { this.diagnostics.warn(rule, exports.processorWarnings.ROOT_AFTER_SPACING()); } } checkRedeclareSymbol(symbolName, node) { const symbol = this.meta.mappedSymbols[symbolName]; if (symbol) { this.diagnostics.warn(node, exports.processorWarnings.REDECLARE_SYMBOL(symbolName), { word: symbolName, }); } } checkRedeclareKeyframes(symbolName, node) { const symbol = this.meta.mappedKeyframes[symbolName]; if (symbol) { this.diagnostics.warn(node, exports.processorWarnings.REDECLARE_SYMBOL_KEYFRAMES(symbolName), { word: symbolName, }); } return symbol; } addElementSymbolOnce(name, rule) { if (selector_utils_1.isCompRoot(name) && !this.meta.elements[name]) { let alias = this.meta.mappedSymbols[name]; if (alias && alias._kind !== 'import') { this.checkRedeclareSymbol(name, rule); alias = undefined; } this.meta.elements[name] = this.meta.mappedSymbols[name] = { _kind: 'element', name, alias, }; this.meta.simpleSelectors[name] = { node: rule, symbol: this.meta.elements[name], }; } } addClassSymbolOnce(name, rule) { if (!this.meta.classes[name]) { let alias = this.meta.mappedSymbols[name]; if (alias && alias._kind !== 'import') { this.checkRedeclareSymbol(name, rule); alias = undefined; } this.meta.classes[name] = this.meta.mappedSymbols[name] = { _kind: 'class', name, alias, }; this.meta.simpleSelectors[name] = { node: rule, symbol: this.meta.mappedSymbols[name], }; } else if (name === this.meta.root && !this.meta.simpleSelectors[name]) { // special handling for registering "root" node comments this.meta.simpleSelectors[name] = { node: rule, symbol: this.meta.classes[name], }; } } addImportSymbols(importDef) { if (importDef.defaultExport) { this.checkRedeclareSymbol(importDef.defaultExport, importDef.rule); this.meta.mappedSymbols[importDef.defaultExport] = { _kind: 'import', type: 'default', name: 'default', import: importDef, context: path_1.default.dirname(this.meta.source), }; } Object.keys(importDef.named).forEach((name) => { this.checkRedeclareSymbol(name, importDef.rule); this.meta.mappedSymbols[name] = { _kind: 'import', type: 'named', name: importDef.named[name], import: importDef, context: path_1.default.dirname(this.meta.source), }; }); Object.keys(importDef.keyframes).forEach((name) => { if (!this.checkRedeclareKeyframes(name, importDef.rule)) { this.meta.mappedKeyframes[name] = { _kind: 'keyframes', alias: name, name: importDef.keyframes[name], import: importDef, }; } }); } addVarSymbols(rule) { rule.walkDecls((decl) => { this.checkRedeclareSymbol(decl.prop, decl); let type = null; const prev = decl.prev(); if (prev && prev.type === 'comment') { const typeMatch = prev.text.match(/^@type (.+)$/); if (typeMatch) { type = typeMatch[1]; } } const varSymbol = { _kind: 'var', name: decl.prop, value: '', text: decl.value, node: decl, valueType: type, }; this.meta.vars.push(varSymbol); this.meta.mappedSymbols[decl.prop] = varSymbol; }); rule.remove(); } handleCSSVarUse(decl) { const parsed = postcss_value_parser_1.default(decl.value); parsed.walk((node) => { if (node.type === 'function' && node.value === 'var' && node.nodes) { const varName = node.nodes[0]; if (!stylable_value_parsers_1.validateAllowedNodesUntil(node, 1)) { const args = postcss_value_parser_1.default.stringify(node.nodes); this.diagnostics.warn(decl, exports.processorWarnings.ILLEGAL_CSS_VAR_ARGS(args), { word: args, }); } this.addCSSVar(postcss_value_parser_1.default.stringify(varName).trim(), decl); } }); } addCSSVarFromProp(decl) { const varName = decl.prop.trim(); this.addCSSVar(varName, decl); } addCSSVar(varName, decl) { if (stylable_utils_1.isCSSVarProp(varName)) { if (!this.meta.cssVars[varName]) { const cssVarSymbol = { _kind: 'cssVar', name: varName, }; this.meta.cssVars[varName] = cssVarSymbol; if (!this.meta.mappedSymbols[varName]) { this.meta.mappedSymbols[varName] = cssVarSymbol; } } } else { this.diagnostics.warn(decl, exports.processorWarnings.ILLEGAL_CSS_VAR_USE(varName), { word: varName, }); } } handleDirectives(rule, decl) { if (decl.prop === stylable_value_parsers_1.valueMapping.states) { if (rule.isSimpleSelector && rule.selectorType !== 'element') { this.extendTypedRule(decl, rule.selector, stylable_value_parsers_1.valueMapping.states, parseStates(decl.value, decl, this.diagnostics)); } else { if (rule.selectorType === 'element') { this.diagnostics.warn(decl, exports.processorWarnings.STATE_DEFINITION_IN_ELEMENT()); } else { this.diagnostics.warn(decl, exports.processorWarnings.STATE_DEFINITION_IN_COMPLEX()); } } } else if (decl.prop === stylable_value_parsers_1.valueMapping.extends) { if (rule.isSimpleSelector) { const parsed = parseExtends(decl.value); const symbolName = parsed.types[0] && parsed.types[0].symbolName; const extendsRefSymbol = this.meta.mappedSymbols[symbolName]; if ((extendsRefSymbol && (extendsRefSymbol._kind === 'import' || extendsRefSymbol._kind === 'class' || extendsRefSymbol._kind === 'element')) || decl.value === this.meta.root) { this.extendTypedRule(decl, rule.selector, stylable_value_parsers_1.valueMapping.extends, stylable_utils_1.getAlias(extendsRefSymbol) || extendsRefSymbol); } else { this.diagnostics.warn(decl, exports.processorWarnings.CANNOT_RESOLVE_EXTEND(decl.value), { word: decl.value }); } } else { this.diagnostics.warn(decl, exports.processorWarnings.CANNOT_EXTEND_IN_COMPLEX()); } } else if (decl.prop === stylable_value_parsers_1.valueMapping.mixin) { const mixins = []; parseMixin(decl, (type) => { const mixinRefSymbol = this.meta.mappedSymbols[type]; if (mixinRefSymbol && mixinRefSymbol._kind === 'import' && !mixinRefSymbol.import.from.match(/.css$/)) { return 'args'; } return 'named'; }, this.diagnostics).forEach((mixin) => { const mixinRefSymbol = this.meta.mappedSymbols[mixin.type]; if (mixinRefSymbol && (mixinRefSymbol._kind === 'import' || mixinRefSymbol._kind === 'class')) { const refedMixin = { mixin, ref: mixinRefSymbol, }; mixins.push(refedMixin); this.meta.mixins.push(refedMixin); } else { this.diagnostics.warn(decl, exports.processorWarnings.UNKNOWN_MIXIN(mixin.type), { word: mixin.type, }); } }); if (rule.mixins) { this.diagnostics.warn(decl, exports.processorWarnings.OVERRIDE_MIXIN()); } rule.mixins = mixins; } else if (decl.prop === stylable_value_parsers_1.valueMapping.global) { if (rule.isSimpleSelector && rule.selectorType !== 'element') { this.setClassGlobalMapping(decl, rule); } else { // TODO: diagnostics - scoped on none class } } } setClassGlobalMapping(decl, rule) { const name = rule.selector.replace('.', ''); const typedRule = this.meta.classes[name]; if (typedRule) { typedRule[stylable_value_parsers_1.valueMapping.global] = parseGlobal(decl, this.diagnostics); } } extendTypedRule(node, selector, key, value) { const name = selector.replace('.', ''); const typedRule = this.meta.mappedSymbols[name]; if (typedRule && typedRule[key]) { this.diagnostics.warn(node, exports.processorWarnings.OVERRIDE_TYPED_RULE(key, name), { word: name, }); } if (typedRule) { typedRule[key] = value; } } handleImport(rule) { let fromExists = false; const importObj = { defaultExport: '', from: '', fromRelative: '', named: {}, keyframes: {}, rule, context: path_1.default.dirname(this.meta.source), }; rule.walkDecls((decl) => { switch (decl.prop) { case stylable_value_parsers_1.valueMapping.from: { const importPath = utils_1.stripQuotation(decl.value); if (!importPath.trim()) { this.diagnostics.error(decl, exports.processorWarnings.EMPTY_IMPORT_FROM()); } if (fromExists) { this.diagnostics.warn(rule, exports.processorWarnings.MULTIPLE_FROM_IN_IMPORT()); } if (!path_1.default.isAbsolute(importPath) && !importPath.startsWith('.')) { // 3rd party request importObj.fromRelative = importPath; importObj.from = importPath; } else { importObj.fromRelative = importPath; const dirPath = path_1.default.dirname(this.meta.source); importObj.from = path_1.default.posix && path_1.default.posix.isAbsolute(dirPath) // browser has no posix methods ? path_1.default.posix.resolve(dirPath, importPath) : path_1.default.resolve(dirPath, importPath); } fromExists = true; break; } case stylable_value_parsers_1.valueMapping.default: importObj.defaultExport = decl.value; if (!selector_utils_1.isCompRoot(importObj.defaultExport) && importObj.from.match(/\.css$/)) { this.diagnostics.warn(decl, exports.processorWarnings.DEFAULT_IMPORT_IS_LOWER_CASE(), { word: importObj.defaultExport }); } break; case stylable_value_parsers_1.valueMapping.named: { const { keyframesMap, namedMap } = parseNamed(decl.value, decl, this.diagnostics); importObj.named = namedMap; importObj.keyframes = keyframesMap; } break; default: this.diagnostics.warn(decl, exports.processorWarnings.ILLEGAL_PROP_IN_IMPORT(decl.prop), { word: decl.prop }); break; } }); if (!importObj.from) { this.diagnostics.error(rule, exports.processorWarnings.FROM_PROP_MISSING_IN_IMPORT()); } rule.remove(); return importObj; } } exports.StylableProcessor = StylableProcessor; function validateScopingSelector(atRule, { selector: scopingSelector, isSimpleSelector }, diagnostics) { if (!scopingSelector) { diagnostics.warn(atRule, exports.processorWarnings.MISSING_SCOPING_PARAM()); } else if (!isSimpleSelector) { diagnostics.warn(atRule, exports.processorWarnings.SCOPE_PARAM_NOT_SIMPLE_SELECTOR(scopingSelector), { word: scopingSelector }); } } exports.validateScopingSelector = validateScopingSelector; function createEmptyMeta(root, diagnostics) { utils_1.deprecated('createEmptyMeta is deprecated and will be removed in the next version. Use "new StylableMeta()"'); return new stylable_meta_1.StylableMeta(root, diagnostics); } exports.createEmptyMeta = createEmptyMeta; function processNamespace(namespace, source) { return namespace + murmurhash_1.default.v3(source); // .toString(36); } exports.processNamespace = processNamespace; function process(root, diagnostics = new diagnostics_1.Diagnostics(), resolveNamespace) { return new StylableProcessor(diagnostics, resolveNamespace).process(root); } exports.process = process; //# sourceMappingURL=stylable-processor.js.map