UNPKG

5.02 kBJavaScriptView Raw
1import { existsSync, mkdirSync, writeFile } from 'fs'
2import { dirname } from 'path'
3import { createFilter } from 'rollup-pluginutils'
4
5export default function css (options = {}) {
6 const filter = createFilter(options.include || ['/**/*.css', '/**/*.scss', '/**/*.sass'], options.exclude)
7 let dest = options.output
8
9 const styles = {}
10 const prefix = options.prefix ? options.prefix + '\n' : ''
11 let includePaths = options.includePaths || ['node_modules/']
12 includePaths.push(process.cwd())
13
14 const compileToCSS = function (scss) {
15 // Compile SASS to CSS
16 if (scss.length) {
17 includePaths = includePaths.filter((v, i, a) => a.indexOf(v) === i)
18 try {
19 const sass = options.sass || require('node-sass')
20 const css = sass.renderSync(Object.assign({
21 data: prefix + scss,
22 includePaths
23 }, options)).css.toString()
24 // Possibly process CSS (e.g. by PostCSS)
25 if (typeof options.processor === 'function') {
26 const processor = options.processor(css, styles)
27
28 // PostCSS support
29 if (typeof processor.process === 'function') {
30 return Promise.resolve(processor.process(css, { from: undefined }))
31 .then(result => result.css)
32 }
33
34 return processor
35 }
36 return css
37 } catch (e) {
38 if (options.failOnError) {
39 throw e
40 }
41 console.log()
42 console.log(red('Error:\n\t' + e.message))
43 if (e.message.includes('Invalid CSS')) {
44 console.log(green('Solution:\n\t' + 'fix your Sass code'))
45 console.log('Line: ' + e.line)
46 console.log('Column: ' + e.column)
47 }
48 if (e.message.includes('node-sass') && e.message.includes('find module')) {
49 console.log(green('Solution:\n\t' + 'npm install --save node-sass'))
50 }
51 if (e.message.includes('node-sass') && e.message.includes('bindings')) {
52 console.log(green('Solution:\n\t' + 'npm rebuild node-sass --force'))
53 }
54 console.log()
55 }
56 }
57 }
58
59 return {
60 name: 'scss',
61 transform (code, id) {
62 if (!filter(id)) {
63 return
64 }
65
66 // Add the include path before doing any processing
67 includePaths.push(dirname(id))
68
69 // Rebuild all scss files if anything happens to this folder
70 // TODO: check if it's possible to get a list of all dependent scss files
71 // and only watch those
72 if ('watch' in options) {
73 const files = Array.isArray(options.watch) ? options.watch : [options.watch]
74 files.forEach(file => this.addWatchFile(file))
75 }
76
77 // When output is disabled, the stylesheet is exported as a string
78 if (options.output === false) {
79 return Promise.resolve(compileToCSS(code)).then(css => ({
80 code: 'export default ' + JSON.stringify(css),
81 map: { mappings: '' }
82 }))
83 }
84
85 // Map of every stylesheet
86 styles[id] = code
87
88 return ''
89 },
90 generateBundle (opts) {
91 // No stylesheet needed
92 if (options.output === false) {
93 return
94 }
95
96 // Combine all stylesheets
97 let scss = ''
98 for (const id in styles) {
99 scss += styles[id] || ''
100 }
101
102 const css = compileToCSS(scss)
103
104 // Resolve if processor returned a Promise
105 Promise.resolve(css).then(css => {
106 // Emit styles through callback
107 if (typeof options.output === 'function') {
108 options.output(css, styles)
109 return
110 }
111
112 if (typeof css !== 'string') {
113 return
114 }
115
116 if (typeof dest !== 'string') {
117 // Don't create unwanted empty stylesheets
118 if (!css.length) {
119 return
120 }
121
122 // Guess destination filename
123 dest = opts.dest || opts.file || 'bundle.js'
124 if (dest.endsWith('.js')) {
125 dest = dest.slice(0, -3)
126 }
127 dest = dest + '.css'
128 }
129
130 // Ensure that dest parent folders exist (create the missing ones)
131 ensureParentDirsSync(dirname(dest))
132
133 // Emit styles to file
134 writeFile(dest, css, (err) => {
135 if (opts.verbose !== false) {
136 if (err) {
137 console.error(red(err))
138 } else if (css) {
139 console.log(green(dest), getSize(css.length))
140 }
141 }
142 })
143 })
144 }
145 }
146}
147
148function red (text) {
149 return '\x1b[1m\x1b[31m' + text + '\x1b[0m'
150}
151
152function green (text) {
153 return '\x1b[1m\x1b[32m' + text + '\x1b[0m'
154}
155
156function getSize (bytes) {
157 return bytes < 10000
158 ? bytes.toFixed(0) + ' B'
159 : bytes < 1024000
160 ? (bytes / 1024).toPrecision(3) + ' kB'
161 : (bytes / 1024 / 1024).toPrecision(4) + ' MB'
162}
163
164function ensureParentDirsSync (dir) {
165 if (existsSync(dir)) {
166 return
167 }
168
169 try {
170 mkdirSync(dir)
171 } catch (err) {
172 if (err.code === 'ENOENT') {
173 ensureParentDirsSync(dirname(dir))
174 ensureParentDirsSync(dir)
175 }
176 }
177}