345 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			345 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| var fs = require('fs');
 | |
| var path = require('path');
 | |
| 
 | |
| var applySourceMaps = require('./apply-source-maps');
 | |
| var extractImportUrlAndMedia = require('./extract-import-url-and-media');
 | |
| var isAllowedResource = require('./is-allowed-resource');
 | |
| var loadOriginalSources = require('./load-original-sources');
 | |
| var normalizePath = require('./normalize-path');
 | |
| var rebase = require('./rebase');
 | |
| var rebaseLocalMap = require('./rebase-local-map');
 | |
| var rebaseRemoteMap = require('./rebase-remote-map');
 | |
| var restoreImport = require('./restore-import');
 | |
| 
 | |
| var tokenize = require('../tokenizer/tokenize');
 | |
| var Token = require('../tokenizer/token');
 | |
| var Marker = require('../tokenizer/marker');
 | |
| var hasProtocol = require('../utils/has-protocol');
 | |
| var isImport = require('../utils/is-import');
 | |
| var isRemoteResource = require('../utils/is-remote-resource');
 | |
| 
 | |
| var UNKNOWN_URI = 'uri:unknown';
 | |
| var FILE_RESOURCE_PROTOCOL = 'file://';
 | |
| 
 | |
| function readSources(input, context, callback) {
 | |
|   return doReadSources(input, context, function(tokens) {
 | |
|     return applySourceMaps(tokens, context, function() {
 | |
|       return loadOriginalSources(context, function() { return callback(tokens); });
 | |
|     });
 | |
|   });
 | |
| }
 | |
| 
 | |
| function doReadSources(input, context, callback) {
 | |
|   if (typeof input == 'string') {
 | |
|     return fromString(input, context, callback);
 | |
|   } if (Buffer.isBuffer(input)) {
 | |
|     return fromString(input.toString(), context, callback);
 | |
|   } if (Array.isArray(input)) {
 | |
|     return fromArray(input, context, callback);
 | |
|   } if (typeof input == 'object') {
 | |
|     return fromHash(input, context, callback);
 | |
|   }
 | |
| }
 | |
| 
 | |
| function fromString(input, context, callback) {
 | |
|   context.source = undefined;
 | |
|   context.sourcesContent[undefined] = input;
 | |
|   context.stats.originalSize += input.length;
 | |
| 
 | |
|   return fromStyles(input, context, { inline: context.options.inline }, callback);
 | |
| }
 | |
| 
 | |
| function fromArray(input, context, callback) {
 | |
|   var inputAsImports = input.reduce(function(accumulator, uriOrHash) {
 | |
|     if (typeof uriOrHash === 'string') {
 | |
|       return addStringSource(uriOrHash, accumulator);
 | |
|     }
 | |
|     return addHashSource(uriOrHash, context, accumulator);
 | |
|   }, []);
 | |
| 
 | |
|   return fromStyles(inputAsImports.join(''), context, { inline: ['all'] }, callback);
 | |
| }
 | |
| 
 | |
| function fromHash(input, context, callback) {
 | |
|   var inputAsImports = addHashSource(input, context, []);
 | |
|   return fromStyles(inputAsImports.join(''), context, { inline: ['all'] }, callback);
 | |
| }
 | |
| 
 | |
| function addStringSource(input, imports) {
 | |
|   imports.push(restoreAsImport(normalizeUri(input)));
 | |
|   return imports;
 | |
| }
 | |
| 
 | |
