mirror of
https://github.com/idanoo/GoScrobble.git
synced 2024-11-24 09:25:15 +00:00
1113 lines
36 KiB
JavaScript
1113 lines
36 KiB
JavaScript
import nodePath from 'path';
|
|
import { SourceMapGenerator } from 'source-map';
|
|
import convert from 'convert-source-map';
|
|
import findRoot from 'find-root';
|
|
import memoize from '@emotion/memoize';
|
|
import hashString from '@emotion/hash';
|
|
import escapeRegexp from 'escape-string-regexp';
|
|
import { serializeStyles } from '@emotion/serialize';
|
|
import { addNamed, addDefault } from '@babel/helper-module-imports';
|
|
import { createMacro } from 'babel-plugin-macros';
|
|
|
|
// babel-plugin-styled-components
|
|
// https://github.com/styled-components/babel-plugin-styled-components/blob/8d44acc36f067d60d4e09f9c22ff89695bc332d2/src/minify/index.js
|
|
var multilineCommentRegex = /\/\*[^!](.|[\r\n])*?\*\//g;
|
|
var lineCommentStart = /\/\//g;
|
|
var symbolRegex = /(\s*[;:{},]\s*)/g; // Counts occurences of substr inside str
|
|
|
|
var countOccurences = function countOccurences(str, substr) {
|
|
return str.split(substr).length - 1;
|
|
}; // Joins substrings until predicate returns true
|
|
|
|
|
|
var reduceSubstr = function reduceSubstr(substrs, join, predicate) {
|
|
var length = substrs.length;
|
|
var res = substrs[0];
|
|
|
|
if (length === 1) {
|
|
return res;
|
|
}
|
|
|
|
for (var i = 1; i < length; i++) {
|
|
if (predicate(res)) {
|
|
break;
|
|
}
|
|
|
|
res += join + substrs[i];
|
|
}
|
|
|
|
return res;
|
|
}; // Joins at comment starts when it's inside a string or parantheses
|
|
// effectively removing line comments
|
|
|
|
|
|
var stripLineComment = function stripLineComment(line) {
|
|
return reduceSubstr(line.split(lineCommentStart), '//', function (str) {
|
|
return !str.endsWith(':') && // NOTE: This is another guard against urls, if they're not inside strings or parantheses.
|
|
countOccurences(str, "'") % 2 === 0 && countOccurences(str, '"') % 2 === 0 && countOccurences(str, '(') === countOccurences(str, ')');
|
|
});
|
|
};
|
|
var compressSymbols = function compressSymbols(code) {
|
|
return code.split(symbolRegex).reduce(function (str, fragment, index) {
|
|
// Even-indices are non-symbol fragments
|
|
if (index % 2 === 0) {
|
|
return str + fragment;
|
|
} // Only manipulate symbols outside of strings
|
|
|
|
|
|
if (countOccurences(str, "'") % 2 === 0 && countOccurences(str, '"') % 2 === 0) {
|
|
return str + fragment.trim();
|
|
}
|
|
|
|
return str + fragment;
|
|
}, '');
|
|
}; // Detects lines that are exclusively line comments
|
|
|
|
var isLineComment = function isLineComment(line) {
|
|
return line.trim().startsWith('//');
|
|
};
|
|
|
|
var linebreakRegex = /[\r\n]\s*/g;
|
|
var spacesAndLinebreakRegex = /\s+|\n+/g;
|
|
|
|
function multilineReplacer(match) {
|
|
// When we encounter a standard multi-line CSS comment and it contains a '@'
|
|
// character, we keep the comment but optimize it into a single line. Some
|
|
// Stylis plugins, such as the stylis-rtl via the cssjanus plugin, use this
|
|
// special comment syntax to control behavior (such as: /* @noflip */).
|
|
// We can do this with standard CSS comments because they will work with
|
|
// compression, as opposed to non-standard single-line comments that will
|
|
// break compressed CSS. If the comment doesn't contain '@', then we replace
|
|
// it with a line break, which effectively removes it from the output.
|
|
var keepComment = match.indexOf('@') > -1;
|
|
|
|
if (keepComment) {
|
|
return match.replace(spacesAndLinebreakRegex, ' ').trim();
|
|
}
|
|
|
|
return '\n';
|
|
}
|
|
|
|
var minify = function minify(code) {
|
|
var newCode = code.replace(multilineCommentRegex, multilineReplacer) // If allowed, remove line breaks and extra space from multi-line comments so they appear on one line
|
|
.split(linebreakRegex) // Split at newlines
|
|
.filter(function (line) {
|
|
return line.length > 0 && !isLineComment(line);
|
|
}) // Removes lines containing only line comments
|
|
.map(stripLineComment) // Remove line comments inside text
|
|
.join(' '); // Rejoin all lines
|
|
|
|
return compressSymbols(newCode);
|
|
};
|
|
|
|
function getExpressionsFromTemplateLiteral(node, t) {
|
|
var raw = createRawStringFromTemplateLiteral(node);
|
|
var minified = minify(raw);
|
|
return replacePlaceholdersWithExpressions(minified, node.expressions || [], t);
|
|
}
|
|
|
|
var interleave = function interleave(strings, interpolations) {
|
|
return interpolations.reduce(function (array, interp, i) {
|
|
return array.concat([interp], strings[i + 1]);
|
|
}, [strings[0]]);
|
|
};
|
|
|
|
function getDynamicMatches(str) {
|
|
var re = /xxx(\d+)xxx/gm;
|
|
var match;
|
|
var matches = [];
|
|
|
|
while ((match = re.exec(str)) !== null) {
|
|
// so that flow doesn't complain
|
|
if (match !== null) {
|
|
matches.push({
|
|
value: match[0],
|
|
p1: parseInt(match[1], 10),
|
|
index: match.index
|
|
});
|
|
}
|
|
}
|
|
|
|
return matches;
|
|
}
|
|
|
|
function replacePlaceholdersWithExpressions(str, expressions, t) {
|
|
var matches = getDynamicMatches(str);
|
|
|
|
if (matches.length === 0) {
|
|
if (str === '') {
|
|
return [];
|
|
}
|
|
|
|
return [t.stringLiteral(str)];
|
|
}
|
|
|
|
var strings = [];
|
|
var finalExpressions = [];
|
|
var cursor = 0;
|
|
matches.forEach(function (_ref, i) {
|
|
var value = _ref.value,
|
|
p1 = _ref.p1,
|
|
index = _ref.index;
|
|
var preMatch = str.substring(cursor, index);
|
|
cursor = cursor + preMatch.length + value.length;
|
|
|
|
if (preMatch) {
|
|
strings.push(t.stringLiteral(preMatch));
|
|
} else if (i === 0) {
|
|
strings.push(t.stringLiteral(''));
|
|
}
|
|
|
|
finalExpressions.push(expressions[p1]);
|
|
|
|
if (i === matches.length - 1) {
|
|
strings.push(t.stringLiteral(str.substring(index + value.length)));
|
|
}
|
|
});
|
|
return interleave(strings, finalExpressions).filter(function (node) {
|
|
return node.value !== '';
|
|
});
|
|
}
|
|
|
|
function createRawStringFromTemplateLiteral(quasi) {
|
|
var strs = quasi.quasis.map(function (x) {
|
|
return x.value.cooked;
|
|
});
|
|
var src = strs.reduce(function (arr, str, i) {
|
|
arr.push(str);
|
|
|
|
if (i !== strs.length - 1) {
|
|
arr.push("xxx" + i + "xxx");
|
|
}
|
|
|
|
return arr;
|
|
}, []).join('').trim();
|
|
return src;
|
|
}
|
|
|
|
var invalidClassNameCharacters = /[!"#$%&'()*+,./:;<=>?@[\]^`|}~{]/g;
|
|
|
|
var sanitizeLabelPart = function sanitizeLabelPart(labelPart) {
|
|
return labelPart.trim().replace(invalidClassNameCharacters, '-');
|
|
};
|
|
|
|
function getLabel(identifierName, autoLabel, labelFormat, filename) {
|
|
if (!identifierName || !autoLabel) return null;
|
|
if (!labelFormat) return sanitizeLabelPart(identifierName);
|
|
var parsedPath = nodePath.parse(filename);
|
|
var localDirname = nodePath.basename(parsedPath.dir);
|
|
var localFilename = parsedPath.name;
|
|
|
|
if (localFilename === 'index') {
|
|
localFilename = localDirname;
|
|
}
|
|
|
|
return labelFormat.replace(/\[local\]/gi, sanitizeLabelPart(identifierName)).replace(/\[filename\]/gi, sanitizeLabelPart(localFilename)).replace(/\[dirname\]/gi, sanitizeLabelPart(localDirname));
|
|
}
|
|
|
|
function getLabelFromPath(path, state, t) {
|
|
return getLabel(getIdentifierName(path, t), state.opts.autoLabel === undefined ? process.env.NODE_ENV !== 'production' : state.opts.autoLabel, state.opts.labelFormat, state.file.opts.filename);
|
|
}
|
|
var pascalCaseRegex = /^[A-Z][A-Za-z]+/;
|
|
|
|
function getDeclaratorName(path, t) {
|
|
// $FlowFixMe
|
|
var parent = path.findParent(function (p) {
|
|
return p.isVariableDeclarator() || p.isFunctionDeclaration() || p.isFunctionExpression() || p.isArrowFunctionExpression() || p.isObjectProperty();
|
|
});
|
|
|
|
if (!parent) {
|
|
return '';
|
|
} // we probably have a css call assigned to a variable
|
|
// so we'll just return the variable name
|
|
|
|
|
|
if (parent.isVariableDeclarator()) {
|
|
if (t.isIdentifier(parent.node.id)) {
|
|
return parent.node.id.name;
|
|
}
|
|
|
|
return '';
|
|
} // we probably have an inline css prop usage
|
|
|
|
|
|
if (parent.isFunctionDeclaration()) {
|
|
var _name = parent.node.id.name;
|
|
|
|
if (pascalCaseRegex.test(_name)) {
|
|
return _name;
|
|
}
|
|
|
|
return '';
|
|
} // we could also have an object property
|
|
|
|
|
|
if (parent.isObjectProperty() && !parent.node.computed) {
|
|
return parent.node.key.name;
|
|
}
|
|
|
|
var variableDeclarator = path.findParent(function (p) {
|
|
return p.isVariableDeclarator();
|
|
});
|
|
|
|
if (!variableDeclarator) {
|
|
return '';
|
|
}
|
|
|
|
var name = variableDeclarator.node.id.name;
|
|
|
|
if (pascalCaseRegex.test(name)) {
|
|
return name;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
function getIdentifierName(path, t) {
|
|
var classOrClassPropertyParent;
|
|
|
|
if (t.isObjectProperty(path.parentPath) && path.parentPath.node.computed === false && (t.isIdentifier(path.parentPath.node.key) || t.isStringLiteral(path.parentPath.node.key))) {
|
|
return path.parentPath.node.key.name || path.parentPath.node.key.value;
|
|
}
|
|
|
|
if (path) {
|
|
// $FlowFixMe
|
|
classOrClassPropertyParent = path.findParent(function (p) {
|
|
return t.isClassProperty(p) || t.isClass(p);
|
|
});
|
|
}
|
|
|
|
if (classOrClassPropertyParent) {
|
|
if (t.isClassProperty(classOrClassPropertyParent) && classOrClassPropertyParent.node.computed === false && t.isIdentifier(classOrClassPropertyParent.node.key)) {
|
|
return classOrClassPropertyParent.node.key.name;
|
|
}
|
|
|
|
if (t.isClass(classOrClassPropertyParent) && classOrClassPropertyParent.node.id) {
|
|
return t.isIdentifier(classOrClassPropertyParent.node.id) ? classOrClassPropertyParent.node.id.name : '';
|
|
}
|
|
}
|
|
|
|
var declaratorName = getDeclaratorName(path, t); // if the name starts with _ it was probably generated by babel so we should ignore it
|
|
|
|
if (declaratorName.charAt(0) === '_') {
|
|
return '';
|
|
}
|
|
|
|
return declaratorName;
|
|
}
|
|
|
|
function getGeneratorOpts(file) {
|
|
return file.opts.generatorOpts ? file.opts.generatorOpts : file.opts;
|
|
}
|
|
|
|
function makeSourceMapGenerator(file) {
|
|
var generatorOpts = getGeneratorOpts(file);
|
|
var filename = generatorOpts.sourceFileName;
|
|
var generator = new SourceMapGenerator({
|
|
file: filename,
|
|
sourceRoot: generatorOpts.sourceRoot
|
|
});
|
|
generator.setSourceContent(filename, file.code);
|
|
return generator;
|
|
}
|
|
function getSourceMap(offset, state) {
|
|
var generator = makeSourceMapGenerator(state.file);
|
|
var generatorOpts = getGeneratorOpts(state.file);
|
|
|
|
if (generatorOpts.sourceFileName && generatorOpts.sourceFileName !== 'unknown') {
|
|
generator.addMapping({
|
|
generated: {
|
|
line: 1,
|
|
column: 0
|
|
},
|
|
source: generatorOpts.sourceFileName,
|
|
original: offset
|
|
});
|
|
return convert.fromObject(generator).toComment({
|
|
multiline: true
|
|
});
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
var hashArray = function hashArray(arr) {
|
|
return hashString(arr.join(''));
|
|
};
|
|
|
|
var unsafeRequire = require;
|
|
var getPackageRootPath = memoize(function (filename) {
|
|
return findRoot(filename);
|
|
});
|
|
var separator = new RegExp(escapeRegexp(nodePath.sep), 'g');
|
|
|
|
var normalizePath = function normalizePath(path) {
|
|
return nodePath.normalize(path).replace(separator, '/');
|
|
};
|
|
|
|
function getTargetClassName(state, t) {
|
|
if (state.emotionTargetClassNameCount === undefined) {
|
|
state.emotionTargetClassNameCount = 0;
|
|
}
|
|
|
|
var hasFilepath = state.file.opts.filename && state.file.opts.filename !== 'unknown';
|
|
var filename = hasFilepath ? state.file.opts.filename : ''; // normalize the file path to ignore folder structure
|
|
// outside the current node project and arch-specific delimiters
|
|
|
|
var moduleName = '';
|
|
var rootPath = filename;
|
|
|
|
try {
|
|
rootPath = getPackageRootPath(filename);
|
|
moduleName = unsafeRequire(rootPath + '/package.json').name;
|
|
} catch (err) {}
|
|
|
|
var finalPath = filename === rootPath ? 'root' : filename.slice(rootPath.length);
|
|
var positionInFile = state.emotionTargetClassNameCount++;
|
|
var stuffToHash = [moduleName];
|
|
|
|
if (finalPath) {
|
|
stuffToHash.push(normalizePath(finalPath));
|
|
} else {
|
|
stuffToHash.push(state.file.code);
|
|
}
|
|
|
|
var stableClassName = "e" + hashArray(stuffToHash) + positionInFile;
|
|
return stableClassName;
|
|
}
|
|
|
|
// it's meant to simplify the most common cases so i don't want to make it especially complex
|
|
// also, this will be unnecessary when prepack is ready
|
|
|
|
function simplifyObject(node, t) {
|
|
var finalString = '';
|
|
|
|
for (var i = 0; i < node.properties.length; i++) {
|
|
var _ref;
|
|
|
|
var property = node.properties[i];
|
|
|
|
if (!t.isObjectProperty(property) || property.computed || !t.isIdentifier(property.key) && !t.isStringLiteral(property.key) || !t.isStringLiteral(property.value) && !t.isNumericLiteral(property.value) && !t.isObjectExpression(property.value)) {
|
|
return node;
|
|
}
|
|
|
|
var key = property.key.name || property.key.value;
|
|
|
|
if (key === 'styles') {
|
|
return node;
|
|
}
|
|
|
|
if (t.isObjectExpression(property.value)) {
|
|
var simplifiedChild = simplifyObject(property.value, t);
|
|
|
|
if (!t.isStringLiteral(simplifiedChild)) {
|
|
return node;
|
|
}
|
|
|
|
finalString += key + "{" + simplifiedChild.value + "}";
|
|
continue;
|
|
}
|
|
|
|
var value = property.value.value;
|
|
finalString += serializeStyles([(_ref = {}, _ref[key] = value, _ref)]).styles;
|
|
}
|
|
|
|
return t.stringLiteral(finalString);
|
|
}
|
|
|
|
// this only works correctly in modules, but we don't run on scripts anyway, so it's fine
|
|
// the difference is that in modules template objects are being cached per call site
|
|
function getTypeScriptMakeTemplateObjectPath(path) {
|
|
if (path.node.arguments.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
var firstArgPath = path.get('arguments')[0];
|
|
|
|
if (firstArgPath.isLogicalExpression() && firstArgPath.get('left').isIdentifier() && firstArgPath.get('right').isAssignmentExpression() && firstArgPath.get('right.right').isCallExpression() && firstArgPath.get('right.right.callee').isIdentifier() && firstArgPath.node.right.right.callee.name.includes('makeTemplateObject') && firstArgPath.node.right.right.arguments.length === 2) {
|
|
return firstArgPath.get('right.right');
|
|
}
|
|
|
|
return null;
|
|
} // this is only used to prevent appending strings/expressions to arguments incorectly
|
|
// we could push them to found array expressions, as we do it for TS-transpile output ¯\_(ツ)_/¯
|
|
// it seems overly complicated though - mainly because we'd also have to check against existing stuff of a particular type (source maps & labels)
|
|
// considering Babel double-transpilation as a valid use case seems rather far-fetched
|
|
|
|
function isTaggedTemplateTranspiledByBabel(path) {
|
|
if (path.node.arguments.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
var firstArgPath = path.get('arguments')[0];
|
|
|
|
if (!firstArgPath.isCallExpression() || !firstArgPath.get('callee').isIdentifier()) {
|
|
return false;
|
|
}
|
|
|
|
var calleeName = firstArgPath.node.callee.name;
|
|
|
|
if (!calleeName.includes('templateObject')) {
|
|
return false;
|
|
}
|
|
|
|
var bindingPath = path.scope.getBinding(calleeName).path;
|
|
|
|
if (!bindingPath.isFunction()) {
|
|
return false;
|
|
}
|
|
|
|
var functionBody = bindingPath.get('body.body');
|
|
|
|
if (!functionBody[0].isVariableDeclaration()) {
|
|
return false;
|
|
}
|
|
|
|
var declarationInit = functionBody[0].get('declarations')[0].get('init');
|
|
|
|
if (!declarationInit.isCallExpression()) {
|
|
return false;
|
|
}
|
|
|
|
var declarationInitArguments = declarationInit.get('arguments');
|
|
|
|
if (declarationInitArguments.length === 0 || declarationInitArguments.length > 2 || declarationInitArguments.some(function (argPath) {
|
|
return !argPath.isArrayExpression();
|
|
})) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
var appendStringToArguments = function appendStringToArguments(path, string, t) {
|
|
if (!string) {
|
|
return;
|
|
}
|
|
|
|
var args = path.node.arguments;
|
|
|
|
if (t.isStringLiteral(args[args.length - 1])) {
|
|
args[args.length - 1].value += string;
|
|
} else {
|
|
var makeTemplateObjectCallPath = getTypeScriptMakeTemplateObjectPath(path);
|
|
|
|
if (makeTemplateObjectCallPath) {
|
|
makeTemplateObjectCallPath.get('arguments').forEach(function (argPath) {
|
|
var elements = argPath.get('elements');
|
|
var lastElement = elements[elements.length - 1];
|
|
lastElement.replaceWith(t.stringLiteral(lastElement.node.value + string));
|
|
});
|
|
} else if (!isTaggedTemplateTranspiledByBabel(path)) {
|
|
args.push(t.stringLiteral(string));
|
|
}
|
|
}
|
|
};
|
|
var joinStringLiterals = function joinStringLiterals(expressions, t) {
|
|
return expressions.reduce(function (finalExpressions, currentExpression, i) {
|
|
if (!t.isStringLiteral(currentExpression)) {
|
|
finalExpressions.push(currentExpression);
|
|
} else if (t.isStringLiteral(finalExpressions[finalExpressions.length - 1])) {
|
|
finalExpressions[finalExpressions.length - 1].value += currentExpression.value;
|
|
} else {
|
|
finalExpressions.push(currentExpression);
|
|
}
|
|
|
|
return finalExpressions;
|
|
}, []);
|
|
};
|
|
|
|
var CSS_OBJECT_STRINGIFIED_ERROR = "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; // with babel@6 fallback
|
|
|
|
var cloneNode = function cloneNode(t, node) {
|
|
return typeof t.cloneNode === 'function' ? t.cloneNode(node) : t.cloneDeep(node);
|
|
};
|
|
|
|
function createSourceMapConditional(t, production, development) {
|
|
return t.conditionalExpression(t.binaryExpression('===', t.memberExpression(t.memberExpression(t.identifier('process'), t.identifier('env')), t.identifier('NODE_ENV')), t.stringLiteral('production')), production, development);
|
|
}
|
|
|
|
var transformExpressionWithStyles = function transformExpressionWithStyles(_ref) {
|
|
var babel = _ref.babel,
|
|
state = _ref.state,
|
|
path = _ref.path,
|
|
shouldLabel = _ref.shouldLabel,
|
|
_ref$sourceMap = _ref.sourceMap,
|
|
sourceMap = _ref$sourceMap === void 0 ? '' : _ref$sourceMap;
|
|
var t = babel.types;
|
|
|
|
if (t.isTaggedTemplateExpression(path)) {
|
|
var expressions = getExpressionsFromTemplateLiteral(path.node.quasi, t);
|
|
|
|
if (state.emotionSourceMap && path.node.quasi.loc !== undefined) {
|
|
sourceMap = getSourceMap(path.node.quasi.loc.start, state);
|
|
}
|
|
|
|
path.replaceWith(t.callExpression(path.node.tag, expressions));
|
|
}
|
|
|
|
if (t.isCallExpression(path)) {
|
|
var canAppendStrings = path.node.arguments.every(function (arg) {
|
|
return arg.type !== 'SpreadElement';
|
|
});
|
|
|
|
if (canAppendStrings && shouldLabel) {
|
|
var label = getLabelFromPath(path, state, t);
|
|
|
|
if (label) {
|
|
appendStringToArguments(path, ";label:" + label + ";", t);
|
|
}
|
|
}
|
|
|
|
path.get('arguments').forEach(function (node) {
|
|
if (t.isObjectExpression(node)) {
|
|
node.replaceWith(simplifyObject(node.node, t));
|
|
}
|
|
});
|
|
path.node.arguments = joinStringLiterals(path.node.arguments, t);
|
|
|
|
if (canAppendStrings && state.emotionSourceMap && !sourceMap && path.node.loc !== undefined) {
|
|
sourceMap = getSourceMap(path.node.loc.start, state);
|
|
}
|
|
|
|
if (path.node.arguments.length === 1 && t.isStringLiteral(path.node.arguments[0])) {
|
|
var cssString = path.node.arguments[0].value;
|
|
var res = serializeStyles([cssString]);
|
|
var prodNode = t.objectExpression([t.objectProperty(t.identifier('name'), t.stringLiteral(res.name)), t.objectProperty(t.identifier('styles'), t.stringLiteral(res.styles))]);
|
|
var node = prodNode;
|
|
|
|
if (sourceMap) {
|
|
if (!state.emotionStringifiedCssId) {
|
|
var uid = state.file.scope.generateUidIdentifier('__EMOTION_STRINGIFIED_CSS_ERROR__');
|
|
state.emotionStringifiedCssId = uid;
|
|
var cssObjectToString = t.functionDeclaration(uid, [], t.blockStatement([t.returnStatement(t.stringLiteral(CSS_OBJECT_STRINGIFIED_ERROR))]));
|
|
cssObjectToString._compact = true;
|
|
state.file.path.unshiftContainer('body', [cssObjectToString]);
|
|
}
|
|
|
|
var devNode = t.objectExpression([t.objectProperty(t.identifier('name'), t.stringLiteral(res.name)), t.objectProperty(t.identifier('styles'), t.stringLiteral(res.styles)), t.objectProperty(t.identifier('map'), t.stringLiteral(sourceMap)), t.objectProperty(t.identifier('toString'), cloneNode(t, state.emotionStringifiedCssId))]);
|
|
node = createSourceMapConditional(t, prodNode, devNode);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
if (sourceMap) {
|
|
var lastIndex = path.node.arguments.length - 1;
|
|
var last = path.node.arguments[lastIndex];
|
|
var sourceMapConditional = createSourceMapConditional(t, t.stringLiteral(''), t.stringLiteral(sourceMap));
|
|
|
|
if (t.isStringLiteral(last)) {
|
|
path.node.arguments[lastIndex] = t.binaryExpression('+', last, sourceMapConditional);
|
|
} else {
|
|
var makeTemplateObjectCallPath = getTypeScriptMakeTemplateObjectPath(path);
|
|
|
|
if (makeTemplateObjectCallPath) {
|
|
var sourceMapId = state.file.scope.generateUidIdentifier('emotionSourceMap');
|
|
var sourceMapDeclaration = t.variableDeclaration('var', [t.variableDeclarator(sourceMapId, sourceMapConditional)]);
|
|
sourceMapDeclaration._compact = true;
|
|
state.file.path.unshiftContainer('body', [sourceMapDeclaration]);
|
|
makeTemplateObjectCallPath.get('arguments').forEach(function (argPath) {
|
|
var elements = argPath.get('elements');
|
|
var lastElement = elements[elements.length - 1];
|
|
lastElement.replaceWith(t.binaryExpression('+', lastElement.node, cloneNode(t, sourceMapId)));
|
|
});
|
|
} else if (!isTaggedTemplateTranspiledByBabel(path)) {
|
|
path.node.arguments.push(sourceMapConditional);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var getKnownProperties = function getKnownProperties(t, node) {
|
|
return new Set(node.properties.filter(function (n) {
|
|
return t.isObjectProperty(n) && !n.computed;
|
|
}).map(function (n) {
|
|
return t.isIdentifier(n.key) ? n.key.name : n.key.value;
|
|
}));
|
|
};
|
|
|
|
var getStyledOptions = function getStyledOptions(t, path, state) {
|
|
var args = path.node.arguments;
|
|
var optionsArgument = args.length >= 2 ? args[1] : null;
|
|
var properties = [];
|
|
var knownProperties = optionsArgument && t.isObjectExpression(optionsArgument) ? getKnownProperties(t, optionsArgument) : new Set();
|
|
|
|
if (!knownProperties.has('target')) {
|
|
properties.push(t.objectProperty(t.identifier('target'), t.stringLiteral(getTargetClassName(state))));
|
|
}
|
|
|
|
var label = getLabelFromPath(path, state, t);
|
|
|
|
if (label && !knownProperties.has('label')) {
|
|
properties.push(t.objectProperty(t.identifier('label'), t.stringLiteral(label)));
|
|
}
|
|
|
|
if (optionsArgument) {
|
|
if (!t.isObjectExpression(optionsArgument)) {
|
|
return t.callExpression(state.file.addHelper('extends'), [t.objectExpression([]), t.objectExpression(properties), optionsArgument]);
|
|
}
|
|
|
|
properties.unshift.apply(properties, optionsArgument.properties);
|
|
}
|
|
|
|
return t.objectExpression( // $FlowFixMe
|
|
properties);
|
|
};
|
|
|
|
var createEmotionMacro = function createEmotionMacro(instancePath) {
|
|
return createMacro(function macro(_ref) {
|
|
var references = _ref.references,
|
|
state = _ref.state,
|
|
babel = _ref.babel,
|
|
isEmotionCall = _ref.isEmotionCall;
|
|
|
|
if (!isEmotionCall) {
|
|
state.emotionSourceMap = true;
|
|
}
|
|
|
|
var t = babel.types;
|
|
Object.keys(references).forEach(function (referenceKey) {
|
|
var isPure = true;
|
|
var runtimeNode = addNamed(state.file.path, referenceKey, instancePath);
|
|
|
|
switch (referenceKey) {
|
|
case 'injectGlobal':
|
|
{
|
|
isPure = false;
|
|
}
|
|
// eslint-disable-next-line no-fallthrough
|
|
|
|
case 'css':
|
|
case 'keyframes':
|
|
{
|
|
references[referenceKey].reverse().forEach(function (reference) {
|
|
var path = reference.parentPath;
|
|
reference.replaceWith(t.cloneDeep(runtimeNode));
|
|
|
|
if (isPure) {
|
|
path.addComment('leading', '#__PURE__');
|
|
}
|
|
|
|
var node = transformExpressionWithStyles({
|
|
babel: babel,
|
|
state: state,
|
|
path: path,
|
|
shouldLabel: true
|
|
});
|
|
|
|
if (node) {
|
|
path.node.arguments[0] = node;
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
references[referenceKey].reverse().forEach(function (reference) {
|
|
reference.replaceWith(t.cloneDeep(runtimeNode));
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
var createStyledMacro = function createStyledMacro(_ref) {
|
|
var importPath = _ref.importPath,
|
|
_ref$originalImportPa = _ref.originalImportPath,
|
|
originalImportPath = _ref$originalImportPa === void 0 ? importPath : _ref$originalImportPa,
|
|
isWeb = _ref.isWeb;
|
|
return createMacro(function (_ref2) {
|
|
var references = _ref2.references,
|
|
state = _ref2.state,
|
|
babel = _ref2.babel,
|
|
isEmotionCall = _ref2.isEmotionCall;
|
|
|
|
if (!isEmotionCall) {
|
|
state.emotionSourceMap = true;
|
|
}
|
|
|
|
var t = babel.types;
|
|
|
|
if (references["default"] && references["default"].length) {
|
|
var _styledIdentifier;
|
|
|
|
var getStyledIdentifier = function getStyledIdentifier() {
|
|
if (_styledIdentifier === undefined) {
|
|
_styledIdentifier = addDefault(state.file.path, importPath, {
|
|
nameHint: 'styled'
|
|
});
|
|
}
|
|
|
|
return t.cloneDeep(_styledIdentifier);
|
|
};
|
|
|
|
var originalImportPathStyledIdentifier;
|
|
|
|
var getOriginalImportPathStyledIdentifier = function getOriginalImportPathStyledIdentifier() {
|
|
if (originalImportPathStyledIdentifier === undefined) {
|
|
originalImportPathStyledIdentifier = addDefault(state.file.path, originalImportPath, {
|
|
nameHint: 'styled'
|
|
});
|
|
}
|
|
|
|
return t.cloneDeep(originalImportPathStyledIdentifier);
|
|
};
|
|
|
|
if (importPath === originalImportPath) {
|
|
getOriginalImportPathStyledIdentifier = getStyledIdentifier;
|
|
}
|
|
|
|
references["default"].forEach(function (reference) {
|
|
var isCall = false;
|
|
|
|
if (t.isMemberExpression(reference.parent) && reference.parent.computed === false) {
|
|
isCall = true;
|
|
|
|
if ( // checks if the first character is lowercase
|
|
// becasue we don't want to transform the member expression if
|
|
// it's in primitives/native
|
|
reference.parent.property.name.charCodeAt(0) > 96) {
|
|
reference.parentPath.replaceWith(t.callExpression(getStyledIdentifier(), [t.stringLiteral(reference.parent.property.name)]));
|
|
} else {
|
|
reference.replaceWith(getStyledIdentifier());
|
|
}
|
|
} else if (reference.parentPath && reference.parentPath.parentPath && t.isCallExpression(reference.parentPath) && reference.parent.callee === reference.node) {
|
|
isCall = true;
|
|
reference.replaceWith(getStyledIdentifier());
|
|
} else {
|
|
reference.replaceWith(getOriginalImportPathStyledIdentifier());
|
|
}
|
|
|
|
if (reference.parentPath && reference.parentPath.parentPath) {
|
|
var styledCallPath = reference.parentPath.parentPath;
|
|
var node = transformExpressionWithStyles({
|
|
path: styledCallPath,
|
|
state: state,
|
|
babel: babel,
|
|
shouldLabel: false
|
|
});
|
|
|
|
if (node && isWeb) {
|
|
// we know the argument length will be 1 since that's the only time we will have a node since it will be static
|
|
styledCallPath.node.arguments[0] = node;
|
|
}
|
|
}
|
|
|
|
if (isCall) {
|
|
reference.addComment('leading', '#__PURE__');
|
|
|
|
if (isWeb) {
|
|
reference.parentPath.node.arguments[1] = getStyledOptions(t, reference.parentPath, state);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
Object.keys(references).filter(function (x) {
|
|
return x !== 'default';
|
|
}).forEach(function (referenceKey) {
|
|
var runtimeNode = addNamed(state.file.path, referenceKey, importPath);
|
|
references[referenceKey].reverse().forEach(function (reference) {
|
|
reference.replaceWith(t.cloneDeep(runtimeNode));
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
var transformCssCallExpression = function transformCssCallExpression(_ref) {
|
|
var babel = _ref.babel,
|
|
state = _ref.state,
|
|
path = _ref.path,
|
|
sourceMap = _ref.sourceMap;
|
|
var node = transformExpressionWithStyles({
|
|
babel: babel,
|
|
state: state,
|
|
path: path,
|
|
shouldLabel: true,
|
|
sourceMap: sourceMap
|
|
});
|
|
|
|
if (node) {
|
|
path.replaceWith(node);
|
|
path.hoist();
|
|
} else if (path.isCallExpression()) {
|
|
path.addComment('leading', '#__PURE__');
|
|
}
|
|
};
|
|
var cssMacro = createMacro(function (_ref2) {
|
|
var references = _ref2.references,
|
|
state = _ref2.state,
|
|
babel = _ref2.babel,
|
|
isEmotionCall = _ref2.isEmotionCall;
|
|
|
|
if (!isEmotionCall) {
|
|
state.emotionSourceMap = true;
|
|
}
|
|
|
|
var t = babel.types;
|
|
|
|
if (references["default"] && references["default"].length) {
|
|
references["default"].reverse().forEach(function (reference) {
|
|
if (!state.cssIdentifier) {
|
|
state.cssIdentifier = addDefault(reference, '@emotion/css', {
|
|
nameHint: 'css'
|
|
});
|
|
}
|
|
|
|
reference.replaceWith(t.cloneDeep(state.cssIdentifier));
|
|
transformCssCallExpression({
|
|
babel: babel,
|
|
state: state,
|
|
path: reference.parentPath
|
|
});
|
|
});
|
|
}
|
|
|
|
Object.keys(references).filter(function (x) {
|
|
return x !== 'default';
|
|
}).forEach(function (referenceKey) {
|
|
var runtimeNode = addNamed(state.file.path, referenceKey, '@emotion/css', {
|
|
nameHint: referenceKey
|
|
});
|
|
references[referenceKey].reverse().forEach(function (reference) {
|
|
reference.replaceWith(t.cloneDeep(runtimeNode));
|
|
});
|
|
});
|
|
});
|
|
|
|
var webStyledMacro = createStyledMacro({
|
|
importPath: '@emotion/styled-base',
|
|
originalImportPath: '@emotion/styled',
|
|
isWeb: true
|
|
});
|
|
var nativeStyledMacro = createStyledMacro({
|
|
importPath: '@emotion/native',
|
|
originalImportPath: '@emotion/native',
|
|
isWeb: false
|
|
});
|
|
var primitivesStyledMacro = createStyledMacro({
|
|
importPath: '@emotion/primitives',
|
|
originalImportPath: '@emotion/primitives',
|
|
isWeb: false
|
|
});
|
|
var macros = {
|
|
createEmotionMacro: createEmotionMacro,
|
|
css: cssMacro,
|
|
createStyledMacro: createStyledMacro
|
|
};
|
|
|
|
var emotionCoreMacroThatsNotARealMacro = function emotionCoreMacroThatsNotARealMacro(_ref) {
|
|
var references = _ref.references,
|
|
state = _ref.state,
|
|
babel = _ref.babel;
|
|
Object.keys(references).forEach(function (refKey) {
|
|
if (refKey === 'css') {
|
|
references[refKey].forEach(function (path) {
|
|
transformCssCallExpression({
|
|
babel: babel,
|
|
state: state,
|
|
path: path.parentPath
|
|
});
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
emotionCoreMacroThatsNotARealMacro.keepImport = true;
|
|
|
|
function getAbsolutePath(instancePath, rootPath) {
|
|
if (instancePath.charAt(0) === '.') {
|
|
var absoluteInstancePath = nodePath.resolve(rootPath, instancePath);
|
|
return absoluteInstancePath;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getInstancePathToCompare(instancePath, rootPath) {
|
|
var absolutePath = getAbsolutePath(instancePath, rootPath);
|
|
|
|
if (absolutePath === false) {
|
|
return instancePath;
|
|
}
|
|
|
|
return absolutePath;
|
|
}
|
|
|
|
function index (babel) {
|
|
var t = babel.types;
|
|
return {
|
|
name: 'emotion',
|
|
inherits: require('babel-plugin-syntax-jsx'),
|
|
visitor: {
|
|
ImportDeclaration: function ImportDeclaration(path, state) {
|
|
var hasFilepath = path.hub.file.opts.filename && path.hub.file.opts.filename !== 'unknown';
|
|
var dirname = hasFilepath ? nodePath.dirname(path.hub.file.opts.filename) : '';
|
|
|
|
if (!state.pluginMacros[path.node.source.value] && state.emotionInstancePaths.indexOf(getInstancePathToCompare(path.node.source.value, dirname)) !== -1) {
|
|
state.pluginMacros[path.node.source.value] = createEmotionMacro(path.node.source.value);
|
|
}
|
|
|
|
var pluginMacros = state.pluginMacros; // most of this is from https://github.com/kentcdodds/babel-plugin-macros/blob/master/src/index.js
|
|
|
|
if (pluginMacros[path.node.source.value] === undefined) {
|
|
return;
|
|
}
|
|
|
|
if (t.isImportNamespaceSpecifier(path.node.specifiers[0])) {
|
|
return;
|
|
}
|
|
|
|
var imports = path.node.specifiers.map(function (s) {
|
|
return {
|
|
localName: s.local.name,
|
|
importedName: s.type === 'ImportDefaultSpecifier' ? 'default' : s.imported.name
|
|
};
|
|
});
|
|
var shouldExit = false;
|
|
var hasReferences = false;
|
|
var referencePathsByImportName = imports.reduce(function (byName, _ref2) {
|
|
var importedName = _ref2.importedName,
|
|
localName = _ref2.localName;
|
|
var binding = path.scope.getBinding(localName);
|
|
|
|
if (!binding) {
|
|
shouldExit = true;
|
|
return byName;
|
|
}
|
|
|
|
byName[importedName] = binding.referencePaths;
|
|
hasReferences = hasReferences || Boolean(byName[importedName].length);
|
|
return byName;
|
|
}, {});
|
|
|
|
if (!hasReferences || shouldExit) {
|
|
return;
|
|
}
|
|
/**
|
|
* Other plugins that run before babel-plugin-macros might use path.replace, where a path is
|
|
* put into its own replacement. Apparently babel does not update the scope after such
|
|
* an operation. As a remedy, the whole scope is traversed again with an empty "Identifier"
|
|
* visitor - this makes the problem go away.
|
|
*
|
|
* See: https://github.com/kentcdodds/import-all.macro/issues/7
|
|
*/
|
|
|
|
|
|
state.file.scope.path.traverse({
|
|
Identifier: function Identifier() {}
|
|
});
|
|
pluginMacros[path.node.source.value]({
|
|
references: referencePathsByImportName,
|
|
state: state,
|
|
babel: babel,
|
|
isBabelMacrosCall: true,
|
|
isEmotionCall: true
|
|
});
|
|
|
|
if (!pluginMacros[path.node.source.value].keepImport) {
|
|
path.remove();
|
|
}
|
|
},
|
|
Program: function Program(path, state) {
|
|
state.emotionInstancePaths = (state.opts.instances || []).map(function (instancePath) {
|
|
return getInstancePathToCompare(instancePath, process.cwd());
|
|
});
|
|
state.pluginMacros = {
|
|
'@emotion/css': cssMacro,
|
|
'@emotion/styled': webStyledMacro,
|
|
'@emotion/core': emotionCoreMacroThatsNotARealMacro,
|
|
'@emotion/primitives': primitivesStyledMacro,
|
|
'@emotion/native': nativeStyledMacro,
|
|
emotion: createEmotionMacro('emotion')
|
|
};
|
|
|
|
if (state.opts.cssPropOptimization === undefined) {
|
|
for (var _iterator = path.node.body, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
|
|
var _ref3;
|
|
|
|
if (_isArray) {
|
|
if (_i >= _iterator.length) break;
|
|
_ref3 = _iterator[_i++];
|
|
} else {
|
|
_i = _iterator.next();
|
|
if (_i.done) break;
|
|
_ref3 = _i.value;
|
|
}
|
|
|
|
var node = _ref3;
|
|
|
|
if (t.isImportDeclaration(node) && node.source.value === '@emotion/core' && node.specifiers.some(function (x) {
|
|
return t.isImportSpecifier(x) && x.imported.name === 'jsx';
|
|
})) {
|
|
state.transformCssProp = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
state.transformCssProp = state.opts.cssPropOptimization;
|
|
}
|
|
|
|
if (state.opts.sourceMap === false) {
|
|
state.emotionSourceMap = false;
|
|
} else {
|
|
state.emotionSourceMap = true;
|
|
}
|
|
},
|
|
JSXAttribute: function JSXAttribute(path, state) {
|
|
if (path.node.name.name !== 'css' || !state.transformCssProp) {
|
|
return;
|
|
}
|
|
|
|
if (t.isJSXExpressionContainer(path.node.value) && (t.isObjectExpression(path.node.value.expression) || t.isArrayExpression(path.node.value.expression))) {
|
|
var expressionPath = path.get('value.expression');
|
|
var sourceMap = state.emotionSourceMap && path.node.loc !== undefined ? getSourceMap(path.node.loc.start, state) : '';
|
|
expressionPath.replaceWith(t.callExpression( // the name of this identifier doesn't really matter at all
|
|
// it'll never appear in generated code
|
|
t.identifier('___shouldNeverAppearCSS'), [path.node.value.expression]));
|
|
transformCssCallExpression({
|
|
babel: babel,
|
|
state: state,
|
|
path: expressionPath,
|
|
sourceMap: sourceMap
|
|
});
|
|
|
|
if (t.isCallExpression(expressionPath)) {
|
|
if (!state.cssIdentifier) {
|
|
state.cssIdentifier = addNamed(path, 'css', '@emotion/core', {
|
|
nameHint: 'css'
|
|
});
|
|
}
|
|
|
|
expressionPath.get('callee').replaceWith(t.cloneDeep(state.cssIdentifier));
|
|
}
|
|
}
|
|
},
|
|
CallExpression: {
|
|
exit: function exit(path, state) {
|
|
try {
|
|
if (path.node.callee && path.node.callee.property && path.node.callee.property.name === 'withComponent') {
|
|
switch (path.node.arguments.length) {
|
|
case 1:
|
|
case 2:
|
|
{
|
|
path.node.arguments[1] = getStyledOptions(t, path, state);
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
throw path.buildCodeFrameError(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
export default index;
|
|
export { macros };
|