1 | var path = require("path")
|
2 | var assign = require("object-assign")
|
3 | var postcss = require("postcss")
|
4 | var joinMedia = require("./lib/join-media")
|
5 | var resolveId = require("./lib/resolve-id")
|
6 | var loadContent = require("./lib/load-content")
|
7 | var parseStatements = require("./lib/parse-statements")
|
8 |
|
9 | function AtImport(options) {
|
10 | options = assign({
|
11 | root: process.cwd(),
|
12 | path: [],
|
13 | skipDuplicates: true,
|
14 | resolve: resolveId,
|
15 | load: loadContent,
|
16 | plugins: [],
|
17 | }, options)
|
18 |
|
19 | options.root = path.resolve(options.root)
|
20 |
|
21 |
|
22 | if (typeof options.path === "string") {
|
23 | options.path = [ options.path ]
|
24 | }
|
25 |
|
26 | if (!Array.isArray(options.path)) {
|
27 | options.path = []
|
28 | }
|
29 |
|
30 | options.path = options.path.map(function(p) {
|
31 | return path.resolve(options.root, p)
|
32 | })
|
33 |
|
34 | return function(styles, result) {
|
35 | var state = {
|
36 | importedFiles: {},
|
37 | hashFiles: {},
|
38 | }
|
39 |
|
40 | if (styles.source && styles.source.input && styles.source.input.file) {
|
41 | state.importedFiles[styles.source.input.file] = {}
|
42 | }
|
43 |
|
44 | if (options.plugins && !Array.isArray(options.plugins)) {
|
45 | throw new Error("plugins option must be an array")
|
46 | }
|
47 |
|
48 | return parseStyles(
|
49 | result,
|
50 | styles,
|
51 | options,
|
52 | state,
|
53 | []
|
54 | ).then(function(bundle) {
|
55 |
|
56 | applyRaws(bundle)
|
57 | applyMedia(bundle)
|
58 | applyStyles(bundle, styles)
|
59 |
|
60 | if (
|
61 | typeof options.addDependencyTo === "object" &&
|
62 | typeof options.addDependencyTo.addDependency === "function"
|
63 | ) {
|
64 | Object.keys(state.importedFiles)
|
65 | .forEach(options.addDependencyTo.addDependency)
|
66 | }
|
67 |
|
68 | if (typeof options.onImport === "function") {
|
69 | options.onImport(Object.keys(state.importedFiles))
|
70 | }
|
71 | })
|
72 | }
|
73 | }
|
74 |
|
75 | function applyRaws(bundle) {
|
76 | bundle.forEach(function(stmt, index) {
|
77 | if (index === 0) {
|
78 | return
|
79 | }
|
80 |
|
81 | if (stmt.parent) {
|
82 | var before = stmt.parent.node.raws.before
|
83 | if (stmt.type === "nodes") {
|
84 | stmt.nodes[0].raws.before = before
|
85 | }
|
86 | else {
|
87 | stmt.node.raws.before = before
|
88 | }
|
89 | }
|
90 | else if (stmt.type === "nodes") {
|
91 | stmt.nodes[0].raws.before = stmt.nodes[0].raws.before || "\n"
|
92 | }
|
93 | })
|
94 | }
|
95 |
|
96 | function applyMedia(bundle) {
|
97 | bundle.forEach(function(stmt) {
|
98 | if (!stmt.media.length) {
|
99 | return
|
100 | }
|
101 | if (stmt.type === "import") {
|
102 | stmt.node.params = stmt.fullUri + " " + stmt.media.join(", ")
|
103 | }
|
104 | else if (stmt.type ==="media") {
|
105 | stmt.node.params = stmt.media.join(", ")
|
106 | }
|
107 | else {
|
108 | var nodes = stmt.nodes
|
109 | var parent = nodes[0].parent
|
110 | var mediaNode = postcss.atRule({
|
111 | name: "media",
|
112 | params: stmt.media.join(", "),
|
113 | source: parent.source,
|
114 | })
|
115 |
|
116 | parent.insertBefore(nodes[0], mediaNode)
|
117 |
|
118 |
|
119 | nodes.forEach(function(node) {
|
120 | node.parent = undefined
|
121 | })
|
122 |
|
123 |
|
124 | nodes[0].raws.before = nodes[0].raws.before || "\n"
|
125 |
|
126 |
|
127 | mediaNode.append(nodes)
|
128 |
|
129 | stmt.type = "media"
|
130 | stmt.node = mediaNode
|
131 | delete stmt.nodes
|
132 | }
|
133 | })
|
134 | }
|
135 |
|
136 | function applyStyles(bundle, styles) {
|
137 | styles.nodes = []
|
138 |
|
139 | bundle.forEach(function(stmt) {
|
140 | if (stmt.type === "import") {
|
141 | stmt.node.parent = undefined
|
142 | styles.append(stmt.node)
|
143 | }
|
144 | else if (stmt.type === "media") {
|
145 | stmt.node.parent = undefined
|
146 | styles.append(stmt.node)
|
147 | }
|
148 | else if (stmt.type === "nodes") {
|
149 | stmt.nodes.forEach(function(node) {
|
150 | node.parent = undefined
|
151 | styles.append(node)
|
152 | })
|
153 | }
|
154 | })
|
155 | }
|
156 |
|
157 | function parseStyles(
|
158 | result,
|
159 | styles,
|
160 | options,
|
161 | state,
|
162 | media
|
163 | ) {
|
164 | var statements = parseStatements(result, styles)
|
165 |
|
166 | return Promise.all(statements.map(function(stmt) {
|
167 | stmt.media = joinMedia(media, stmt.media)
|
168 |
|
169 |
|
170 | if (stmt.type !== "import" || /^(?:[a-z]+:)?\/\//i.test(stmt.uri)) {
|
171 | return
|
172 | }
|
173 | return resolveImportId(
|
174 | result,
|
175 | stmt,
|
176 | options,
|
177 | state
|
178 | )
|
179 | })).then(function() {
|
180 | var imports = []
|
181 | var bundle = []
|
182 |
|
183 |
|
184 | statements.forEach(function(stmt) {
|
185 | if (stmt.type === "import") {
|
186 | if (stmt.children) {
|
187 | stmt.children.forEach(function(child, index) {
|
188 | if (child.type === "import") {
|
189 | imports.push(child)
|
190 | }
|
191 | else {
|
192 | bundle.push(child)
|
193 | }
|
194 |
|
195 | if (index === 0) {
|
196 | child.parent = stmt
|
197 | }
|
198 | })
|
199 | }
|
200 | else {
|
201 | imports.push(stmt)
|
202 | }
|
203 | }
|
204 | else if (stmt.type === "media" || stmt.type === "nodes") {
|
205 | bundle.push(stmt)
|
206 | }
|
207 | })
|
208 |
|
209 | return imports.concat(bundle)
|
210 | })
|
211 | }
|
212 |
|
213 | function resolveImportId(
|
214 | result,
|
215 | stmt,
|
216 | options,
|
217 | state
|
218 | ) {
|
219 | var atRule = stmt.node
|
220 | var base = atRule.source && atRule.source.input && atRule.source.input.file
|
221 | ? path.dirname(atRule.source.input.file)
|
222 | : options.root
|
223 |
|
224 | return Promise.resolve(options.resolve(stmt.uri, base, options))
|
225 | .then(function(resolved) {
|
226 | if (!Array.isArray(resolved)) {
|
227 | resolved = [ resolved ]
|
228 | }
|
229 | return Promise.all(resolved.map(function(file) {
|
230 | return loadImportContent(
|
231 | result,
|
232 | stmt,
|
233 | file,
|
234 | options,
|
235 | state
|
236 | )
|
237 | }))
|
238 | })
|
239 | .then(function(result) {
|
240 |
|
241 | stmt.children = result.reduce(function(result, statements) {
|
242 | if (statements) {
|
243 | result = result.concat(statements)
|
244 | }
|
245 | return result
|
246 | }, [])
|
247 | })
|
248 | .catch(function(err) {
|
249 | result.warn(err.message, { node: atRule })
|
250 | })
|
251 | }
|
252 |
|
253 | function loadImportContent(
|
254 | result,
|
255 | stmt,
|
256 | filename,
|
257 | options,
|
258 | state
|
259 | ) {
|
260 | var atRule = stmt.node
|
261 | var media = stmt.media
|
262 | if (options.skipDuplicates) {
|
263 |
|
264 | if (
|
265 | state.importedFiles[filename] &&
|
266 | state.importedFiles[filename][media]
|
267 | ) {
|
268 | return
|
269 | }
|
270 |
|
271 |
|
272 | if (!state.importedFiles[filename]) {
|
273 | state.importedFiles[filename] = {}
|
274 | }
|
275 | state.importedFiles[filename][media] = true
|
276 | }
|
277 |
|
278 | return Promise.resolve(options.load(filename, options))
|
279 | .then(function(content) {
|
280 | if (typeof options.transform !== "function") {
|
281 | return content
|
282 | }
|
283 | return Promise.resolve(options.transform(content, filename, options))
|
284 | .then(function(transformed) {
|
285 | return typeof transformed === "string" ? transformed : content
|
286 | })
|
287 | })
|
288 | .then(function(content) {
|
289 | if (content.trim() === "") {
|
290 | result.warn(filename + " is empty", { node: atRule })
|
291 | return
|
292 | }
|
293 |
|
294 |
|
295 | if (
|
296 | state.hashFiles[content] &&
|
297 | state.hashFiles[content][media]
|
298 | ) {
|
299 | return
|
300 | }
|
301 |
|
302 | return postcss(options.plugins).process(content, {
|
303 | from: filename,
|
304 | syntax: result.opts.syntax,
|
305 | parser: result.opts.parser,
|
306 | })
|
307 | .then(function(importedResult) {
|
308 | var styles = importedResult.root
|
309 | result.messages = result.messages.concat(importedResult.messages)
|
310 |
|
311 | if (options.skipDuplicates) {
|
312 | var hasImport = styles.some(function(child) {
|
313 | return child.type === "atrule" && child.name === "import"
|
314 | })
|
315 | if (!hasImport) {
|
316 |
|
317 | if (!state.hashFiles[content]) {
|
318 | state.hashFiles[content] = {}
|
319 | }
|
320 | state.hashFiles[content][media] = true
|
321 | }
|
322 | }
|
323 |
|
324 |
|
325 | return parseStyles(
|
326 | result,
|
327 | styles,
|
328 | options,
|
329 | state,
|
330 | media
|
331 | )
|
332 | })
|
333 | })
|
334 | }
|
335 |
|
336 | module.exports = postcss.plugin(
|
337 | "postcss-import",
|
338 | AtImport
|
339 | )
|