|
| 1 | +var sourceMap = require('source-map'); |
| 2 | +var sassResolve = require('@csstools/sass-import-resolve'); |
| 3 | +var sass = require('sass'); |
| 4 | +var path = require('path'); |
| 5 | + |
| 6 | +function _interopNamespaceDefault(e) { |
| 7 | + var n = Object.create(null); |
| 8 | + if (e) { |
| 9 | + Object.keys(e).forEach(function (k) { |
| 10 | + if (k !== 'default') { |
| 11 | + var d = Object.getOwnPropertyDescriptor(e, k); |
| 12 | + Object.defineProperty(n, k, d.get ? d : { |
| 13 | + enumerable: true, |
| 14 | + get: function () { return e[k]; } |
| 15 | + }); |
| 16 | + } |
| 17 | + }); |
| 18 | + } |
| 19 | + n.default = e; |
| 20 | + return Object.freeze(n); |
| 21 | +} |
| 22 | + |
| 23 | +var sass__namespace = /*#__PURE__*/_interopNamespaceDefault(sass); |
| 24 | + |
| 25 | +// tooling |
| 26 | + |
| 27 | +// special sass source matcher |
| 28 | +const sassMatch = /#sass$/; |
| 29 | + |
| 30 | +// returns merged source maps |
| 31 | +var mergeSourceMaps = ((...maps) => { |
| 32 | + // new sourcemap |
| 33 | + const generator = new sourceMap.SourceMapGenerator(); |
| 34 | + |
| 35 | + // existing sourcemaps |
| 36 | + const consumersPromise = Promise.all(maps.map(map => new sourceMap.SourceMapConsumer(map))); |
| 37 | + return consumersPromise.then(consumers => consumers.forEach(consumer => { |
| 38 | + // copy each original mapping to the new sourcemap |
| 39 | + consumer.eachMapping(mapping => { |
| 40 | + const originalPosition = originalPositionFor(mapping, consumers); |
| 41 | + if (originalPosition.source) { |
| 42 | + generator.addMapping({ |
| 43 | + generated: { |
| 44 | + line: mapping.generatedLine, |
| 45 | + column: mapping.generatedColumn |
| 46 | + }, |
| 47 | + original: { |
| 48 | + // use positive numbers to work around sass/libsass#2312 |
| 49 | + line: Math.abs(originalPosition.line), |
| 50 | + column: Math.abs(originalPosition.column) |
| 51 | + }, |
| 52 | + source: originalPosition.source, |
| 53 | + name: originalPosition.name |
| 54 | + }); |
| 55 | + } |
| 56 | + }); |
| 57 | + |
| 58 | + // copy each original source to the new sourcemap |
| 59 | + consumer.sources.forEach(source => { |
| 60 | + generator._sources.add(source); |
| 61 | + const content = consumer.sourceContentFor(source); |
| 62 | + if (content !== null) { |
| 63 | + generator.setSourceContent(source, content); |
| 64 | + } |
| 65 | + }); |
| 66 | + })).then(() => { |
| 67 | + // merged map as json |
| 68 | + const mergedMap = JSON.parse(generator); |
| 69 | + |
| 70 | + // clean all special sass sources in merged map |
| 71 | + mergedMap.sources = mergedMap.sources.map(source => source.replace(sassMatch, '')); |
| 72 | + return mergedMap; |
| 73 | + }); |
| 74 | +}); |
| 75 | +function originalPositionFor(mapping, consumers) { |
| 76 | + // initial positioning |
| 77 | + let originalPosition = { |
| 78 | + line: mapping.generatedLine, |
| 79 | + column: mapping.generatedColumn |
| 80 | + }; |
| 81 | + |
| 82 | + // special sass sources are mapped in reverse |
| 83 | + consumers.slice(0).reverse().forEach(consumer => { |
| 84 | + const possiblePosition = consumer.originalPositionFor(originalPosition); |
| 85 | + if (possiblePosition.source) { |
| 86 | + if (sassMatch.test(possiblePosition.source)) { |
| 87 | + originalPosition = possiblePosition; |
| 88 | + } |
| 89 | + } |
| 90 | + }); |
| 91 | + |
| 92 | + // regular sources are mapped regularly |
| 93 | + consumers.forEach(consumer => { |
| 94 | + const possiblePosition = consumer.originalPositionFor(originalPosition); |
| 95 | + if (possiblePosition.source) { |
| 96 | + if (!sassMatch.test(possiblePosition.source)) { |
| 97 | + originalPosition = possiblePosition; |
| 98 | + } |
| 99 | + } |
| 100 | + }); |
| 101 | + return originalPosition; |
| 102 | +} |
| 103 | + |
| 104 | +// tooling |
| 105 | +const requiredPostConfig = { |
| 106 | + map: { |
| 107 | + annotation: false, |
| 108 | + inline: false, |
| 109 | + sourcesContent: true |
| 110 | + } |
| 111 | +}; |
| 112 | +const requiredSassConfig = { |
| 113 | + omitSourceMapUrl: true, |
| 114 | + sourceMap: true, |
| 115 | + sourceMapContents: true |
| 116 | +}; |
| 117 | + |
| 118 | +// transform css with sass |
| 119 | +const plugin = (opts = {}) => { |
| 120 | + return { |
| 121 | + postcssPlugin: 'postcss-sass', |
| 122 | + Once(root, { |
| 123 | + result, |
| 124 | + parse |
| 125 | + }) { |
| 126 | + // postcss configuration |
| 127 | + const postConfig = Object.assign({}, result.opts, requiredPostConfig); |
| 128 | + |
| 129 | + // postcss results |
| 130 | + const { |
| 131 | + css: postCSS, |
| 132 | + map: postMap |
| 133 | + } = root.toResult(postConfig); |
| 134 | + |
| 135 | + // include paths |
| 136 | + const includePaths = [].concat(opts && opts.includePaths || []); |
| 137 | + |
| 138 | + // sass engine to use |
| 139 | + const sassEngine = opts && opts.sass || sass__namespace; |
| 140 | + |
| 141 | + // sass resolve cache |
| 142 | + const cache = {}; |
| 143 | + |
| 144 | + // replication of the default sass file importer |
| 145 | + const defaultSassImporter = (id, parentId, done) => { |
| 146 | + // resolve the absolute parent |
| 147 | + const parent = path.resolve(parentId); |
| 148 | + |
| 149 | + // cwds is the list of all directories to search |
| 150 | + const cwds = [path.dirname(parent)].concat(includePaths).map(includePath => path.resolve(includePath)); |
| 151 | + cwds.reduce( |
| 152 | + // resolve the first available files |
| 153 | + (promise, cwd) => promise.catch(() => sassResolve(id, { |
| 154 | + cwd, |
| 155 | + cache, |
| 156 | + readFile: true |
| 157 | + })), Promise.reject()).then(({ |
| 158 | + file, |
| 159 | + contents |
| 160 | + }) => { |
| 161 | + // pass the file and contents back to sass |
| 162 | + done({ |
| 163 | + file, |
| 164 | + contents |
| 165 | + }); |
| 166 | + }, importerError => { |
| 167 | + // otherwise, pass the error |
| 168 | + done(importerError); |
| 169 | + }); |
| 170 | + }; |
| 171 | + |
| 172 | + // sass importer |
| 173 | + const sassImporter = opts && opts.importer || defaultSassImporter; |
| 174 | + return new Promise( |
| 175 | + // promise sass results |
| 176 | + (resolve, reject) => sassEngine.render( |
| 177 | + // pass options directly into node-sass |
| 178 | + Object.assign({}, opts, requiredSassConfig, { |
| 179 | + file: `${postConfig.from}#sass`, |
| 180 | + outFile: postConfig.from, |
| 181 | + data: postCSS, |
| 182 | + importer(id, parentId, done) { |
| 183 | + const doneWrap = importerResult => { |
| 184 | + const file = importerResult && importerResult.file; |
| 185 | + if (file) { |
| 186 | + const parent = path.resolve(parentId); |
| 187 | + |
| 188 | + // push the dependency to watch tasks |
| 189 | + result.messages.push({ |
| 190 | + type: 'dependency', |
| 191 | + file, |
| 192 | + parent |
| 193 | + }); |
| 194 | + } |
| 195 | + done(importerResult); |
| 196 | + }; |
| 197 | + |
| 198 | + // strip the #sass suffix we added |
| 199 | + const prev = parentId.replace(/#sass$/, ''); |
| 200 | + |
| 201 | + // call the sass importer and catch its output |
| 202 | + sassImporter.call(this, id, prev, doneWrap); |
| 203 | + } |
| 204 | + }), (sassError, sassResult) => sassError ? reject(sassError) : resolve(sassResult))).then(({ |
| 205 | + css: sassCSS, |
| 206 | + map: sassMap, |
| 207 | + stats |
| 208 | + }) => { |
| 209 | + const parent = path.resolve(postConfig.from); |
| 210 | + |
| 211 | + // use stats.includedFiles to get the full list of dependencies. Importer will not receive relative imports. See https://github.com/sass/dart-sass/issues/574 |
| 212 | + for (const includedFile of stats.includedFiles) { |
| 213 | + // strip the #sass suffix we added |
| 214 | + const file = path.resolve(includedFile.replace(/#sass$/, '')); |
| 215 | + |
| 216 | + // don't include the parent as a dependency of itself |
| 217 | + if (file === parent) { |
| 218 | + continue; |
| 219 | + } |
| 220 | + |
| 221 | + // push the dependency to watch tasks |
| 222 | + result.messages.push({ |
| 223 | + type: 'dependency', |
| 224 | + plugin: 'postcss-sass', |
| 225 | + file, |
| 226 | + parent |
| 227 | + }); |
| 228 | + } |
| 229 | + return mergeSourceMaps(postMap.toJSON(), JSON.parse(sassMap)).then(prev => { |
| 230 | + // update root to post-node-sass ast |
| 231 | + result.root = parse(sassCSS.toString(), Object.assign({}, postConfig, { |
| 232 | + map: { |
| 233 | + prev |
| 234 | + } |
| 235 | + })); |
| 236 | + }); |
| 237 | + }); |
| 238 | + } |
| 239 | + }; |
| 240 | +}; |
| 241 | +plugin.postcss = true; |
| 242 | + |
| 243 | +module.exports = plugin; |
0 commit comments