Skip to content

Commit

Permalink
async/await and less logic per file (#531)
Browse files Browse the repository at this point in the history
* async/await and less logic per file

* Apply suggestions from code review

Co-authored-by: Ryan Zimmerman <opensrc@ryanzim.com>

* Apply suggestions from code review

Co-authored-by: Ryan Zimmerman <opensrc@ryanzim.com>

* formatting and consistency

---------

Co-authored-by: Ryan Zimmerman <opensrc@ryanzim.com>
  • Loading branch information
romainmenke and RyanZim committed Jul 20, 2023
1 parent 0aaed50 commit fb2db6f
Show file tree
Hide file tree
Showing 11 changed files with 376 additions and 366 deletions.
377 changes: 17 additions & 360 deletions index.js

Large diffs are not rendered by default.

113 changes: 113 additions & 0 deletions lib/apply-media.js
@@ -0,0 +1,113 @@
"use strict"

const assignLayerNames = require("./assign-layer-names")

module.exports = function applyMedia(bundle, options, state, atRule) {
bundle.forEach(stmt => {
if ((!stmt.media.length && !stmt.layer.length) || stmt.type === "charset") {
return
}

if (stmt.layer.length > 1) {
assignLayerNames(stmt.layer, stmt.node, state, options)
}

if (stmt.type === "import") {
const parts = [stmt.fullUri]

const media = stmt.media.join(", ")

if (stmt.layer.length) {
const layerName = stmt.layer.join(".")

let layerParams = "layer"
if (layerName) {
layerParams = `layer(${layerName})`
}

parts.push(layerParams)
}

if (media) {
parts.push(media)
}

stmt.node.params = parts.join(" ")
} else if (stmt.type === "media") {
if (stmt.layer.length) {
const layerNode = atRule({
name: "layer",
params: stmt.layer.join("."),
source: stmt.node.source,
})

if (stmt.parentMedia?.length) {
const mediaNode = atRule({
name: "media",
params: stmt.parentMedia.join(", "),
source: stmt.node.source,
})

mediaNode.append(layerNode)
layerNode.append(stmt.node)
stmt.node = mediaNode
} else {
layerNode.append(stmt.node)
delete stmt.node
stmt.nodes = [layerNode]
stmt.type = "nodes"
}
} else {
stmt.node.params = stmt.media.join(", ")
}
} else {
const { nodes } = stmt
const { parent } = nodes[0]

const atRules = []

if (stmt.media.length) {
const mediaNode = atRule({
name: "media",
params: stmt.media.join(", "),
source: parent.source,
})

atRules.push(mediaNode)
}

if (stmt.layer.length) {
const layerNode = atRule({
name: "layer",
params: stmt.layer.join("."),
source: parent.source,
})

atRules.push(layerNode)
}

const outerAtRule = atRules.shift()
const innerAtRule = atRules.reduce((previous, next) => {
previous.append(next)
return next
}, outerAtRule)

parent.insertBefore(nodes[0], outerAtRule)

// remove nodes
nodes.forEach(node => {
node.parent = undefined
})

// better output
nodes[0].raws.before = nodes[0].raws.before || "\n"

// wrap new rules with media query and/or layer at rule
innerAtRule.append(nodes)

stmt.type = "media"
stmt.node = outerAtRule
delete stmt.nodes
}
})
}
15 changes: 15 additions & 0 deletions lib/apply-raws.js
@@ -0,0 +1,15 @@
"use strict"

module.exports = function applyRaws(bundle) {
bundle.forEach((stmt, index) => {
if (index === 0) return

if (stmt.parent) {
const { before } = stmt.parent.node.raws
if (stmt.type === "nodes") stmt.nodes[0].raws.before = before
else stmt.node.raws.before = before
} else if (stmt.type === "nodes") {
stmt.nodes[0].raws.before = stmt.nodes[0].raws.before || "\n"
}
})
}
18 changes: 18 additions & 0 deletions lib/apply-styles.js
@@ -0,0 +1,18 @@
"use strict"

module.exports = function applyStyles(bundle, styles) {
styles.nodes = []

// Strip additional statements.
bundle.forEach(stmt => {
if (["charset", "import", "media"].includes(stmt.type)) {
stmt.node.parent = undefined
styles.append(stmt.node)
} else if (stmt.type === "nodes") {
stmt.nodes.forEach(node => {
node.parent = undefined
styles.append(node)
})
}
})
}
2 changes: 1 addition & 1 deletion lib/assign-layer-names.js
@@ -1,6 +1,6 @@
"use strict"

module.exports = function (layer, node, state, options) {
module.exports = function assignLayerNames(layer, node, state, options) {
layer.forEach((layerPart, i) => {
if (layerPart.trim() === "") {
if (options.nameLayer) {
Expand Down
2 changes: 1 addition & 1 deletion lib/join-layer.js
@@ -1,6 +1,6 @@
"use strict"

module.exports = function (parentLayer, childLayer) {
module.exports = function joinLayer(parentLayer, childLayer) {
if (!parentLayer.length && childLayer.length) return childLayer
if (parentLayer.length && !childLayer.length) return parentLayer
if (!parentLayer.length && !childLayer.length) return []
Expand Down
2 changes: 1 addition & 1 deletion lib/join-media.js
Expand Up @@ -2,7 +2,7 @@

const startsWithKeywordRegexp = /^(all|not|only|print|screen)/i

module.exports = function (parentMedia, childMedia) {
module.exports = function joinMedia(parentMedia, childMedia) {
if (!parentMedia.length && childMedia.length) return childMedia
if (parentMedia.length && !childMedia.length) return parentMedia
if (!parentMedia.length && !childMedia.length) return []
Expand Down
2 changes: 1 addition & 1 deletion lib/load-content.js
Expand Up @@ -3,7 +3,7 @@
const readCache = require("read-cache")
const dataURL = require("./data-url")

module.exports = filename => {
module.exports = function loadContent(filename) {
if (dataURL.isValid(filename)) {
return dataURL.contents(filename)
}
Expand Down
2 changes: 1 addition & 1 deletion lib/parse-statements.js
Expand Up @@ -20,7 +20,7 @@ function split(params, start) {
return list
}

module.exports = function (result, styles) {
module.exports = function parseStatements(result, styles) {
const statements = []
let nodes = []

Expand Down
207 changes: 207 additions & 0 deletions lib/parse-styles.js
@@ -0,0 +1,207 @@
"use strict"

const path = require("path")

const assignLayerNames = require("./assign-layer-names")
const dataURL = require("./data-url")
const joinLayer = require("./join-layer")
const joinMedia = require("./join-media")
const parseStatements = require("./parse-statements")
const processContent = require("./process-content")
const resolveId = require("./resolve-id")

async function parseStyles(
result,
styles,
options,
state,
media,
layer,
postcss
) {
const statements = parseStatements(result, styles)

for (const stmt of statements) {
stmt.media = joinMedia(media, stmt.media || [])
stmt.parentMedia = media
stmt.layer = joinLayer(layer, stmt.layer || [])

// skip protocol base uri (protocol://url) or protocol-relative
if (stmt.type !== "import" || /^(?:[a-z]+:)?\/\//i.test(stmt.uri)) {
continue
}

if (options.filter && !options.filter(stmt.uri)) {
// rejected by filter
continue
}

await resolveImportId(result, stmt, options, state, postcss)
}

let charset
const imports = []
const bundle = []

function handleCharset(stmt) {
if (!charset) charset = stmt
// charsets aren't case-sensitive, so convert to lower case to compare
else if (
stmt.node.params.toLowerCase() !== charset.node.params.toLowerCase()
) {
throw new Error(
`Incompatable @charset statements:
${stmt.node.params} specified in ${stmt.node.source.input.file}
${charset.node.params} specified in ${charset.node.source.input.file}`
)
}
}

// squash statements and their children
statements.forEach(stmt => {
if (stmt.type === "charset") handleCharset(stmt)
else if (stmt.type === "import") {
if (stmt.children) {
stmt.children.forEach((child, index) => {
if (child.type === "import") imports.push(child)
else if (child.type === "charset") handleCharset(child)
else bundle.push(child)
// For better output
if (index === 0) child.parent = stmt
})
} else imports.push(stmt)
} else if (stmt.type === "media" || stmt.type === "nodes") {
bundle.push(stmt)
}
})

return charset ? [charset, ...imports.concat(bundle)] : imports.concat(bundle)
}

async function resolveImportId(result, stmt, options, state, postcss) {
if (dataURL.isValid(stmt.uri)) {
// eslint-disable-next-line require-atomic-updates
stmt.children = await loadImportContent(
result,
stmt,
stmt.uri,
options,
state,
postcss
)

return
}

const atRule = stmt.node
let sourceFile
if (atRule.source?.input?.file) {
sourceFile = atRule.source.input.file
}
const base = sourceFile
? path.dirname(atRule.source.input.file)
: options.root

const paths = [await options.resolve(stmt.uri, base, options)].flat()

// Ensure that each path is absolute:
const resolved = await Promise.all(
paths.map(file => {
return !path.isAbsolute(file) ? resolveId(file, base, options) : file
})
)

// Add dependency messages:
resolved.forEach(file => {
result.messages.push({
type: "dependency",
plugin: "postcss-import",
file,
parent: sourceFile,
})
})

const importedContent = await Promise.all(
resolved.map(file => {
return loadImportContent(result, stmt, file, options, state, postcss)
})
)

// Merge loaded statements
// eslint-disable-next-line require-atomic-updates
stmt.children = importedContent.flat().filter(x => !!x)
}

async function loadImportContent(
result,
stmt,
filename,
options,
state,
postcss
) {
const atRule = stmt.node
const { media, layer } = stmt

assignLayerNames(layer, atRule, state, options)

if (options.skipDuplicates) {
// skip files already imported at the same scope
if (state.importedFiles[filename]?.[media]?.[layer]) {
return
}

// save imported files to skip them next time
if (!state.importedFiles[filename]) {
state.importedFiles[filename] = {}
}
if (!state.importedFiles[filename][media]) {
state.importedFiles[filename][media] = {}
}
state.importedFiles[filename][media][layer] = true
}

const content = await options.load(filename, options)

if (content.trim() === "") {
result.warn(`${filename} is empty`, { node: atRule })
return
}

// skip previous imported files not containing @import rules
if (state.hashFiles[content]?.[media]?.[layer]) {
return
}

const importedResult = await processContent(
result,
content,
filename,
options,
postcss
)

const styles = importedResult.root
result.messages = result.messages.concat(importedResult.messages)

if (options.skipDuplicates) {
const hasImport = styles.some(child => {
return child.type === "atrule" && child.name === "import"
})
if (!hasImport) {
// save hash files to skip them next time
if (!state.hashFiles[content]) {
state.hashFiles[content] = {}
}
if (!state.hashFiles[content][media]) {
state.hashFiles[content][media] = {}
}
state.hashFiles[content][media][layer] = true
}
}

// recursion: import @import from imported file
return parseStyles(result, styles, options, state, media, layer, postcss)
}

module.exports = parseStyles

0 comments on commit fb2db6f

Please sign in to comment.