fuse-box
Version:
Fuse-Box a bundler that does it right
200 lines (199 loc) • 10.1 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.CSSInJSXTransformer = void 0;
const utils_1 = require("../../../utils/utils");
const labelMapping = {
'[dirname]': '',
'[filename]': '',
'[local]': '',
};
/**
* 1. compare minifiers styled-components and emotion
* 2. don't reinvent the wheel..
* 3. find a place for these helper functions..
*/
// From https://github.com/styled-components/babel-plugin-styled-components/blob/master/src/minify/index.js#L58
// Counts occurences of substr inside str
const countOccurences = (str, substr) => str.split(substr).length - 1;
const compressSymbols = code => code.split(/(\s*[;:{},]\s*)/g).reduce((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;
}, '');
// Super simple minifier.. doesn't cover any edge cases or side effects
const minify = (value) => compressSymbols(value.replace(/[\n]\s*/g, ''));
/**
* @todo
* 1. expand the minify (way to simple :P)
*
* 2. Components as selectors
* 3. Minification
* 4. Sourcemaps
* 5. Minify configurable?
*
* 5. Dead Code Elimination // not needed??
*/
function CSSInJSXTransformer(options) {
const { autoInject = true, autoLabel = true,
// cssPropOptimization = true,
emotionCoreAlias = '@emotion/core', jsxFactory = 'jsx', labelFormat = '[dirname]--[local]', test = /\.(js|jsx|ts|tsx)$/, } = options;
const testPathRegex = utils_1.path2RegexPattern(test);
const emotionLibraries = [[emotionCoreAlias, 'emotion'], ['@emotion/styled']];
// const reactLibrary = 'react';
function renderAutoLabel() {
return {
type: 'Literal',
value: `label:${labelFormat
.replace(/\[local\]|\[filename\]|\[dirname\]/gi, m => labelMapping[m])
.replace(/\-\-$/, '')};`,
};
}
return {
target: { test: testPathRegex },
commonVisitors: props => {
const isProduction = false;
const { transformationContext: { compilerOptions, module }, } = props;
// Keep track of imported emotion functions.
// Allows us to look for custom imports
// import { css as emotionCss } from '@emotion/core'
const importedEmotionFunctions = [];
let needsInjection = true;
// Check if we're executing an emotion call in this file
function isEmotionCall(node, index) {
return (
// css('styles')
importedEmotionFunctions.indexOf(node[index].name) > -1 ||
// styled('obj')('styles')
importedEmotionFunctions.indexOf(node[index].callee && node[index].callee.name) > -1 ||
// styled.div('div')
importedEmotionFunctions.indexOf(node[index].object && node[index].object.name) > -1);
}
const compilerJsxFactory = compilerOptions.jsxFactory;
// Process dirName and fileName only once per file
const filePath = module.publicPath.replace(/\.([^.]+)$/, '');
labelMapping['[dirname]'] = filePath.replace(/(\\|\/)/g, '-');
labelMapping['[filename]'] = filePath.replace(/(.+)(\\|\/)(.+)$/, '$3');
return {
onEach: (schema) => {
const { node, parent, replace } = schema;
switch (node.type) {
case 'CallExpression':
// append the css class label
if (autoLabel && !parent.callee && isEmotionCall(node, 'callee')) {
labelMapping['[local]'] =
(parent.type === 'VariableDeclarator' && parent.id.name) ||
(parent.type === 'AssignmentExpression' && parent.left.property.name) ||
'';
node.arguments.push(renderAutoLabel());
}
break;
// case 'JSXElement':
// if (!cssPropOptimization) {
// break;
// }
// const {
// openingElement: { attributes },
// } = node;
// const attrLength = attributes.length;
// if (attrLength === 0) {
// break;
// }
// for (let i = 0; i < attrLength; i++) {
// let { name, type, value } = attributes[i]; // call 'attr' once
// if (type !== 'JSXAttribute' || name.name !== 'css') {
// continue;
// }
// if (value.expression.type === 'ObjectExpression' || value.expression.type === 'ArrayExpression') {
// // css prop optimization
// // console.log(name, value);
// }
// }
// break;
case 'TaggedTemplateExpression':
if (isEmotionCall(node, 'tag')) {
let { quasi: { expressions, quasis }, tag: callee, } = node;
// Convert template strings to a literal and put the expressions back at it's position
const styleProperties = [];
const quasisLength = quasis.length;
let i = 0;
while (i < quasisLength) {
if (quasis[i].value.cooked) {
// We don't need minification in devMode!
styleProperties.push({
type: 'Literal',
value: isProduction ? quasis[i].value.cooked : minify(quasis[i].value.cooked),
});
}
// Put the expressions back in the place where they belong
if (!quasis[i].tail && expressions.length > 0) {
styleProperties.push(expressions.shift());
}
i++;
}
// Replace this node with new shiny stuff
return replace({
arguments: styleProperties.filter(Boolean),
callee,
type: 'CallExpression',
});
}
break;
}
},
onProgramBody: (schema) => {
const { context, node } = schema;
if (node.type === 'ImportDeclaration') {
if (compilerJsxFactory === 'jsx') {
// @todo:
// fix this, always insert if it's jsx
}
if ([].concat(emotionLibraries[0], emotionLibraries[1]).indexOf(node.source.value) > -1) {
const specifiersLength = node.specifiers.length;
let i = 0;
while (i < specifiersLength) {
if (node.specifiers[i].imported && node.specifiers[i].imported.name === 'jsx') {
needsInjection = false;
// @todo:
// fix this, remove specifier if compilerJsxFactory === 'jsx'
// set the globalContext.jsxFactory for the JSXTransformer
context.jsxFactory = node.specifiers[i].local.name;
// visit.globalContext.jsxFactory = node.specifiers[i].local.name;
}
else {
importedEmotionFunctions.push(node.specifiers[i].local.name);
}
i++;
}
if (autoInject && needsInjection && emotionLibraries[0].indexOf(node.source.value) > -1) {
needsInjection = false;
// set the globalContext.jsxFactory for the JSXTransformer
if (node.source.value === emotionCoreAlias) {
// visit.globalContext.jsxFactory = jsxFactory;
context.jsxFactory = jsxFactory;
}
node.specifiers.push({
imported: {
name: 'jsx',
type: 'Identifier',
},
local: {
name: jsxFactory,
type: 'Identifier',
},
type: 'ImportSpecifier',
});
}
}
}
},
};
},
};
}
exports.CSSInJSXTransformer = CSSInJSXTransformer;
;