| function addHashSource(input, context, imports) {
 | |
|   var uri;
 | |
|   var normalizedUri;
 | |
|   var source;
 | |
| 
 | |
|   for (uri in input) {
 | |
|     source = input[uri];
 | |
|     normalizedUri = normalizeUri(uri);
 | |
| 
 | |
|     imports.push(restoreAsImport(normalizedUri));
 | |
| 
 | |
|     context.sourcesContent[normalizedUri] = source.styles;
 | |
| 
 | |
|     if (source.sourceMap) {
 | |
|       trackSourceMap(source.sourceMap, normalizedUri, context);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return imports;
 | |
| }
 | |
| 
 | |
| function normalizeUri(uri) {
 | |
|   var currentPath = path.resolve('');
 | |
|   var absoluteUri;
 | |
|   var relativeToCurrentPath;
 | |
|   var normalizedUri;
 | |
| 
 | |
|   if (isRemoteResource(uri)) {
 | |
|     return uri;
 | |
|   }
 | |
| 
 | |
|   absoluteUri = path.isAbsolute(uri)
 | |
|     ? uri
 | |
|     : path.resolve(uri);
 | |
|   relativeToCurrentPath = path.relative(currentPath, absoluteUri);
 | |
|   normalizedUri = normalizePath(relativeToCurrentPath);
 | |
| 
 | |
|   return normalizedUri;
 | |
| }
 | |
| 
 | |
| function trackSourceMap(sourceMap, uri, context) {
 | |
|   var parsedMap = typeof sourceMap == 'string'
 | |
|     ? JSON.parse(sourceMap)
 | |
|     : sourceMap;
 | |
|   var rebasedMap = isRemoteResource(uri)
 | |
|     ? rebaseRemoteMap(parsedMap, uri)
 | |
|     : rebaseLocalMap(parsedMap, uri || UNKNOWN_URI, context.options.rebaseTo);
 | |
| 
 | |
|   context.inputSourceMapTracker.track(uri, rebasedMap);
 | |
| }
 | |
| 
 | |
| function restoreAsImport(uri) {
 | |
|   return restoreImport('url(' + uri + ')', '') + Marker.SEMICOLON;
 | |
| }
 | |
| 
 | |
| function fromStyles(styles, context, parentInlinerContext, callback) {
 | |
|   var tokens;
 | |
|   var rebaseConfig = {};
 | |
| 
 | |
|   if (!context.source) {
 | |
|     rebaseConfig.fromBase = path.resolve('');
 | |
|     rebaseConfig.toBase = context.options.rebaseTo;
 | |
|   } else if (isRemoteResource(context.source)) {
 | |
|     rebaseConfig.fromBase = context.source;
 | |
|     rebaseConfig.toBase = context.source;
 | |
|   } else if (path.isAbsolute(context.source)) {
 | |
|     rebaseConfig.fromBase = path.dirname(context.source);
 | |
|     rebaseConfig.toBase = context.options.rebaseTo;
 | |
|   } else {
 | |
|     rebaseConfig.fromBase = path.dirname(path.resolve(context.source));
 | |
|     rebaseConfig.toBase = context.options.rebaseTo;
 | |
|   }
 | |
| 
 | |
|   tokens = tokenize(styles, context);
 | |
|   tokens = rebase(tokens, context.options.rebase, context.validator, rebaseConfig);
 | |
| 
 | |
|   return allowsAnyImports(parentInlinerContext.inline)
 | |
|     ? inline(tokens, context, parentInlinerContext, callback)
 | |
|     : callback(tokens);
 | |
| }
 | |
| 
 | |
| function allowsAnyImports(inline) {
 | |
|   return !(inline.length == 1 && inline[0] == 'none');
 | |
| }
 | |
| 
 | |
| function inline(tokens, externalContext, parentInlinerContext, callback) {
 | |
|   var inlinerContext = {
 | |
|     afterContent: false,
 | |
|     callback: callback,
 | |
|     errors: externalContext.errors,
 | |
|     externalContext: externalContext,
 | |
|     fetch: externalContext.options.fetch,
 | |
|     inlinedStylesheets: parentInlinerContext.inlinedStylesheets || externalContext.inlinedStylesheets,
 | |
|     inline: parentInlinerContext.inline,
 | |
|     inlineRequest: externalContext.options.inlineRequest,
 | |
|     inlineTimeout: externalContext.options.inlineTimeout,
 | |
|     isRemote: parentInlinerContext.isRemote || false,
 | |
|     localOnly: externalContext.localOnly,
 | |
|     outputTokens: [],
 | |
|     rebaseTo: externalContext.options.rebaseTo,
 | |
|     sourceTokens: tokens,
 | |
|     warnings: externalContext.warnings
 | |
|   };
 | |
| 
 | |
|   return doInlineImports(inlinerContext);
 | |
| }
 | |
| 
 | |
| function doInlineImports(inlinerContext) {
 | |
|   var token;
 | |
|   var i, l;
 | |
| 
 | |
|   for (i = 0, l = inlinerContext.sourceTokens.length; i < l; i++) {
 | |
|     token = inlinerContext.sourceTokens[i];
 | |
| 
 | |
|     if (token[0] == Token.AT_RULE && isImport(token[1])) {
 | |
|       inlinerContext.sourceTokens.splice(0, i);
 | |
|       return inlineStylesheet(token, inlinerContext);
 | |
|     } if (token[0] == Token.AT_RULE || token[0] == Token.COMMENT) {
 | |
|       inlinerContext.outputTokens.push(token);
 | |
|     } else {
 | |
|       inlinerContext.outputTokens.push(token);
 | |
|       inlinerContext.afterContent = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   inlinerContext.sourceTokens = [];
 | |
|   return inlinerContext.callback(inlinerContext.outputTokens);
 | |
| }
 | |
| 
 | |
| function inlineStylesheet(token, inlinerContext) {
 | |
|   var uriAndMediaQuery = extractImportUrlAndMedia(token[1]);
 | |
|   var uri = uriAndMediaQuery[0];
 | |
|   var mediaQuery = uriAndMediaQuery[1];
 | |
|   var metadata = token[2];
 | |
| 
 | |
|   return isRemoteResource(uri)
 | |
|     ? inlineRemoteStylesheet(uri, mediaQuery, metadata, inlinerContext)
 | |
|     : inlineLocalStylesheet(uri, mediaQuery, metadata, inlinerContext);
 | |
| }
 | |
| 
 | |
| function inlineRemoteStylesheet(uri, mediaQuery, metadata, inlinerContext) {
 | |
|   var isAllowed = isAllowedResource(uri, true, inlinerContext.inline);
 | |
|   var originalUri = uri;
 | |
|   var isLoaded = uri in inlinerContext.externalContext.sourcesContent;
 | |
|   var isRuntimeResource = !hasProtocol(uri);
 | |
| 
 | |
|   if (inlinerContext.inlinedStylesheets.indexOf(uri) > -1) {
 | |
|     inlinerContext.warnings.push('Ignoring remote @import of "' + uri + '" as it has already been imported.');
 | |
|     inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
 | |
|     return doInlineImports(inlinerContext);
 | |
|   } if (inlinerContext.localOnly && inlinerContext.afterContent) {
 | |
|     inlinerContext.warnings.push('Ignoring remote @import of "' + uri + '" as no callback given and after other content.');
 | |
|     inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
 | |
|     return doInlineImports(inlinerContext);
 | |
|   } if (isRuntimeResource) {
 | |
|     inlinerContext.warnings.push('Skipping remote @import of "' + uri + '" as no protocol given.');
 | |
|     inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
 | |
|     inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
 | |
|     return doInlineImports(inlinerContext);
 | |
|   } if (inlinerContext.localOnly && !isLoaded) {
 | |
|     inlinerContext.warnings.push('Skipping remote @import of "' + uri + '" as no callback given.');
 | |
|     inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
 | |
|     inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
 | |
|     return doInlineImports(inlinerContext);
 | |
|   } if (!isAllowed && inlinerContext.afterContent) {
 | |
|     inlinerContext.warnings.push('Ignoring remote @import of "' + uri + '" as resource is not allowed and after other content.');
 | |
|     inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
 | |
|     return doInlineImports(inlinerContext);
 | |
|   } if (!isAllowed) {
 | |
|     inlinerContext.warnings.push('Skipping remote @import of "' + uri + '" as resource is not allowed.');
 | |
|     inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
 | |
|     inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
 | |
|     return doInlineImports(inlinerContext);
 | |
|   }
 | |
| 
 | |
|   inlinerContext.inlinedStylesheets.push(uri);
 | |
| 
 | |
|   function whenLoaded(error, importedStyles) {
 | |
|     if (error) {
 | |
|       inlinerContext.errors.push('Broken @import declaration of "' + uri + '" - ' + error);
 | |
| 
 | |
|       return process.nextTick(function() {
 | |
|         inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
 | |
|         inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
 | |
|         doInlineImports(inlinerContext);
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     inlinerContext.inline = inlinerContext.externalContext.options.inline;
 | |
|     inlinerContext.isRemote = true;
 | |
| 
 | |
|     inlinerContext.externalContext.source = originalUri;
 | |
|     inlinerContext.externalContext.sourcesContent[uri] = importedStyles;
 | |
|     inlinerContext.externalContext.stats.originalSize += importedStyles.length;
 | |
| 
 | |
|     return fromStyles(importedStyles, inlinerContext.externalContext, inlinerContext, function(importedTokens) {
 | |
|       importedTokens = wrapInMedia(importedTokens, mediaQuery, metadata);
 | |
| 
 | |
|       inlinerContext.outputTokens = inlinerContext.outputTokens.concat(importedTokens);
 | |
|       inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
 | |
| 
 | |
|       return doInlineImports(inlinerContext);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   return isLoaded
 | |
|     ? whenLoaded(null, inlinerContext.externalContext.sourcesContent[uri])
 | |
|     : inlinerContext.fetch(uri, inlinerContext.inlineRequest, inlinerContext.inlineTimeout, whenLoaded);
 | |
| }
 | |
| 
 | |
| function inlineLocalStylesheet(uri, mediaQuery, metadata, inlinerContext) {
 | |
|   var protocolLessUri = uri.replace(FILE_RESOURCE_PROTOCOL, '');
 | |
|   var currentPath = path.resolve('');
 | |
|   var absoluteUri = path.isAbsolute(protocolLessUri)
 | |
|     ? path.resolve(currentPath, protocolLessUri[0] == '/' ? protocolLessUri.substring(1) : protocolLessUri)
 | |
|     : path.resolve(inlinerContext.rebaseTo, protocolLessUri);
 | |
|   var relativeToCurrentPath = path.relative(currentPath, absoluteUri);
 | |
|   var importedStyles;
 | |
|   var isAllowed = isAllowedResource(protocolLessUri, false, inlinerContext.inline);
 | |
|   var normalizedPath = normalizePath(relativeToCurrentPath);
 | |
|   var isLoaded = normalizedPath in inlinerContext.externalContext.sourcesContent;
 | |
| 
 | |
|   if (inlinerContext.inlinedStylesheets.indexOf(absoluteUri) > -1) {
 | |
|     inlinerContext.warnings.push('Ignoring local @import of "' + protocolLessUri + '" as it has already been imported.');
 | |
|   } else if (isAllowed && !isLoaded && (!fs.existsSync(absoluteUri) || !fs.statSync(absoluteUri).isFile())) {
 | |
|     inlinerContext.errors.push('Ignoring local @import of "' + protocolLessUri + '" as resource is missing.');
 | |
|   } else if (!isAllowed && inlinerContext.afterContent) {
 | |
|     inlinerContext.warnings.push('Ignoring local @import of "' + protocolLessUri + '" as resource is not allowed and after other content.');
 | |
|   } else if (inlinerContext.afterContent) {
 | |
|     inlinerContext.warnings.push('Ignoring local @import of "' + protocolLessUri + '" as after other content.');
 | |
|   } else if (!isAllowed) {
 | |
|     inlinerContext.warnings.push('Skipping local @import of "' + protocolLessUri + '" as resource is not allowed.');
 | |
|     inlinerContext.outputTokens = inlinerContext.outputTokens.concat(inlinerContext.sourceTokens.slice(0, 1));
 | |
|   } else {
 | |
|     importedStyles = isLoaded
 | |
|       ? inlinerContext.externalContext.sourcesContent[normalizedPath]
 | |
|       : fs.readFileSync(absoluteUri, 'utf-8');
 | |
| 
 | |
|     if (importedStyles.charCodeAt(0) === 65279) {
 | |
|       importedStyles = importedStyles.substring(1);
 | |
|     }
 | |
| 
 | |
|     inlinerContext.inlinedStylesheets.push(absoluteUri);
 | |
|     inlinerContext.inline = inlinerContext.externalContext.options.inline;
 | |
| 
 | |
|     inlinerContext.externalContext.source = normalizedPath;
 | |
|     inlinerContext.externalContext.sourcesContent[normalizedPath] = importedStyles;
 | |
|     inlinerContext.externalContext.stats.originalSize += importedStyles.length;
 | |
| 
 | |
|     return fromStyles(importedStyles, inlinerContext.externalContext, inlinerContext, function(importedTokens) {
 | |
|       importedTokens = wrapInMedia(importedTokens, mediaQuery, metadata);
 | |
| 
 | |
|       inlinerContext.outputTokens = inlinerContext.outputTokens.concat(importedTokens);
 | |
|       inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
 | |
| 
 | |
|       return doInlineImports(inlinerContext);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   inlinerContext.sourceTokens = inlinerContext.sourceTokens.slice(1);
 | |
| 
 | |
|   return doInlineImports(inlinerContext);
 | |
| }
 | |
| 
 | |
| function wrapInMedia(tokens, mediaQuery, metadata) {
 | |
|   if (mediaQuery) {
 | |
|     return [[Token.NESTED_BLOCK, [[Token.NESTED_BLOCK_SCOPE, '@media ' + mediaQuery, metadata]], tokens]];
 | |
|   }
 | |
|   return tokens;
 | |
| }
 | |
| 
 | |
| module.exports = readSources;
 |