UNPKG

6.92 kBJavaScriptView Raw
1"use strict";
2
3exports.__esModule = true;
4exports.default = void 0;
5
6var _path = require("path");
7
8var _groupBy = _interopRequireDefault(require("lodash/groupBy"));
9
10var _uniq = _interopRequireDefault(require("lodash/uniq"));
11
12var _resolve = _interopRequireDefault(require("resolve"));
13
14var _cssUnits = _interopRequireDefault(require("./cssUnits"));
15
16var _getNameFromPath = _interopRequireDefault(require("./getNameFromPath"));
17
18var _murmurHash = _interopRequireDefault(require("./murmurHash"));
19
20function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21
22const rComposes = /\b(?:composes\s*?:\s*([^;>]*?)(?:from\s(.+?))?(?=[;}/\n\r]))/gim;
23const rPlaceholder = /###ASTROTURF_PLACEHOLDER_\d*?###/g; // Match any valid CSS units followed by a separator such as ;, newline etc.
24
25const rUnit = new RegExp(`^(${_cssUnits.default.join('|')})(;|,|\n| |\\))`);
26
27function defaultResolveDependency({
28 request
29}, localStyle) {
30 const source = _resolve.default.sync(request, {
31 basedir: (0, _path.dirname)(localStyle.absoluteFilePath)
32 });
33
34 return {
35 source
36 };
37}
38
39function resolveMemberExpression(path) {
40 let nextPath = path.resolve();
41
42 while (nextPath && nextPath.isMemberExpression()) {
43 nextPath = nextPath.get('object').resolve();
44 }
45
46 return nextPath;
47}
48
49function resolveImport(path) {
50 const resolvedPath = resolveMemberExpression(path);
51 const binding = resolvedPath.scope.getBinding(resolvedPath.node.name);
52 if (!binding || binding.kind !== 'module') return false;
53 const importPath = binding.path;
54 const parent = importPath.parentPath;
55 if (!parent.isImportDeclaration()) return null;
56 const request = parent.node.source.value;
57 let identifier;
58
59 if (importPath.isImportNamespaceSpecifier()) {
60 if (!path.isMemberExpression()) throw new Error('this is weird');
61 identifier = (0, _getNameFromPath.default)(path.get('property'));
62 } else if (importPath.isImportDefaultSpecifier()) {
63 identifier = (0, _getNameFromPath.default)(resolvedPath);
64 } else if (importPath.isImportSpecifier()) {
65 // TODO: this isn't correct doesn't do member expressions
66 identifier = (0, _getNameFromPath.default)(importPath.get('imported'));
67 }
68
69 return {
70 identifier,
71 request,
72 type: importPath.node.type
73 };
74}
75
76function resolveStyleInterpolation(path, nodeMap, localStyle, resolveDependency = defaultResolveDependency) {
77 const resolvedPath = resolveMemberExpression(path);
78 const style = resolvedPath && nodeMap.get(resolvedPath.node);
79
80 if (style) {
81 return {
82 imported: !style.isStyledComponent ? path.get('property').node.name : 'cls1',
83 source: (0, _path.relative)((0, _path.dirname)(localStyle.absoluteFilePath), style.absoluteFilePath)
84 };
85 }
86
87 if (resolveDependency) {
88 const resolvedImport = resolveImport(path);
89
90 if (resolvedImport) {
91 const {
92 identifier
93 } = resolvedImport;
94 const interpolation = resolveDependency(resolvedImport, localStyle, path.node) || null;
95 const isStyledComponent = interpolation.isStyledComponent == null ? identifier.toLowerCase()[0] !== identifier[0] : interpolation.isStyledComponent;
96 return interpolation && {
97 imported: !isStyledComponent ? path.get('property').node.name : 'cls1',
98 ...interpolation
99 };
100 }
101 }
102
103 return null;
104}
105
106const getPlaceholder = idx => `###ASTROTURF_PLACEHOLDER_${idx}###`;
107
108var _default = ({
109 quasiPath,
110 nodeMap,
111 tagName,
112 resolveDependency,
113 useCssProperties,
114 style: localStyle
115}) => {
116 const quasi = quasiPath.node;
117 const styleInterpolations = new Map();
118 const dynamicInterpolations = new Set();
119 const expressions = quasiPath.get('expressions');
120 let text = '';
121 let lastDynamic = null;
122 quasi.quasis.forEach((tmplNode, idx) => {
123 const {
124 cooked
125 } = tmplNode.value;
126 const expr = expressions[idx];
127 let matches; // If the last quasi is a replaced dynamic import then see if there
128 // was a trailing css unit and extract it as part of the interpolation
129 // eslint-disable-next-line no-cond-assign
130
131 if (lastDynamic && text.endsWith(`var(--${lastDynamic.id})`) && (matches = cooked.match(rUnit))) {
132 const [, unit] = matches;
133 lastDynamic.unit = unit;
134 text += cooked.replace(rUnit, '$2');
135 } else {
136 text += cooked;
137 }
138
139 if (!expr) {
140 return;
141 }
142
143 const result = expr.evaluate();
144
145 if (result.confident) {
146 text += result.value;
147 return;
148 } // TODO: dedupe the same expressions in a tag
149
150
151 const interpolation = resolveStyleInterpolation(expr, nodeMap, localStyle, resolveDependency);
152
153 if (interpolation) {
154 interpolation.expr = expr;
155 const ph = getPlaceholder(idx);
156 styleInterpolations.set(ph, interpolation);
157 text += ph;
158 return;
159 }
160
161 if (!useCssProperties) {
162 throw expr.buildCodeFrameError(`Could not resolve interpolation to a value, ${tagName} returned class name, or styled component. ` + 'All interpolated styled components must be in the same file and values must be statically determinable at compile time.');
163 } // custom properties need to start with a letter
164
165
166 const id = `a${(0, _murmurHash.default)(`${localStyle.identifier}-${idx}`)}`;
167 lastDynamic = {
168 id,
169 expr,
170 unit: ''
171 };
172 dynamicInterpolations.add(lastDynamic);
173 text += `var(--${id})`;
174 }); // Replace references in `composes` rules
175
176 text = text.replace(rComposes, (composes, classNames, fromPart) => {
177 const classList = classNames.replace(/(\n|\r|\n\r)/, '').split(/\s+/);
178 const composed = classList.map(className => styleInterpolations.get(className)).filter(Boolean);
179 if (!composed.length) return composes;
180
181 if (fromPart) {
182 // don't want to deal with this case right now
183 throw classList[0].expr.buildCodeFrameError('A styled interpolation found inside a `composes` rule with a "from". ' + 'Interpolated values should be in their own `composes` without specifying the file.');
184 }
185
186 if (composed.length < classList.length) {
187 throw classList[0].expr.buildCodeFrameError('Mixing interpolated and non-interpolated classes in a `composes` rule is not allowed.');
188 }
189
190 return Object.entries((0, _groupBy.default)(composed, i => i.source)).reduce((acc, [source, values]) => {
191 const classes = (0, _uniq.default)(values.map(v => v.imported)).join(' ');
192 return `${acc ? `${acc};\n` : ''}composes: ${classes} from "${source}"`;
193 }, '');
194 });
195 let id = 0;
196 let imports = '';
197 text = text.replace(rPlaceholder, match => {
198 const {
199 imported,
200 source
201 } = styleInterpolations.get(match);
202 const localName = `a${id++}`;
203 imports += `@value ${imported} as ${localName} from "${source}";\n`;
204 return `.${localName}`;
205 });
206 if (imports) imports += '\n\n';
207 return {
208 text,
209 imports,
210 dynamicInterpolations
211 };
212};
213
214exports.default = _default;
\No newline at end of file