123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- const postcss = require('postcss')
- const topologicalSort = require('./topologicalSort')
- const declWhitelist = ['composes']
- const declFilter = new RegExp(`^(${declWhitelist.join('|')})$`)
- const matchImports = /^(.+?)\s+from\s+(?:"([^"]+)"|'([^']+)'|(global))$/
- const icssImport = /^:import\((?:"([^"]+)"|'([^']+)')\)/
- const VISITED_MARKER = 1
- function createParentName(rule, root) {
- return `__${root.index(rule.parent)}_${rule.selector}`
- }
- function serializeImports(imports) {
- return imports.map(importPath => '`' + importPath + '`').join(', ')
- }
- /**
- * :import('G') {}
- *
- * Rule
- * composes: ... from 'A'
- * composes: ... from 'B'
- * Rule
- * composes: ... from 'A'
- * composes: ... from 'A'
- * composes: ... from 'C'
- *
- * Results in:
- *
- * graph: {
- * G: [],
- * A: [],
- * B: ['A'],
- * C: ['A'],
- * }
- */
- function addImportToGraph(importId, parentId, graph, visited) {
- const siblingsId = parentId + '_' + 'siblings'
- const visitedId = parentId + '_' + importId
- if (visited[visitedId] !== VISITED_MARKER) {
- if (!Array.isArray(visited[siblingsId])) visited[siblingsId] = []
- const siblings = visited[siblingsId]
- if (Array.isArray(graph[importId]))
- graph[importId] = graph[importId].concat(siblings)
- else graph[importId] = siblings.slice()
- visited[visitedId] = VISITED_MARKER
- siblings.push(importId)
- }
- }
- module.exports = postcss.plugin('modules-extract-imports', function(
- options = {}
- ) {
- const failOnWrongOrder = options.failOnWrongOrder
- return css => {
- const graph = {}
- const visited = {}
- const existingImports = {}
- const importDecls = {}
- const imports = {}
- let importIndex = 0
- const createImportedName = typeof options.createImportedName !== 'function'
- ? (importName /*, path*/) =>
- `i__imported_${importName.replace(/\W/g, '_')}_${importIndex++}`
- : options.createImportedName
- // Check the existing imports order and save refs
- css.walkRules(rule => {
- const matches = icssImport.exec(rule.selector)
- if (matches) {
- const [, /*match*/ doubleQuotePath, singleQuotePath] = matches
- const importPath = doubleQuotePath || singleQuotePath
- addImportToGraph(importPath, 'root', graph, visited)
- existingImports[importPath] = rule
- }
- })
- // Find any declaration that supports imports
- css.walkDecls(declFilter, decl => {
- let matches = decl.value.match(matchImports)
- let tmpSymbols
- if (matches) {
- let [
- ,
- /*match*/ symbols,
- doubleQuotePath,
- singleQuotePath,
- global
- ] = matches
- if (global) {
- // Composing globals simply means changing these classes to wrap them in global(name)
- tmpSymbols = symbols.split(/\s+/).map(s => `global(${s})`)
- } else {
- const importPath = doubleQuotePath || singleQuotePath
- const parentRule = createParentName(decl.parent, css)
- addImportToGraph(importPath, parentRule, graph, visited)
- importDecls[importPath] = decl
- imports[importPath] = imports[importPath] || {}
- tmpSymbols = symbols.split(/\s+/).map(s => {
- if (!imports[importPath][s]) {
- imports[importPath][s] = createImportedName(s, importPath)
- }
- return imports[importPath][s]
- })
- }
- decl.value = tmpSymbols.join(' ')
- }
- })
- const importsOrder = topologicalSort(graph, failOnWrongOrder)
- if (importsOrder instanceof Error) {
- const importPath = importsOrder.nodes.find(importPath =>
- importDecls.hasOwnProperty(importPath)
- )
- const decl = importDecls[importPath]
- const errMsg =
- 'Failed to resolve order of composed modules ' +
- serializeImports(importsOrder.nodes) +
- '.'
- throw decl.error(errMsg, {
- plugin: 'modules-extract-imports',
- word: 'composes'
- })
- }
- let lastImportRule
- importsOrder.forEach(path => {
- const importedSymbols = imports[path]
- let rule = existingImports[path]
- if (!rule && importedSymbols) {
- rule = postcss.rule({
- selector: `:import("${path}")`,
- raws: { after: '\n' }
- })
- if (lastImportRule) css.insertAfter(lastImportRule, rule)
- else css.prepend(rule)
- }
- lastImportRule = rule
- if (!importedSymbols) return
- Object.keys(importedSymbols).forEach(importedSymbol => {
- rule.append(
- postcss.decl({
- value: importedSymbol,
- prop: importedSymbols[importedSymbol],
- raws: { before: '\n ' }
- })
- )
- })
- })
- }
- })
|