UNPKG

@stylable/core

Version:

CSS for Components

433 lines 16.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createSymbolResolverWithCache = exports.StylableResolver = exports.isValidCSSResolve = void 0; const features_1 = require("./features"); const rule_1 = require("./helpers/rule"); const custom_values_1 = require("./custom-values"); function isValidCSSResolve(resolved) { return resolved.symbol !== undefined; } exports.isValidCSSResolve = isValidCSSResolve; function isInPath(extendPath, { symbol: { name: name1 }, meta: { source: source1 } }) { return extendPath.find(({ symbol: { name }, meta: { source } }) => { return name1 === name && source === source1; }); } // this is a safe cache key delimiter for all OS; const safePathDelimiter = ';:'; class StylableResolver { constructor(fileProcessor, requireModule, moduleResolver, cache) { this.fileProcessor = fileProcessor; this.requireModule = requireModule; this.moduleResolver = moduleResolver; this.cache = cache; } getModule({ context, request, }) { let entity; let resolvedPath; const key = cacheKey(context, request); if (this.cache?.has(key)) { const entity = this.cache.get(key); if (entity.kind === 'resolve') { resolvedPath = entity.resolvedPath; } else { return entity; } } try { resolvedPath || (resolvedPath = this.moduleResolver(context, request)); } catch (error) { entity = { kind: request.endsWith('css') ? 'css' : 'js', value: null, error, request, context, resolvedPath: undefined, }; this.cache?.set(key, entity); return entity; } if (resolvedPath.endsWith('.css')) { const kind = 'css'; try { entity = { kind, value: this.fileProcessor.process(resolvedPath), resolvedPath }; } catch (error) { entity = { kind, value: null, error, request, context, resolvedPath }; } } else { const kind = 'js'; try { entity = { kind, value: this.requireModule(resolvedPath), resolvedPath }; } catch (error) { entity = { kind, value: null, error, request, context, resolvedPath }; } } this.cache?.set(key, entity); return entity; } analyze(filePath) { return this.fileProcessor.process(filePath); } resolvePath(directoryPath, request) { const key = cacheKey(directoryPath, request); let resolvedPath = this.cache?.get(key)?.resolvedPath; if (resolvedPath !== undefined) { return resolvedPath; } resolvedPath = this.moduleResolver(directoryPath, request); this.cache?.set(key, { resolvedPath, value: null, kind: 'resolve', }); return resolvedPath; } resolveImported(imported, name, subtype = 'mappedSymbols') { const res = this.getModule(imported); if (res.value === null) { return null; } if (res.kind === 'css') { const { value: meta } = res; const namespace = subtype === `mappedSymbols` ? `main` : subtype === 'mappedKeyframes' ? `keyframes` : subtype; name = !name && namespace === `main` ? meta.root : name; const symbol = features_1.STSymbol.getAll(meta, namespace)[name]; return { _kind: 'css', symbol, meta, }; } else { const { value: jsModule } = res; return { _kind: 'js', symbol: !name ? jsModule.default || jsModule : jsModule[name], meta: null, }; } } resolveImport(importSymbol) { const name = importSymbol.type === 'named' ? importSymbol.name : ''; return this.resolveImported(importSymbol.import, name); } resolve(maybeImport) { if (!maybeImport || maybeImport._kind !== 'import') { if (maybeImport && maybeImport._kind !== 'var' && maybeImport._kind !== 'cssVar' && maybeImport._kind !== 'keyframes' && maybeImport._kind !== 'layer' && maybeImport._kind !== 'container') { if (maybeImport.alias && !maybeImport[`-st-extends`]) { maybeImport = maybeImport.alias; } else if (maybeImport[`-st-extends`]) { maybeImport = maybeImport[`-st-extends`]; } else { return null; } } else if (maybeImport && maybeImport._kind === 'cssVar') { if (maybeImport.alias) { maybeImport = maybeImport.alias; } else { return null; } } else { return null; } } if (!maybeImport || maybeImport._kind !== 'import') { return null; } return this.resolveImport(maybeImport); } deepResolve(maybeImport, path = []) { let resolved = this.resolve(maybeImport); while (resolved && resolved._kind === 'css' && resolved.symbol && resolved.symbol._kind === 'import') { resolved = this.resolve(resolved.symbol); } if (resolved && resolved.symbol && resolved.meta) { if (((resolved.symbol._kind === 'class' || resolved.symbol._kind === 'element') && resolved.symbol.alias && !resolved.symbol[`-st-extends`]) || (resolved.symbol._kind === 'cssVar' && resolved.symbol.alias)) { if (path.includes(resolved.symbol)) { return { _kind: 'css', symbol: resolved.symbol, meta: resolved.meta }; } path.push(resolved.symbol); return this.deepResolve(resolved.symbol.alias, path); } } return resolved; } resolveSymbolOrigin(symbol, meta, path = []) { if (!symbol || !meta) { return null; } if (symbol._kind === 'element' || symbol._kind === 'class') { if (path.includes(symbol)) { return { meta, symbol, _kind: 'css' }; } path.push(symbol); const isAliasOnly = symbol.alias && !symbol[`-st-extends`]; return isAliasOnly ? this.resolveSymbolOrigin(symbol.alias, meta, path) : { meta, symbol, _kind: 'css' }; } else if (symbol._kind === 'cssVar') { if (path.includes(symbol)) { return { meta, symbol, _kind: 'css' }; } } else if (symbol._kind === 'import') { const resolved = this.resolveImport(symbol); if (resolved && resolved.symbol && resolved._kind === 'css') { return this.resolveSymbolOrigin(resolved.symbol, resolved.meta, path); } else { return null; } } return null; } resolveSymbols(meta, diagnostics) { const resolvedSymbols = { mainNamespace: {}, class: {}, element: {}, var: {}, js: {}, customValues: { ...custom_values_1.stTypes }, keyframes: {}, layer: {}, container: {}, cssVar: {}, import: {}, }; // resolve main namespace for (const [name, symbol] of Object.entries(meta.getAllSymbols())) { let deepResolved; if (symbol._kind === `import` || (symbol._kind === `cssVar` && symbol.alias)) { deepResolved = this.deepResolve(symbol); if (!deepResolved || !deepResolved.symbol) { // diagnostics for unresolved imports are reported // as part of st-import validateImports continue; } else if (deepResolved?._kind === `js`) { resolvedSymbols.js[name] = deepResolved; resolvedSymbols.mainNamespace[name] = `js`; const customValueSymbol = deepResolved.symbol; if ((0, custom_values_1.isCustomValue)(customValueSymbol)) { resolvedSymbols.customValues[name] = customValueSymbol.register(name); } continue; } else if (symbol._kind !== `cssVar` && (deepResolved.symbol._kind === `class` || deepResolved.symbol._kind === `element`)) { // virtual alias deepResolved = { _kind: `css`, meta, symbol: features_1.CSSClass.createSymbol({ name, alias: symbol }), }; } } else { deepResolved = { _kind: `css`, meta, symbol }; } // deepResolved symbol is resolved by this point switch (deepResolved.symbol._kind) { case `class`: resolvedSymbols.class[name] = this.resolveExtends(meta, deepResolved.symbol, false, validateClassResolveExtends(meta, name, diagnostics, deepResolved)); break; case `element`: resolvedSymbols.element[name] = this.resolveExtends(meta, name, true); break; case `var`: resolvedSymbols.var[name] = deepResolved; break; case `cssVar`: resolvedSymbols.cssVar[name] = deepResolved; break; } resolvedSymbols.mainNamespace[name] = deepResolved.symbol._kind; } // resolve keyframes for (const [name, symbol] of Object.entries(features_1.CSSKeyframes.getAll(meta))) { const result = resolveByNamespace(meta, symbol, this, 'keyframes'); if (result) { resolvedSymbols.keyframes[name] = { _kind: `css`, meta: result.meta, symbol: result.symbol, }; } } // resolve layers for (const [name, symbol] of Object.entries(features_1.CSSLayer.getAll(meta))) { const result = resolveByNamespace(meta, symbol, this, 'layer'); if (result) { resolvedSymbols.layer[name] = { _kind: `css`, meta: result.meta, symbol: result.symbol, }; } } // resolve containers for (const [name, symbol] of Object.entries(features_1.CSSContains.getAll(meta))) { const result = resolveByNamespace(meta, symbol, this, 'container'); if (result) { resolvedSymbols.container[name] = { _kind: `css`, meta: result.meta, symbol: result.symbol, }; } } return resolvedSymbols; } resolveExtends(meta, nameOrSymbol, isElement = false, reportError) { const name = typeof nameOrSymbol === `string` ? nameOrSymbol : nameOrSymbol.name; const symbol = typeof nameOrSymbol === `string` ? isElement ? meta.getTypeElement(nameOrSymbol) : meta.getClass(nameOrSymbol) : nameOrSymbol; if (!symbol) { return []; } let current = { _kind: 'css', symbol, meta, }; const extendPath = []; while (current?.symbol) { if (isInPath(extendPath, current)) { break; } extendPath.push(current); const parent = current.symbol[`-st-extends`] || current.symbol.alias; if (parent) { if (parent._kind === 'import') { const res = this.resolve(parent); if (res && res._kind === 'css' && res.symbol && (res.symbol._kind === 'element' || res.symbol._kind === 'class')) { const { _kind, meta, symbol } = res; current = { _kind, meta, symbol, }; } else { if (reportError) { reportError(res, parent, extendPath, meta, name, isElement); } break; } } else { current = { _kind: 'css', symbol: parent, meta: current.meta }; } } else { break; } } return extendPath; } } exports.StylableResolver = StylableResolver; function cacheKey(context, request) { return `${context}${safePathDelimiter}${request}`; } function validateClassResolveExtends(meta, name, diagnostics, deepResolved) { return (res, extend) => { const decl = (0, rule_1.findRule)(meta.sourceAst, '.' + name); if (decl) { // ToDo: move to STExtends if (res && res._kind === 'js') { diagnostics.report(features_1.CSSClass.diagnostics.CANNOT_EXTEND_JS(), { node: decl, word: decl.value, }); } else if (res && !res.symbol) { diagnostics.report(features_1.CSSClass.diagnostics.CANNOT_EXTEND_UNKNOWN_SYMBOL(extend.name), { node: decl, word: decl.value, }); } else { diagnostics.report(features_1.CSSClass.diagnostics.IMPORT_ISNT_EXTENDABLE(), { node: decl, word: decl.value, }); } } else { if (deepResolved.symbol.alias) { meta.sourceAst.walkRules(new RegExp('\\.' + name), (rule) => { diagnostics.report(features_1.CSSClass.diagnostics.UNKNOWN_IMPORT_ALIAS(name), { node: rule, word: name, }); return false; }); } } }; } function createSymbolResolverWithCache(resolver, diagnostics) { const cache = new WeakMap(); return (meta) => { let symbols = cache.get(meta); if (!symbols) { symbols = resolver.resolveSymbols(meta, diagnostics); cache.set(meta, symbols); } return symbols; }; } exports.createSymbolResolverWithCache = createSymbolResolverWithCache; function resolveByNamespace(meta, symbol, resolver, type) { const current = { meta, symbol }; while ('import' in current.symbol && current.symbol.import) { const res = resolver.resolveImported(current.symbol.import, current.symbol.name, type); if (res?._kind === 'css' && res.symbol?._kind === type) { ({ meta: current.meta, symbol: current.symbol } = res); } else { return undefined; } } if (current.symbol?._kind === type) { return current; } return undefined; } //# sourceMappingURL=stylable-resolver.js.map