[79a0317] | 1 | var fs = require('fs');
|
---|
| 2 | var path = require('path');
|
---|
| 3 |
|
---|
| 4 | var applySourceMaps = require('./apply-source-maps');
|
---|
| 5 | var extractImportUrlAndMedia = require('./extract-import-url-and-media');
|
---|
| 6 | var isAllowedResource = require('./is-allowed-resource');
|
---|
| 7 | var loadOriginalSources = require('./load-original-sources');
|
---|
| 8 | var normalizePath = require('./normalize-path');
|
---|
| 9 | var rebase = require('./rebase');
|
---|
| 10 | var rebaseLocalMap = require('./rebase-local-map');
|
---|
| 11 | var rebaseRemoteMap = require('./rebase-remote-map');
|
---|
| 12 | var restoreImport = require('./restore-import');
|
---|
| 13 |
|
---|
| 14 | var tokenize = require('../tokenizer/tokenize');
|
---|
| 15 | var Token = require('../tokenizer/token');
|
---|
| 16 | var Marker = require('../tokenizer/marker');
|
---|
| 17 | var hasProtocol = require('../utils/has-protocol');
|
---|
| 18 | var isImport = require('../utils/is-import');
|
---|
| 19 | var isRemoteResource = require('../utils/is-remote-resource');
|
---|
| 20 |
|
---|
| 21 | var UNKNOWN_URI = 'uri:unknown';
|
---|
| 22 | var FILE_RESOURCE_PROTOCOL = 'file://';
|
---|
| 23 |
|
---|
| 24 | function readSources(input, context, callback) {
|
---|
| 25 | return doReadSources(input, context, function(tokens) {
|
---|
| 26 | return applySourceMaps(tokens, context, function() {
|
---|
| 27 | return loadOriginalSources(context, function() { return callback(tokens); });
|
---|
| 28 | });
|
---|
| 29 | });
|
---|
| 30 | }
|
---|
| 31 |
|
---|
| 32 | function doReadSources(input, context, callback) {
|
---|
| 33 | if (typeof input == 'string') {
|
---|
| 34 | return fromString(input, context, callback);
|
---|
| 35 | } if (Buffer.isBuffer(input)) {
|
---|
| 36 | return fromString(input.toString(), context, callback);
|
---|
| 37 | } if (Array.isArray(input)) {
|
---|
| 38 | return fromArray(input, context, callback);
|
---|
| 39 | } if (typeof input == 'object') {
|
---|
| 40 | return fromHash(input, context, callback);
|
---|
| 41 | }
|
---|
| 42 | }
|
---|
| 43 |
|
---|
| 44 | function fromString(input, context, callback) {
|
---|
| 45 | context.source = undefined;
|
---|
| 46 | context.sourcesContent[undefined] = input;
|
---|
| 47 | context.stats.originalSize += input.length;
|
---|
| 48 |
|
---|
| 49 | return fromStyles(input, context, { inline: context.options.inline }, callback);
|
---|
| 50 | }
|
---|
| 51 |
|
---|
| 52 | function fromArray(input, context, callback) {
|
---|
| 53 | var inputAsImports = input.reduce(function(accumulator, uriOrHash) {
|
---|
| 54 | if (typeof uriOrHash === 'string') {
|
---|
| 55 | return addStringSource(uriOrHash, accumulator);
|
---|
| 56 | }
|
---|
| 57 | return addHashSource(uriOrHash, context, accumulator);
|
---|
| 58 | }, []);
|
---|
| 59 |
|
---|
| 60 | return fromStyles(inputAsImports.join(''), context, { inline: ['all'] }, callback);
|
---|
| 61 | }
|
---|
| 62 |
|
---|
| 63 | function fromHash(input, context, callback) {
|
---|
| 64 | var inputAsImports = addHashSource(input, context, []);
|
---|
| 65 | return fromStyles(inputAsImports.join(''), context, { inline: ['all'] }, callback);
|
---|
| 66 | }
|
---|
| 67 |
|
---|
| 68 | function addStringSource(input, imports) {
|
---|
| 69 | imports.push(restoreAsImport(normalizeUri(input)));
|
---|
| 70 | return imports;
|
---|
| 71 | }
|
---|
| 72 |
|
---|
| 73 | function addHashSource(input, context, imports) {
|
---|
| 74 | var uri;
|
---|
| 75 | var normalizedUri;
|
---|
| 76 | var source;
|
---|
| 77 |
|
---|
| 78 | for (uri in input) {
|
---|
| 79 | source = input[uri];
|
---|
| 80 | normalizedUri = normalizeUri(uri);
|
---|
| 81 |
|
---|
| 82 | imports.push(restoreAsImport(normalizedUri));
|
---|
| 83 |
|
---|
| 84 | context.sourcesContent[normalizedUri] = source.styles;
|
---|
| 85 |
|
---|
| 86 | if (source.sourceMap) {
|
---|
| 87 | trackSourceMap(source.sourceMap, normalizedUri, context);
|
---|
| 88 | }
|
---|
| 89 | }
|
---|
| 90 |
|
---|
| 91 | return imports;
|
---|
| 92 | }
|
---|
| 93 |
|
---|
| 94 | function normalizeUri(uri) {
|
---|
| 95 | var currentPath = path.resolve('');
|
---|
| 96 | var absoluteUri;
|
---|
| 97 | var relativeToCurrentPath;
|
---|
| 98 | var normalizedUri;
|
---|
| 99 |
|
---|
| 100 | if (isRemoteResource(uri)) {
|
---|
| 101 | return uri;
|
---|
| 102 | }
|
---|
| 103 |
|
---|
| 104 | absoluteUri = path.isAbsolute(uri)
|
---|
| 105 | ? uri
|
---|
| 106 | : path.resolve(uri);
|
---|
| 107 | relativeToCurrentPath = path.relative(currentPath, absoluteUri);
|
---|
| 108 | normalizedUri = normalizePath(relativeToCurrentPath);
|
---|
| 109 |
|
---|
| 110 | return normalizedUri;
|
---|
| 111 | }
|
---|
| 112 |
|
---|
| 113 | function trackSourceMap(sourceMap, uri, context) {
|
---|
| 114 | var parsedMap = typeof sourceMap == 'string'
|
---|
| 115 | ? JSON.parse(sourceMap)
|
---|
| 116 | : sourceMap;
|
---|
| 117 | var rebasedMap = isRemoteResource(uri)
|
---|
| 118 | ? rebaseRemoteMap(parsedMap, uri)
|
---|
| 119 | : rebaseLocalMap(parsedMap, uri || UNKNOWN_URI, context.options.rebaseTo);
|
---|
| 120 |
|
---|
| 121 | context.inputSourceMapTracker.track(uri, rebasedMap);
|
---|
| 122 | }
|
---|
| 123 |
|
---|
| 124 | function restoreAsImport(uri) {
|
---|
| 125 | return restoreImport('url(' + uri + ')', '') + Marker.SEMICOLON;
|
---|
| 126 | }
|
---|
| 127 |
|
---|
| 128 | function fromStyles(styles, context, parentInlinerContext, callback) {
|
---|
| 129 | var tokens;
|
---|
| 130 | var rebaseConfig = {};
|
---|
| 131 |
|
---|
| 132 | if (!context.source) {
|
---|
| 133 | rebaseConfig.fromBase = path.resolve('');
|
---|
| 134 | rebaseConfig.toBase = context.options.rebaseTo;
|
---|
| 135 | } else if (isRemoteResource(context.source)) {
|
---|
| 136 | rebaseConfig.fromBase = context.source;
|
---|
| 137 | rebaseConfig.toBase = context.source;
|
---|
| 138 | } else if (path.isAbsolute(context.source)) {
|
---|
| 139 | rebaseConfig.fromBase = path.dirname(context.source);
|
---|
| 140 | rebaseConfig.toBase = context.options.rebaseTo;
|
---|
| 141 | } else {
|
---|
| 142 | rebaseConfig.fromBase = path.dirname(path.resolve(context.source));
|
---|
| 143 | rebaseConfig.toBase = context.options.rebaseTo;
|
---|
| 144 | }
|
---|
| 145 |
|
---|
| 146 | tokens = tokenize(styles, context);
|
---|
| 147 | tokens = rebase(tokens, context.options.rebase, context.validator, rebaseConfig);
|
---|
| 148 |
|
---|
| 149 | return allowsAnyImports(parentInlinerContext.inline)
|
---|
| 150 | ? inline(tokens, context, parentInlinerContext, callback)
|
---|
| 151 | : callback(tokens);
|
---|
| 152 | }
|
---|
| 153 |
|
---|
| 154 | function allowsAnyImports(inline) {
|
---|
| 155 | return !(inline.length == 1 && inline[0] == 'none');
|
---|
| 156 | }
|
---|
| 157 |
|
---|
| 158 | function inline(tokens, externalContext, parentInlinerContext, callback) {
|
---|
| 159 | var inlinerContext = {
|
---|
| 160 | afterContent: false,
|
---|
| 161 | callback: callback,
|
---|
| 162 | errors: externalContext.errors,
|
---|
| 163 | externalContext: externalContext,
|
---|
| 164 | fetch: externalContext.options.fetch,
|
---|
| 165 | inlinedStylesheets: parentInlinerContext.inlinedStylesheets || externalContext.inlinedStylesheets,
|
---|
| 166 | inline: parentInlinerContext.inline,
|
---|
| 167 | inlineRequest: externalContext.options.inlineRequest,
|
---|
| 168 | inlineTimeout: externalContext.options.inlineTimeout,
|
---|
| 169 | isRemote: parentInlinerContext.isRemote || false,
|
---|
| 170 | localOnly: externalContext.localOnly,
|
---|
| 171 | outputTokens: [],
|
---|
| 172 | rebaseTo: externalContext.options.rebaseTo,
|
---|
| 173 | sourceTokens: tokens,
|
---|
| 174 | warnings: externalContext.warnings
|
---|
| 175 | };
|
---|
| 176 |
|
---|
| 177 | return doInlineImports(inlinerContext);
|
---|
| 178 | }
|
---|
| 179 |
|
---|
| 180 | function doInlineImports(inlinerContext) {
|
---|
| 181 | var token;
|
---|
| 182 | var i, l;
|
---|
| 183 |
|
---|
| 184 | for (i = 0, l = inlinerContext.sourceTokens.length; i < l; i++) {
|
---|
| 185 | token = inlinerContext.sourceTokens[i];
|
---|
| 186 |
|
---|
| 187 | if (token[0] == Token.AT_RULE && isImport(token[1])) {
|
---|
| 188 | inlinerContext.sourceTokens.splice(0, i);
|
---|
| 189 | return inlineStylesheet(token, inlinerContext);
|
---|
| 190 | } if (token[0] == Token.AT_RULE || token[0] == Token.COMMENT) {
|
---|
| 191 | inlinerContext.outputTokens.push(token);
|
---|
| 192 | } else {
|
---|
| 193 | inlinerContext.outputTokens.push(token);
|
---|
| 194 | inlinerContext.afterContent = true;
|
---|
| 195 | }
|
---|
| 196 | }
|
---|
| 197 |
|
---|
| 198 | inlinerContext.sourceTokens = [];
|
---|
| 199 | return inlinerContext.callback(inlinerContext.outputTokens);
|
---|
| 200 | }
|
---|
| 201 |
|
---|
| 202 | function inlineStylesheet(token, inlinerContext) {
|
---|
| 203 | var uriAndMediaQuery = extractImportUrlAndMedia(token[1]);
|
---|
| 204 | var uri = uriAndMediaQuery[0];
|
---|
| 205 | var mediaQuery = uriAndMediaQuery[1];
|
---|
| 206 | var metadata = token[2];
|
---|
| 207 |
|
---|
| 208 | return isRemoteResource(uri)
|
---|
| 209 | ? inlineRemoteStylesheet(uri, mediaQuery, metadata, inlinerContext)
|
---|
| 210 | : inlineLocalStylesheet(uri, mediaQuery, metadata, inlinerContext);
|
---|
| 211 | }
|
---|
| 212 |
|
---|
| 213 | function inlineRemoteStylesheet(uri, mediaQuery, metadata, inlinerContext) {
|
---|
| 214 | var isAllowed = isAllowedResource(uri, true, inlinerContext.inline);
|
---|
| 215 | var originalUri = uri;
|
---|
| 216 | var isLoaded = uri in inlinerContext.externalContext.sourcesContent;
|
---|
| 217 | var isRuntimeResource = !hasProtocol(uri);
|
---|
| 218 |
|
---|
| 219 | if (inlinerContext.inlinedStylesheets.indexOf(uri) > -1) {
|
---|
| 220 | inlinerContext.warnings.push('Ignoring remote @import of "' + uri + '" as it has already been imported.');
|
---|
| 221 | inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
|
---|
| 222 | return doInlineImports(inlinerContext);
|
---|
| 223 | } if (inlinerContext.localOnly && inlinerContext.afterContent) {
|
---|
| 224 | inlinerContext.warnings.push('Ignoring remote @import of "' + uri + '" as no callback given and after other content.');
|
---|
| 225 | inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
|
---|
| 226 | return doInlineImports(inlinerContext);
|
---|
| 227 | } if (isRuntimeResource) {
|
---|
| 228 | inlinerContext.warnings.push('Skipping remote @import of "' + uri + '" as no protocol given.');
|
---|
| 229 | inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
|
---|
| 230 | inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
|
---|
| 231 | return doInlineImports(inlinerContext);
|
---|
| 232 | } if (inlinerContext.localOnly && !isLoaded) {
|
---|
| 233 | inlinerContext.warnings.push('Skipping remote @import of "' + uri + '" as no callback given.');
|
---|
| 234 | inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
|
---|
| 235 | inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
|
---|
| 236 | return doInlineImports(inlinerContext);
|
---|
| 237 | } if (!isAllowed && inlinerContext.afterContent) {
|
---|
| 238 | inlinerContext.warnings.push('Ignoring remote @import of "' + uri + '" as resource is not allowed and after other content.');
|
---|
| 239 | inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
|
---|
| 240 | return doInlineImports(inlinerContext);
|
---|
| 241 | } if (!isAllowed) {
|
---|
| 242 | inlinerContext.warnings.push('Skipping remote @import of "' + uri + '" as resource is not allowed.');
|
---|
| 243 | inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
|
---|
| 244 | inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
|
---|
| 245 | return doInlineImports(inlinerContext);
|
---|
| 246 | }
|
---|
| 247 |
|
---|
| 248 | inlinerContext.inlinedStylesheets.push(uri);
|
---|
| 249 |
|
---|
| 250 | function whenLoaded(error, importedStyles) {
|
---|
| 251 | if (error) {
|
---|
| 252 | inlinerContext.errors.push('Broken @import declaration of "' + uri + '" - ' + error);
|
---|
| 253 |
|
---|
| 254 | return process.nextTick(function() {
|
---|
| 255 | inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
|
---|
| 256 | inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
|
---|
| 257 | doInlineImports(inlinerContext);
|
---|
| 258 | });
|
---|
| 259 | }
|
---|
| 260 |
|
---|
| 261 | inlinerContext.inline = inlinerContext.externalContext.options.inline;
|
---|
| 262 | inlinerContext.isRemote = true;
|
---|
| 263 |
|
---|
| 264 | inlinerContext.externalContext.source = originalUri;
|
---|
| 265 | inlinerContext.externalContext.sourcesContent[uri] = importedStyles;
|
---|
| 266 | inlinerContext.externalContext.stats.originalSize += importedStyles.length;
|
---|
| 267 |
|
---|
| 268 | return fromStyles(importedStyles, inlinerContext.externalContext, inlinerContext, function(importedTokens) {
|
---|
| 269 | importedTokens = wrapInMedia(importedTokens, mediaQuery, metadata);
|
---|
| 270 |
|
---|
| 271 | inlinerContext.outputTokens = inlinerContext.outputTokens.concat(importedTokens);
|
---|
| 272 | inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
|
---|
| 273 |
|
---|
| 274 | return doInlineImports(inlinerContext);
|
---|
| 275 | });
|
---|
| 276 | }
|
---|
| 277 |
|
---|
| 278 | return isLoaded
|
---|
| 279 | ? whenLoaded(null, inlinerContext.externalContext.sourcesContent[uri])
|
---|
| 280 | : inlinerContext.fetch(uri, inlinerContext.inlineRequest, inlinerContext.inlineTimeout, whenLoaded);
|
---|
| 281 | }
|
---|
| 282 |
|
---|
| 283 | function inlineLocalStylesheet(uri, mediaQuery, metadata, inlinerContext) {
|
---|
| 284 | var protocolLessUri = uri.replace(FILE_RESOURCE_PROTOCOL, '');
|
---|
| 285 | var currentPath = path.resolve('');
|
---|
| 286 | var absoluteUri = path.isAbsolute(protocolLessUri)
|
---|
| 287 | ? path.resolve(currentPath, protocolLessUri[0] == '/' ? protocolLessUri.substring(1) : protocolLessUri)
|
---|
| 288 | : path.resolve(inlinerContext.rebaseTo, protocolLessUri);
|
---|
| 289 | var relativeToCurrentPath = path.relative(currentPath, absoluteUri);
|
---|
| 290 | var importedStyles;
|
---|
| 291 | var isAllowed = isAllowedResource(protocolLessUri, false, inlinerContext.inline);
|
---|
| 292 | var normalizedPath = normalizePath(relativeToCurrentPath);
|
---|
| 293 | var isLoaded = normalizedPath in inlinerContext.externalContext.sourcesContent;
|
---|
| 294 |
|
---|
| 295 | if (inlinerContext.inlinedStylesheets.indexOf(absoluteUri) > -1) {
|
---|
| 296 | inlinerContext.warnings.push('Ignoring local @import of "' + protocolLessUri + '" as it has already been imported.');
|
---|
| 297 | } else if (isAllowed && !isLoaded && (!fs.existsSync(absoluteUri) || !fs.statSync(absoluteUri).isFile())) {
|
---|
| 298 | inlinerContext.errors.push('Ignoring local @import of "' + protocolLessUri + '" as resource is missing.');
|
---|
| 299 | } else if (!isAllowed && inlinerContext.afterContent) {
|
---|
| 300 | inlinerContext.warnings.push('Ignoring local @import of "' + protocolLessUri + '" as resource is not allowed and after other content.');
|
---|
| 301 | } else if (inlinerContext.afterContent) {
|
---|
| 302 | inlinerContext.warnings.push('Ignoring local @import of "' + protocolLessUri + '" as after other content.');
|
---|
| 303 | } else if (!isAllowed) {
|
---|
| 304 | inlinerContext.warnings.push('Skipping local @import of "' + protocolLessUri + '" as resource is not allowed.');
|
---|
| 305 | inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
|
---|
| 306 | } else {
|
---|
| 307 | importedStyles = isLoaded
|
---|
| 308 | ? inlinerContext.externalContext.sourcesContent[normalizedPath]
|
---|
| 309 | : fs.readFileSync(absoluteUri, 'utf-8');
|
---|
| 310 |
|
---|
| 311 | if (importedStyles.charCodeAt(0) === 65279) {
|
---|
| 312 | importedStyles = importedStyles.substring(1);
|
---|
| 313 | }
|
---|
| 314 |
|
---|
| 315 | inlinerContext.inlinedStylesheets.push(absoluteUri);
|
---|
| 316 | inlinerContext.inline = inlinerContext.externalContext.options.inline;
|
---|
| 317 |
|
---|
| 318 | inlinerContext.externalContext.source = normalizedPath;
|
---|
| 319 | inlinerContext.externalContext.sourcesContent[normalizedPath] = importedStyles;
|
---|
| 320 | inlinerContext.externalContext.stats.originalSize += importedStyles.length;
|
---|
| 321 |
|
---|
| 322 | return fromStyles(importedStyles, inlinerContext.externalContext, inlinerContext, function(importedTokens) {
|
---|
| 323 | importedTokens = wrapInMedia(importedTokens, mediaQuery, metadata);
|
---|
| 324 |
|
---|
| 325 | inlinerContext.outputTokens = inlinerContext.outputTokens.concat(importedTokens);
|
---|
| 326 | inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
|
---|
| 327 |
|
---|
| 328 | return doInlineImports(inlinerContext);
|
---|
| 329 | });
|
---|
| 330 | }
|
---|
| 331 |
|
---|
| 332 | inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
|
---|
| 333 |
|
---|
| 334 | return doInlineImports(inlinerContext);
|
---|
| 335 | }
|
---|
| 336 |
|
---|
| 337 | function wrapInMedia(tokens, mediaQuery, metadata) {
|
---|
| 338 | if (mediaQuery) {
|
---|
| 339 | return [[Token.NESTED_BLOCK, [[Token.NESTED_BLOCK_SCOPE, '@media ' + mediaQuery, metadata]], tokens]];
|
---|
| 340 | }
|
---|
| 341 | return tokens;
|
---|
| 342 | }
|
---|
| 343 |
|
---|
| 344 | module.exports = readSources;
|
---|