Skip to content

Commit c5679db

Browse files
authoredOct 13, 2020
Add support for postcss v8 (#432)
* added support for postcss v8 * Added a different sourcemap test for Windows * fix map in windows
1 parent d288ea3 commit c5679db

File tree

4 files changed

+233
-208
lines changed

4 files changed

+233
-208
lines changed
 

‎index.js

+222-206
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
// builtin tooling
33
const path = require("path")
44

5-
// external tooling
6-
const postcss = require("postcss")
7-
85
// internal tooling
96
const joinMedia = require("./lib/join-media")
107
const resolveId = require("./lib/resolve-id")
@@ -35,234 +32,253 @@ function AtImport(options) {
3532

3633
options.path = options.path.map(p => path.resolve(options.root, p))
3734

38-
return function(styles, result) {
39-
const state = {
40-
importedFiles: {},
41-
hashFiles: {},
42-
}
43-
44-
if (styles.source && styles.source.input && styles.source.input.file) {
45-
state.importedFiles[styles.source.input.file] = {}
46-
}
47-
48-
if (options.plugins && !Array.isArray(options.plugins)) {
49-
throw new Error("plugins option must be an array")
50-
}
51-
52-
return parseStyles(result, styles, options, state, []).then(bundle => {
53-
applyRaws(bundle)
54-
applyMedia(bundle)
55-
applyStyles(bundle, styles)
56-
})
57-
}
58-
}
59-
60-
function applyRaws(bundle) {
61-
bundle.forEach((stmt, index) => {
62-
if (index === 0) return
63-
64-
if (stmt.parent) {
65-
const before = stmt.parent.node.raws.before
66-
if (stmt.type === "nodes") stmt.nodes[0].raws.before = before
67-
else stmt.node.raws.before = before
68-
} else if (stmt.type === "nodes") {
69-
stmt.nodes[0].raws.before = stmt.nodes[0].raws.before || "\n"
70-
}
71-
})
72-
}
35+
return {
36+
postcssPlugin: "postcss-import",
37+
Once(styles, { result, atRule }) {
38+
const state = {
39+
importedFiles: {},
40+
hashFiles: {},
41+
}
7342

74-
function applyMedia(bundle) {
75-
bundle.forEach(stmt => {
76-
if (!stmt.media.length) return
77-
if (stmt.type === "import") {
78-
stmt.node.params = `${stmt.fullUri} ${stmt.media.join(", ")}`
79-
} else if (stmt.type === "media") stmt.node.params = stmt.media.join(", ")
80-
else {
81-
const nodes = stmt.nodes
82-
const parent = nodes[0].parent
83-
const mediaNode = postcss.atRule({
84-
name: "media",
85-
params: stmt.media.join(", "),
86-
source: parent.source,
87-
})
43+
if (styles.source && styles.source.input && styles.source.input.file) {
44+
state.importedFiles[styles.source.input.file] = {}
45+
}
8846

89-
parent.insertBefore(nodes[0], mediaNode)
47+
if (options.plugins && !Array.isArray(options.plugins)) {
48+
throw new Error("plugins option must be an array")
49+
}
9050

91-
// remove nodes
92-
nodes.forEach(node => {
93-
node.parent = undefined
51+
return parseStyles(result, styles, options, state, []).then(bundle => {
52+
applyRaws(bundle)
53+
applyMedia(bundle)
54+
applyStyles(bundle, styles)
9455
})
9556

96-
// better output
97-
nodes[0].raws.before = nodes[0].raws.before || "\n"
57+
function applyRaws(bundle) {
58+
bundle.forEach((stmt, index) => {
59+
if (index === 0) return
9860

99-
// wrap new rules with media query
100-
mediaNode.append(nodes)
61+
if (stmt.parent) {
62+
const before = stmt.parent.node.raws.before
63+
if (stmt.type === "nodes") stmt.nodes[0].raws.before = before
64+
else stmt.node.raws.before = before
65+
} else if (stmt.type === "nodes") {
66+
stmt.nodes[0].raws.before = stmt.nodes[0].raws.before || "\n"
67+
}
68+
})
69+
}
10170

102-
stmt.type = "media"
103-
stmt.node = mediaNode
104-
delete stmt.nodes
105-
}
106-
})
107-
}
71+
function applyMedia(bundle) {
72+
bundle.forEach(stmt => {
73+
if (!stmt.media.length) return
74+
if (stmt.type === "import") {
75+
stmt.node.params = `${stmt.fullUri} ${stmt.media.join(", ")}`
76+
} else if (stmt.type === "media")
77+
stmt.node.params = stmt.media.join(", ")
78+
else {
79+
const nodes = stmt.nodes
80+
const parent = nodes[0].parent
81+
const mediaNode = atRule({
82+
name: "media",
83+
params: stmt.media.join(", "),
84+
source: parent.source,
85+
})
10886

109-
function applyStyles(bundle, styles) {
110-
styles.nodes = []
111-
112-
// Strip additional statements.
113-
bundle.forEach(stmt => {
114-
if (stmt.type === "import") {
115-
stmt.node.parent = undefined
116-
styles.append(stmt.node)
117-
} else if (stmt.type === "media") {
118-
stmt.node.parent = undefined
119-
styles.append(stmt.node)
120-
} else if (stmt.type === "nodes") {
121-
stmt.nodes.forEach(node => {
122-
node.parent = undefined
123-
styles.append(node)
124-
})
125-
}
126-
})
127-
}
87+
parent.insertBefore(nodes[0], mediaNode)
12888

129-
function parseStyles(result, styles, options, state, media) {
130-
const statements = parseStatements(result, styles)
89+
// remove nodes
90+
nodes.forEach(node => {
91+
node.parent = undefined
92+
})
13193

132-
return Promise.resolve(statements)
133-
.then(stmts => {
134-
// process each statement in series
135-
return stmts.reduce((promise, stmt) => {
136-
return promise.then(() => {
137-
stmt.media = joinMedia(media, stmt.media || [])
94+
// better output
95+
nodes[0].raws.before = nodes[0].raws.before || "\n"
13896

139-
// skip protocol base uri (protocol://url) or protocol-relative
140-
if (stmt.type !== "import" || /^(?:[a-z]+:)?\/\//i.test(stmt.uri)) {
141-
return
142-
}
97+
// wrap new rules with media query
98+
mediaNode.append(nodes)
14399

144-
if (options.filter && !options.filter(stmt.uri)) {
145-
// rejected by filter
146-
return
100+
stmt.type = "media"
101+
stmt.node = mediaNode
102+
delete stmt.nodes
147103
}
148-
149-
return resolveImportId(result, stmt, options, state)
150-
})
151-
}, Promise.resolve())
152-
})
153-
.then(() => {
154-
const imports = []
155-
const bundle = []
156-
157-
// squash statements and their children
158-
statements.forEach(stmt => {
159-
if (stmt.type === "import") {
160-
if (stmt.children) {
161-
stmt.children.forEach((child, index) => {
162-
if (child.type === "import") imports.push(child)
163-
else bundle.push(child)
164-
// For better output
165-
if (index === 0) child.parent = stmt
166-
})
167-
} else imports.push(stmt)
168-
} else if (stmt.type === "media" || stmt.type === "nodes") {
169-
bundle.push(stmt)
170-
}
171-
})
172-
173-
return imports.concat(bundle)
174-
})
175-
}
176-
177-
function resolveImportId(result, stmt, options, state) {
178-
const atRule = stmt.node
179-
let sourceFile
180-
if (atRule.source && atRule.source.input && atRule.source.input.file) {
181-
sourceFile = atRule.source.input.file
182-
}
183-
const base = sourceFile
184-
? path.dirname(atRule.source.input.file)
185-
: options.root
186-
187-
return Promise.resolve(options.resolve(stmt.uri, base, options))
188-
.then(paths => {
189-
if (!Array.isArray(paths)) paths = [paths]
190-
// Ensure that each path is absolute:
191-
return Promise.all(
192-
paths.map(file => {
193-
return !path.isAbsolute(file) ? resolveId(file, base, options) : file
194104
})
195-
)
196-
})
197-
.then(resolved => {
198-
// Add dependency messages:
199-
resolved.forEach(file => {
200-
result.messages.push({
201-
type: "dependency",
202-
plugin: "postcss-import",
203-
file: file,
204-
parent: sourceFile,
205-
})
206-
})
105+
}
207106

208-
return Promise.all(
209-
resolved.map(file => {
210-
return loadImportContent(result, stmt, file, options, state)
107+
function applyStyles(bundle, styles) {
108+
styles.nodes = []
109+
110+
// Strip additional statements.
111+
bundle.forEach(stmt => {
112+
if (stmt.type === "import") {
113+
stmt.node.parent = undefined
114+
styles.append(stmt.node)
115+
} else if (stmt.type === "media") {
116+
stmt.node.parent = undefined
117+
styles.append(stmt.node)
118+
} else if (stmt.type === "nodes") {
119+
stmt.nodes.forEach(node => {
120+
node.parent = undefined
121+
styles.append(node)
122+
})
123+
}
211124
})
212-
)
213-
})
214-
.then(result => {
215-
// Merge loaded statements
216-
stmt.children = result.reduce((result, statements) => {
217-
return statements ? result.concat(statements) : result
218-
}, [])
219-
})
220-
}
125+
}
221126

222-
function loadImportContent(result, stmt, filename, options, state) {
223-
const atRule = stmt.node
224-
const media = stmt.media
225-
if (options.skipDuplicates) {
226-
// skip files already imported at the same scope
227-
if (state.importedFiles[filename] && state.importedFiles[filename][media]) {
228-
return
229-
}
230-
231-
// save imported files to skip them next time
232-
if (!state.importedFiles[filename]) state.importedFiles[filename] = {}
233-
state.importedFiles[filename][media] = true
234-
}
127+
function parseStyles(result, styles, options, state, media) {
128+
const statements = parseStatements(result, styles)
129+
130+
return Promise.resolve(statements)
131+
.then(stmts => {
132+
// process each statement in series
133+
return stmts.reduce((promise, stmt) => {
134+
return promise.then(() => {
135+
stmt.media = joinMedia(media, stmt.media || [])
136+
137+
// skip protocol base uri (protocol://url) or protocol-relative
138+
if (
139+
stmt.type !== "import" ||
140+
/^(?:[a-z]+:)?\/\//i.test(stmt.uri)
141+
) {
142+
return
143+
}
144+
145+
if (options.filter && !options.filter(stmt.uri)) {
146+
// rejected by filter
147+
return
148+
}
149+
150+
return resolveImportId(result, stmt, options, state)
151+
})
152+
}, Promise.resolve())
153+
})
154+
.then(() => {
155+
const imports = []
156+
const bundle = []
157+
158+
// squash statements and their children
159+
statements.forEach(stmt => {
160+
if (stmt.type === "import") {
161+
if (stmt.children) {
162+
stmt.children.forEach((child, index) => {
163+
if (child.type === "import") imports.push(child)
164+
else bundle.push(child)
165+
// For better output
166+
if (index === 0) child.parent = stmt
167+
})
168+
} else imports.push(stmt)
169+
} else if (stmt.type === "media" || stmt.type === "nodes") {
170+
bundle.push(stmt)
171+
}
172+
})
235173

236-
return Promise.resolve(options.load(filename, options)).then(content => {
237-
if (content.trim() === "") {
238-
result.warn(`${filename} is empty`, { node: atRule })
239-
return
240-
}
174+
return imports.concat(bundle)
175+
})
176+
}
241177

242-
// skip previous imported files not containing @import rules
243-
if (state.hashFiles[content] && state.hashFiles[content][media]) return
178+
function resolveImportId(result, stmt, options, state) {
179+
const atRule = stmt.node
180+
let sourceFile
181+
if (atRule.source && atRule.source.input && atRule.source.input.file) {
182+
sourceFile = atRule.source.input.file
183+
}
184+
const base = sourceFile
185+
? path.dirname(atRule.source.input.file)
186+
: options.root
187+
188+
return Promise.resolve(options.resolve(stmt.uri, base, options))
189+
.then(paths => {
190+
if (!Array.isArray(paths)) paths = [paths]
191+
// Ensure that each path is absolute:
192+
return Promise.all(
193+
paths.map(file => {
194+
return !path.isAbsolute(file)
195+
? resolveId(file, base, options)
196+
: file
197+
})
198+
)
199+
})
200+
.then(resolved => {
201+
// Add dependency messages:
202+
resolved.forEach(file => {
203+
result.messages.push({
204+
type: "dependency",
205+
plugin: "postcss-import",
206+
file: file,
207+
parent: sourceFile,
208+
})
209+
})
244210

245-
return processContent(result, content, filename, options).then(
246-
importedResult => {
247-
const styles = importedResult.root
248-
result.messages = result.messages.concat(importedResult.messages)
211+
return Promise.all(
212+
resolved.map(file => {
213+
return loadImportContent(result, stmt, file, options, state)
214+
})
215+
)
216+
})
217+
.then(result => {
218+
// Merge loaded statements
219+
stmt.children = result.reduce((result, statements) => {
220+
return statements ? result.concat(statements) : result
221+
}, [])
222+
})
223+
}
249224

225+
function loadImportContent(result, stmt, filename, options, state) {
226+
const atRule = stmt.node
227+
const media = stmt.media
250228
if (options.skipDuplicates) {
251-
const hasImport = styles.some(child => {
252-
return child.type === "atrule" && child.name === "import"
253-
})
254-
if (!hasImport) {
255-
// save hash files to skip them next time
256-
if (!state.hashFiles[content]) state.hashFiles[content] = {}
257-
state.hashFiles[content][media] = true
229+
// skip files already imported at the same scope
230+
if (
231+
state.importedFiles[filename] &&
232+
state.importedFiles[filename][media]
233+
) {
234+
return
258235
}
236+
237+
// save imported files to skip them next time
238+
if (!state.importedFiles[filename]) state.importedFiles[filename] = {}
239+
state.importedFiles[filename][media] = true
259240
}
260241

261-
// recursion: import @import from imported file
262-
return parseStyles(result, styles, options, state, media)
242+
return Promise.resolve(options.load(filename, options)).then(
243+
content => {
244+
if (content.trim() === "") {
245+
result.warn(`${filename} is empty`, { node: atRule })
246+
return
247+
}
248+
249+
// skip previous imported files not containing @import rules
250+
if (state.hashFiles[content] && state.hashFiles[content][media])
251+
return
252+
253+
return processContent(result, content, filename, options).then(
254+
importedResult => {
255+
const styles = importedResult.root
256+
result.messages = result.messages.concat(
257+
importedResult.messages
258+
)
259+
260+
if (options.skipDuplicates) {
261+
const hasImport = styles.some(child => {
262+
return child.type === "atrule" && child.name === "import"
263+
})
264+
if (!hasImport) {
265+
// save hash files to skip them next time
266+
if (!state.hashFiles[content]) state.hashFiles[content] = {}
267+
state.hashFiles[content][media] = true
268+
}
269+
}
270+
271+
// recursion: import @import from imported file
272+
return parseStyles(result, styles, options, state, media)
273+
}
274+
)
275+
}
276+
)
263277
}
264-
)
265-
})
278+
},
279+
}
266280
}
267281

268-
module.exports = postcss.plugin("postcss-import", AtImport)
282+
AtImport.postcss = true
283+
284+
module.exports = AtImport

‎package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
"node": ">=10.0.0"
2222
},
2323
"dependencies": {
24-
"postcss": "^7.0.1",
2524
"postcss-value-parser": "^3.2.3",
2625
"read-cache": "^1.0.0",
2726
"resolve": "^1.1.7"
@@ -32,10 +31,14 @@
3231
"eslint-config-i-am-meticulous": "^11.0.0",
3332
"eslint-plugin-import": "^2.17.1",
3433
"eslint-plugin-prettier": "^3.0.0",
34+
"postcss": "^8.0.0",
3535
"postcss-scss": "^2.0.0",
3636
"prettier": "~1.19.1",
3737
"sugarss": "^2.0.0"
3838
},
39+
"peerDependencies": {
40+
"postcss": "^8.0.0"
41+
},
3942
"scripts": {
4043
"ci": "eslint . && ava",
4144
"lint": "eslint . --fix",

‎test/import.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,12 @@ test("should contain a correct sourcemap", t => {
6565
.then(result => {
6666
t.is(
6767
result.map.toString(),
68-
readFileSync("test/sourcemap/out.css.map", "utf8").trim()
68+
readFileSync(
69+
process.platform === "win32"
70+
? "test/sourcemap/out.css.win.map"
71+
: "test/sourcemap/out.css.map",
72+
"utf8"
73+
).trim()
6974
)
7075
})
7176
})

‎test/sourcemap/out.css.win.map

+1
Original file line numberDiff line numberDiff line change

0 commit comments

Comments
 (0)
Please sign in to comment.