Skip to content
This repository was archived by the owner on Oct 14, 2026. It is now read-only.

Commit 511fafc

Browse files
author
James Kolce
committed
Move "import" package to the codebase for further customization
1 parent a30fe65 commit 511fafc

15 files changed

Lines changed: 897 additions & 4 deletions

‎package-lock.json‎

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,18 @@
2828
"postcss": "^6.0.13",
2929
"postcss-easy-import": "^3.0.0",
3030
"postcss-if-media": "^1.0.3",
31+
"postcss-import": "^11.0.0",
3132
"postcss-nested": "^2.1.2",
3233
"postcss-scss": "^1.0.2",
33-
"postcss-strip-inline-comments": "^0.1.5"
34+
"postcss-strip-inline-comments": "^0.1.5",
35+
"globby": "^6.1.0",
36+
"is-glob": "^4.0.0",
37+
"lodash": "^4.17.4",
38+
"object-assign": "^4.0.1",
39+
"pify": "^3.0.0",
40+
"resolve": "^1.1.7",
41+
"postcss-value-parser": "^3.2.3",
42+
"read-cache": "^1.0.0"
3443
},
3544
"devDependencies": {
3645
"@types/node": "^8.0.47",

‎src/index.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import * as nested from 'postcss-nested';
55
import * as ifMedia from 'postcss-if-media';
66
import * as scssSyntax from 'postcss-scss';
77
import * as stripComments from 'postcss-strip-inline-comments';
8-
import * as imports from 'postcss-easy-import';
98

9+
import imports from './lib/import';
1010
import lh from './lib/lh';
1111
import customMedia from './lib/custom-media';
1212
import mediaMinMax from './lib/media-minmax';
@@ -17,7 +17,7 @@ export default file => {
1717
const ccss = fs.readFileSync(file, 'utf8');
1818

1919
return postcss()
20-
.use(imports({ extensions: '.ccss', prefix: '_' }))
20+
.use(imports())
2121
.use(stripComments())
2222
.use(ifMedia())
2323
.use(nested())

‎src/lib/import/index.ts‎

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
// The MIT License (MIT)
2+
3+
// Copyright (c) Maxime Thirouin, Jason Campbell & Kevin Mårtensson
4+
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy of
6+
// this software and associated documentation files (the "Software"), to deal in
7+
// the Software without restriction, including without limitation the rights to
8+
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9+
// the Software, and to permit persons to whom the Software is furnished to do so,
10+
// subject to the following conditions:
11+
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17+
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18+
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19+
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20+
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
import * as path from 'path'
23+
import * as postcss from 'postcss'
24+
import * as postcssImport from 'postcss-import'
25+
import * as isGlob from 'is-glob'
26+
27+
import joinMedia from './lib/join-media'
28+
import resolveId from './lib/resolve-id'
29+
import loadContent from './lib/load-content'
30+
import processContent from './lib/process-content'
31+
import parseStatements from './lib/parse-statements'
32+
import resolveGlob from './lib/resolve-glob'
33+
import resolveModule from './lib/resolve-module'
34+
35+
function resolve(id) {
36+
const resolver = isGlob(id) ? resolveGlob : resolveModule
37+
return resolver.apply(null, arguments)
38+
}
39+
40+
function AtImport(options) {
41+
options = Object.assign(
42+
{
43+
root: process.cwd(),
44+
path: [],
45+
skipDuplicates: true,
46+
resolve: resolve,
47+
load: loadContent,
48+
plugins: [],
49+
addModulesDirectories: [],
50+
prefix: '_',
51+
extensions: ['.ccss']
52+
},
53+
options
54+
)
55+
56+
options.root = path.resolve(options.root)
57+
58+
// convert string to an array of a single element
59+
if (typeof options.path === "string") options.path = [options.path]
60+
61+
if (!Array.isArray(options.path)) options.path = []
62+
63+
options.path = options.path.map(p => path.resolve(options.root, p))
64+
65+
return function(styles, result) {
66+
const state = {
67+
importedFiles: {},
68+
hashFiles: {},
69+
}
70+
71+
if (styles.source && styles.source.input && styles.source.input.file) {
72+
state.importedFiles[styles.source.input.file] = {}
73+
}
74+
75+
if (options.plugins && !Array.isArray(options.plugins)) {
76+
throw new Error("plugins option must be an array")
77+
}
78+
79+
return parseStyles(result, styles, options, state, []).then(bundle => {
80+
applyRaws(bundle)
81+
applyMedia(bundle)
82+
applyStyles(bundle, styles)
83+
})
84+
}
85+
}
86+
87+
function applyRaws(bundle) {
88+
bundle.forEach((stmt, index) => {
89+
if (index === 0) return
90+
91+
if (stmt.parent) {
92+
const before = stmt.parent.node.raws.before
93+
if (stmt.type === "nodes") stmt.nodes[0].raws.before = before
94+
else stmt.node.raws.before = before
95+
} else if (stmt.type === "nodes") {
96+
stmt.nodes[0].raws.before = stmt.nodes[0].raws.before || "\n"
97+
}
98+
})
99+
}
100+
101+
function applyMedia(bundle) {
102+
bundle.forEach(stmt => {
103+
if (!stmt.media.length) return
104+
if (stmt.type === "import") {
105+
stmt.node.params = `${stmt.fullUri} ${stmt.media.join(", ")}`
106+
} else if (stmt.type === "media") stmt.node.params = stmt.media.join(", ")
107+
else {
108+
109+
const nodes = stmt.nodes
110+
const parent = nodes[0].parent
111+
const mediaNode = postcss.atRule({
112+
name: "media",
113+
params: stmt.media.join(", "),
114+
// I don't know what this does but isn't included in the type definition
115+
//source: parent.source
116+
})
117+
118+
parent.insertBefore(nodes[0], mediaNode)
119+
120+
// remove nodes
121+
nodes.forEach(node => {
122+
node.parent = undefined
123+
})
124+
125+
// better output
126+
nodes[0].raws.before = nodes[0].raws.before || "\n"
127+
128+
// wrap new rules with media query
129+
mediaNode.append(nodes)
130+
131+
stmt.type = "media"
132+
stmt.node = mediaNode
133+
delete stmt.nodes
134+
}
135+
})
136+
}
137+
138+
function applyStyles(bundle, styles) {
139+
styles.nodes = []
140+
141+
bundle.forEach(stmt => {
142+
if (stmt.type === "import") {
143+
stmt.node.parent = undefined
144+
styles.append(stmt.node)
145+
} else if (stmt.type === "media") {
146+
stmt.node.parent = undefined
147+
styles.append(stmt.node)
148+
} else if (stmt.type === "nodes") {
149+
stmt.nodes.forEach(node => {
150+
node.parent = undefined
151+
styles.append(node)
152+
})
153+
}
154+
})
155+
}
156+
157+
function parseStyles(result, styles, options, state, media) {
158+
const statements = parseStatements(result, styles)
159+
160+
return Promise.resolve(statements)
161+
.then(stmts => {
162+
// process each statement in series
163+
return stmts.reduce((promise, stmt) => {
164+
return promise.then(() => {
165+
stmt.media = joinMedia(media, stmt.media || [])
166+
167+
// skip protocol base uri (protocol://url) or protocol-relative
168+
if (stmt.type !== "import" || /^(?:[a-z]+:)?\/\//i.test(stmt.uri)) {
169+
return
170+
}
171+
172+
return resolveImportId(result, stmt, options, state)
173+
})
174+
}, Promise.resolve())
175+
})
176+
.then(() => {
177+
const imports = []
178+
const bundle = []
179+
180+
// squash statements and their children
181+
statements.forEach(stmt => {
182+
if (stmt.type === "import") {
183+
if (stmt.children) {
184+
stmt.children.forEach((child, index) => {
185+
if (child.type === "import") imports.push(child)
186+
else bundle.push(child)
187+
// For better output
188+
if (index === 0) child.parent = stmt
189+
})
190+
} else imports.push(stmt)
191+
} else if (stmt.type === "media" || stmt.type === "nodes") {
192+
bundle.push(stmt)
193+
}
194+
})
195+
196+
return imports.concat(bundle)
197+
})
198+
}
199+
200+
function resolveImportId(result, stmt, options, state) {
201+
const atRule = stmt.node
202+
let sourceFile
203+
if (atRule.source && atRule.source.input && atRule.source.input.file) {
204+
sourceFile = atRule.source.input.file
205+
}
206+
const base = sourceFile
207+
? path.dirname(atRule.source.input.file)
208+
: options.root
209+
210+
return Promise.resolve(options.resolve(stmt.uri, base, options))
211+
.then(paths => {
212+
if (!Array.isArray(paths)) paths = [paths]
213+
// Ensure that each path is absolute:
214+
return Promise.all(
215+
paths.map(file => {
216+
return !path.isAbsolute(file) ? resolveId(file, base, options) : file
217+
})
218+
)
219+
})
220+
.then(resolved => {
221+
// Add dependency messages:
222+
resolved.forEach(file => {
223+
result.messages.push({
224+
type: "dependency",
225+
file: file,
226+
parent: sourceFile,
227+
})
228+
})
229+
230+
return Promise.all(
231+
resolved.map(file => {
232+
return loadImportContent(result, stmt, file, options, state)
233+
})
234+
)
235+
})
236+
.then(result => {
237+
// Merge loaded statements
238+
stmt.children = result.reduce((result, statements) => {
239+
return statements ? result.concat(statements) : result
240+
}, [])
241+
})
242+
}
243+
244+
function loadImportContent(result, stmt, filename, options, state) {
245+
const atRule = stmt.node
246+
const media = stmt.media
247+
if (options.skipDuplicates) {
248+
// skip files already imported at the same scope
249+
if (state.importedFiles[filename] && state.importedFiles[filename][media]) {
250+
return
251+
}
252+
253+
// save imported files to skip them next time
254+
if (!state.importedFiles[filename]) state.importedFiles[filename] = {}
255+
state.importedFiles[filename][media] = true
256+
}
257+
258+
return Promise.resolve(options.load(filename, options)).then(content => {
259+
if (content.trim() === "") {
260+
result.warn(`${filename} is empty`, { node: atRule })
261+
return
262+
}
263+
264+
// skip previous imported files not containing @import rules
265+
if (state.hashFiles[content] && state.hashFiles[content][media]) return
266+
267+
return processContent(result, content, filename, options).then(
268+
importedResult => {
269+
const styles = importedResult.root
270+
result.messages = result.messages.concat(importedResult.messages)
271+
272+
if (options.skipDuplicates) {
273+
const hasImport = styles.some(child => {
274+
return child.type === "atrule" && child.name === "import"
275+
})
276+
if (!hasImport) {
277+
// save hash files to skip them next time
278+
if (!state.hashFiles[content]) state.hashFiles[content] = {}
279+
state.hashFiles[content][media] = true
280+
}
281+
}
282+
283+
// recursion: import @import from imported file
284+
return parseStyles(result, styles, options, state, media)
285+
}
286+
)
287+
})
288+
}
289+
290+
export default postcss.plugin("postcss-import", AtImport)

‎src/lib/import/lib/add-prefix.ts‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// The MIT License (MIT)
2+
3+
// Copyright 2016 Bogdan Chadkin <trysound@yandex.ru>
4+
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy of
6+
// this software and associated documentation files (the "Software"), to deal in
7+
// the Software without restriction, including without limitation the rights to
8+
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9+
// the Software, and to permit persons to whom the Software is furnished to do so,
10+
// subject to the following conditions:
11+
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17+
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18+
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19+
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20+
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
import * as path from 'path'
23+
24+
export default function (id, prefix) {
25+
return path.join(path.dirname(id), prefix + path.basename(id))
26+
}

0 commit comments

Comments
 (0)