Spaces:
Running
Running
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; | |