1 | 'use strict';
|
2 |
|
3 | const invariant = require('invariant');
|
4 | const path = require('path');
|
5 | const vm = require('vm');
|
6 | const util = require('util');
|
7 |
|
8 | const {
|
9 | _getStyleKeysForProps: getStyleKeysForProps,
|
10 | _defaults: jsxstyleDefaults,
|
11 | } = require('jsxstyle');
|
12 |
|
13 | const canEvaluate = require('./canEvaluate');
|
14 | const canEvaluateObject = require('./canEvaluateObject');
|
15 | const extractStaticTernaries = require('./extractStaticTernaries');
|
16 | const getPropValueFromAttributes = require('./getPropValueFromAttributes');
|
17 | const getStaticBindingsForScope = require('./getStaticBindingsForScope');
|
18 | const getStylesByClassName = require('../getStylesByClassName');
|
19 | const cssRelativeURL = require('../cssRelativeURL');
|
20 |
|
21 | const parse = require('./parse');
|
22 | const traverse = require('babel-traverse').default;
|
23 | const generate = require('babel-generator').default;
|
24 | const t = require('babel-types');
|
25 |
|
26 |
|
27 | const UNTOUCHED_PROPS = {
|
28 | ref: true,
|
29 | key: true,
|
30 | style: true,
|
31 | };
|
32 |
|
33 |
|
34 | const ALL_SPECIAL_PROPS = Object.assign(
|
35 | {
|
36 | component: true,
|
37 | className: true,
|
38 | },
|
39 | UNTOUCHED_PROPS
|
40 | );
|
41 |
|
42 | const ucRegex = /([A-Z])/g;
|
43 |
|
44 | function extractStyles({
|
45 | src,
|
46 | styleGroups,
|
47 | namedStyleGroups,
|
48 | sourceFileName,
|
49 | rootFileName,
|
50 | whitelistedModules,
|
51 | cacheObject,
|
52 | parserPlugins,
|
53 | addCSSRequire,
|
54 | errorCallback,
|
55 | extremelyLiteMode,
|
56 | }) {
|
57 | invariant(typeof src === 'string', '`src` must be a string of javascript');
|
58 |
|
59 | invariant(
|
60 | typeof sourceFileName === 'string' && path.isAbsolute(sourceFileName),
|
61 | '`sourceFileName` must be an absolute path to a .js file'
|
62 | );
|
63 |
|
64 | invariant(
|
65 | typeof cacheObject === 'object' && cacheObject !== null,
|
66 | '`cacheObject` must be an object'
|
67 | );
|
68 |
|
69 | if (typeof styleGroups !== 'undefined') {
|
70 | invariant(
|
71 | Array.isArray(styleGroups),
|
72 | '`styleGroups` must be an array of style prop objects'
|
73 | );
|
74 | }
|
75 |
|
76 | if (typeof namedStyleGroups !== 'undefined') {
|
77 | invariant(
|
78 | typeof namedStyleGroups === 'object' && namedStyleGroups !== null,
|
79 | '`namedStyleGroups` must be an object of style prop objects keyed by className'
|
80 | );
|
81 | }
|
82 |
|
83 | if (typeof whitelistedModules !== 'undefined') {
|
84 | invariant(
|
85 | Array.isArray(whitelistedModules),
|
86 | '`whitelistedModules` must be an array of paths to modules that are OK to require'
|
87 | );
|
88 | }
|
89 |
|
90 | if (typeof errorCallback !== 'undefined') {
|
91 | invariant(
|
92 | typeof errorCallback === 'function',
|
93 | '`errorCallback` is expected to be a function'
|
94 | );
|
95 | } else {
|
96 | errorCallback = console.warn;
|
97 | }
|
98 |
|
99 | if (typeof addCSSRequire === 'undefined') {
|
100 | addCSSRequire = true;
|
101 | }
|
102 |
|
103 | const sourceDir = path.dirname(sourceFileName);
|
104 | const rootDir = rootFileName ? path.dirname(rootFileName) : sourceDir;
|
105 |
|
106 |
|
107 | const cssMap = new Map();
|
108 |
|
109 | const ast = parse(src, parserPlugins);
|
110 |
|
111 | let jsxstyleSrc;
|
112 | let validComponents;
|
113 |
|
114 | if (typeof extremelyLiteMode === 'string') {
|
115 | jsxstyleSrc =
|
116 | extremelyLiteMode === 'react'
|
117 | ? 'jsxstyle/lite'
|
118 | : `jsxstyle/lite/${extremelyLiteMode}`;
|
119 | validComponents = validComponents || {};
|
120 | for (const key in jsxstyleDefaults) {
|
121 | const dashCaseName = key.replace(ucRegex, '-$1').toLowerCase().slice(1);
|
122 | validComponents[dashCaseName] = key;
|
123 | }
|
124 | } else {
|
125 | ast.program.body = ast.program.body.filter(item => {
|
126 | if (t.isVariableDeclaration(item)) {
|
127 | for (let idx = -1, len = item.declarations.length; ++idx < len; ) {
|
128 | const dec = item.declarations[idx];
|
129 |
|
130 | if (
|
131 |
|
132 | !t.isVariableDeclarator(dec) ||
|
133 |
|
134 | !t.isObjectPattern(dec.id) ||
|
135 |
|
136 | !t.isCallExpression(dec.init) ||
|
137 | !t.isIdentifier(dec.init.callee) ||
|
138 | dec.init.callee.name !== 'require' ||
|
139 |
|
140 | dec.init.arguments.length !== 1 ||
|
141 | !t.isStringLiteral(dec.init.arguments[0])
|
142 | ) {
|
143 | continue;
|
144 | }
|
145 |
|
146 |
|
147 | if (dec.init.arguments[0].value.startsWith('jsxstyle/lib/')) {
|
148 | continue;
|
149 | }
|
150 |
|
151 |
|
152 |
|
153 |
|
154 | if (
|
155 | dec.init.arguments[0].value !== 'jsxstyle' &&
|
156 | !dec.init.arguments[0].value.startsWith('jsxstyle/')
|
157 | ) {
|
158 | continue;
|
159 | }
|
160 |
|
161 | if (jsxstyleSrc) {
|
162 | invariant(
|
163 | jsxstyleSrc === dec.init.arguments[0].value,
|
164 | 'Expected duplicate `require` to be from "%s", received "%s"',
|
165 | jsxstyleSrc,
|
166 | dec.init.arguments[0].value
|
167 | );
|
168 | }
|
169 |
|
170 | for (let idx = -1, len = dec.id.properties.length; ++idx < len; ) {
|
171 | const prop = dec.id.properties[idx];
|
172 | if (
|
173 | !t.isObjectProperty(prop) ||
|
174 | !t.isIdentifier(prop.key) ||
|
175 | !t.isIdentifier(prop.value)
|
176 | ) {
|
177 | continue;
|
178 | }
|
179 |
|
180 |
|
181 | if (
|
182 | prop.key.name[0] !== prop.key.name[0].toUpperCase() ||
|
183 | prop.value.name[0] !== prop.value.name[0].toUpperCase()
|
184 | ) {
|
185 | continue;
|
186 | }
|
187 |
|
188 |
|
189 | validComponents = validComponents || {};
|
190 | validComponents[prop.value.name] = prop.key.name;
|
191 | }
|
192 |
|
193 | jsxstyleSrc = dec.init.arguments[0].value;
|
194 |
|
195 | if (
|
196 | jsxstyleSrc === 'jsxstyle/lite' ||
|
197 | jsxstyleSrc.startsWith('jsxstyle/lite/')
|
198 | ) {
|
199 | return false;
|
200 | }
|
201 | }
|
202 | } else if (t.isImportDeclaration(item)) {
|
203 |
|
204 |
|
205 |
|
206 | if (item.source.value.startsWith('jsxstyle/lib/')) {
|
207 | return true;
|
208 | }
|
209 |
|
210 |
|
211 | if (
|
212 | !t.isStringLiteral(item.source) ||
|
213 | (item.source.value !== 'jsxstyle' &&
|
214 | !item.source.value.startsWith('jsxstyle/'))
|
215 | ) {
|
216 | return true;
|
217 | }
|
218 |
|
219 | if (jsxstyleSrc) {
|
220 | invariant(
|
221 | jsxstyleSrc === item.source.value,
|
222 | 'Expected duplicate `import` to be from "%s", received "%s"',
|
223 | jsxstyleSrc,
|
224 | item.source.value
|
225 | );
|
226 | }
|
227 |
|
228 | jsxstyleSrc = item.source.value;
|
229 |
|
230 | for (let idx = -1, len = item.specifiers.length; ++idx < len; ) {
|
231 | const specifier = item.specifiers[idx];
|
232 | if (
|
233 | !t.isImportSpecifier(specifier) ||
|
234 | !t.isIdentifier(specifier.imported) ||
|
235 | !t.isIdentifier(specifier.local)
|
236 | ) {
|
237 | continue;
|
238 | }
|
239 |
|
240 | if (
|
241 | specifier.imported.name[0] !==
|
242 | specifier.imported.name[0].toUpperCase() ||
|
243 | specifier.local.name[0] !== specifier.local.name[0].toUpperCase()
|
244 | ) {
|
245 | continue;
|
246 | }
|
247 |
|
248 | validComponents = validComponents || {};
|
249 | validComponents[specifier.local.name] = specifier.imported.name;
|
250 | }
|
251 |
|
252 | if (
|
253 | jsxstyleSrc === 'jsxstyle/lite' ||
|
254 | jsxstyleSrc.startsWith('jsxstyle/lite/')
|
255 | ) {
|
256 | return false;
|
257 | }
|
258 | }
|
259 | return true;
|
260 | });
|
261 |
|
262 |
|
263 | if (!jsxstyleSrc || !validComponents) {
|
264 | return {
|
265 | js: src,
|
266 | css: '',
|
267 | cssFileName: null,
|
268 | ast,
|
269 | map: null,
|
270 | };
|
271 | }
|
272 | }
|
273 |
|
274 |
|
275 | const classPropName =
|
276 | jsxstyleSrc === 'jsxstyle/preact' || jsxstyleSrc === 'jsxstyle/lite/preact'
|
277 | ? 'class'
|
278 | : 'className';
|
279 |
|
280 | const removeAllTrace =
|
281 | jsxstyleSrc === 'jsxstyle/lite' || jsxstyleSrc.startsWith('jsxstyle/lite/');
|
282 |
|
283 | const boxComponentName = 'Jsxstyle$Box';
|
284 | if (!removeAllTrace) {
|
285 |
|
286 |
|
287 | ast.program.body.unshift(
|
288 | t.variableDeclaration('var', [
|
289 | t.variableDeclarator(
|
290 | t.identifier(boxComponentName),
|
291 | t.memberExpression(
|
292 | t.callExpression(t.identifier('require'), [
|
293 | t.stringLiteral(jsxstyleSrc),
|
294 | ]),
|
295 | t.identifier('Box')
|
296 | )
|
297 | ),
|
298 | ])
|
299 | );
|
300 | }
|
301 |
|
302 | const traverseOptions = {
|
303 | JSXElement(path) {
|
304 | const node = path.node.openingElement;
|
305 |
|
306 | if (
|
307 |
|
308 | !t.isJSXIdentifier(node.name) ||
|
309 |
|
310 | !validComponents.hasOwnProperty(node.name.name)
|
311 | ) {
|
312 | return;
|
313 | }
|
314 |
|
315 |
|
316 | const originalNodeName = node.name.name;
|
317 | const src = validComponents[originalNodeName];
|
318 |
|
319 | if (!removeAllTrace) {
|
320 | node.name.name = boxComponentName;
|
321 | }
|
322 |
|
323 | const defaultProps = jsxstyleDefaults[src];
|
324 | invariant(defaultProps, `jsxstyle component <${src} /> does not exist!`);
|
325 |
|
326 | const propKeys = Object.keys(defaultProps);
|
327 |
|
328 | for (let idx = propKeys.length; --idx >= 0; ) {
|
329 | const prop = propKeys[idx];
|
330 | const value = defaultProps[prop];
|
331 | if (value == null || value === '') {
|
332 | continue;
|
333 | }
|
334 |
|
335 | let valueEx;
|
336 | if (typeof value === 'number') {
|
337 | valueEx = t.jSXExpressionContainer(t.numericLiteral(value));
|
338 | } else if (typeof value === 'string') {
|
339 | valueEx = t.stringLiteral(value);
|
340 | } else {
|
341 | continue;
|
342 | }
|
343 |
|
344 | node.attributes.unshift(t.jSXAttribute(t.jSXIdentifier(prop), valueEx));
|
345 | }
|
346 |
|
347 |
|
348 | const staticNamespace = getStaticBindingsForScope(
|
349 | path.scope,
|
350 | whitelistedModules,
|
351 | sourceFileName
|
352 | );
|
353 | const evalContext = vm.createContext(staticNamespace);
|
354 |
|
355 | let lastSpreadIndex = null;
|
356 | const flattenedAttributes = [];
|
357 | node.attributes.forEach(attr => {
|
358 | if (t.isJSXSpreadAttribute(attr)) {
|
359 | if (canEvaluate(staticNamespace, attr.argument)) {
|
360 | const spreadValue = vm.runInContext(
|
361 | generate(attr.argument).code,
|
362 | evalContext
|
363 | );
|
364 |
|
365 | if (typeof spreadValue !== 'object' || spreadValue === null) {
|
366 | lastSpreadIndex = flattenedAttributes.push(attr) - 1;
|
367 | } else {
|
368 | for (const k in spreadValue) {
|
369 | const value = spreadValue[k];
|
370 |
|
371 | if (typeof value === 'number') {
|
372 | flattenedAttributes.push(
|
373 | t.jSXAttribute(
|
374 | t.jSXIdentifier(k),
|
375 | t.jSXExpressionContainer(t.numericLiteral(value))
|
376 | )
|
377 | );
|
378 | } else if (value === null) {
|
379 |
|
380 | flattenedAttributes.push(
|
381 | t.jSXAttribute(
|
382 | t.jSXIdentifier(k),
|
383 | t.jSXExpressionContainer(t.nullLiteral())
|
384 | )
|
385 | );
|
386 | } else {
|
387 |
|
388 |
|
389 | flattenedAttributes.push(
|
390 | t.jSXAttribute(
|
391 | t.jSXIdentifier(k),
|
392 | t.jSXExpressionContainer(t.stringLiteral('' + value))
|
393 | )
|
394 | );
|
395 | }
|
396 | }
|
397 | }
|
398 | } else {
|
399 | lastSpreadIndex = flattenedAttributes.push(attr) - 1;
|
400 | }
|
401 | } else {
|
402 | flattenedAttributes.push(attr);
|
403 | }
|
404 | });
|
405 |
|
406 | node.attributes = flattenedAttributes;
|
407 |
|
408 | let propsAttributes;
|
409 | const staticAttributes = {};
|
410 | let inlinePropCount = 0;
|
411 |
|
412 | const staticTernaries = [];
|
413 |
|
414 | node.attributes = node.attributes.filter((attribute, idx) => {
|
415 | if (
|
416 |
|
417 | !attribute.name ||
|
418 | !attribute.name.name ||
|
419 |
|
420 | idx < lastSpreadIndex
|
421 | ) {
|
422 | inlinePropCount++;
|
423 | return true;
|
424 | }
|
425 |
|
426 | const name = attribute.name.name;
|
427 | const value = t.isJSXExpressionContainer(attribute.value)
|
428 | ? attribute.value.expression
|
429 | : attribute.value;
|
430 |
|
431 |
|
432 | if (lastSpreadIndex !== null && idx <= lastSpreadIndex) {
|
433 | inlinePropCount++;
|
434 | return true;
|
435 | }
|
436 |
|
437 |
|
438 | if (UNTOUCHED_PROPS.hasOwnProperty(name)) {
|
439 | return true;
|
440 | }
|
441 |
|
442 |
|
443 | if (name === classPropName) {
|
444 | return true;
|
445 | }
|
446 |
|
447 |
|
448 | if (name === 'component') {
|
449 | if (t.isLiteral(value) && typeof value.value === 'string') {
|
450 | const char1 = value.value[0];
|
451 |
|
452 | if (char1 === char1.toUpperCase()) {
|
453 |
|
454 |
|
455 |
|
456 | inlinePropCount++;
|
457 | }
|
458 | } else if (t.isIdentifier(value)) {
|
459 | const char1 = value.name[0];
|
460 |
|
461 | if (char1 === char1.toLowerCase()) {
|
462 |
|
463 | inlinePropCount++;
|
464 | }
|
465 | } else if (t.isMemberExpression(value)) {
|
466 |
|
467 | } else {
|
468 |
|
469 | errorCallback(
|
470 | '`component` prop value was not handled by extractStyles: %s',
|
471 | generate(value).code
|
472 | );
|
473 | inlinePropCount++;
|
474 | }
|
475 | return true;
|
476 | }
|
477 |
|
478 |
|
479 | if (UNTOUCHED_PROPS.hasOwnProperty(name)) {
|
480 | return true;
|
481 | }
|
482 |
|
483 | if (name === 'props') {
|
484 | if (!value) {
|
485 | errorCallback('`props` prop does not have a value');
|
486 | inlinePropCount++;
|
487 | return true;
|
488 | }
|
489 |
|
490 | if (t.isObjectExpression(value)) {
|
491 | let errorCount = 0;
|
492 | const attributes = [];
|
493 |
|
494 | for (const k in value.properties) {
|
495 | const propObj = value.properties[k];
|
496 |
|
497 | if (t.isObjectProperty(propObj)) {
|
498 | let key;
|
499 |
|
500 | if (t.isIdentifier(propObj.key)) {
|
501 | key = propObj.key.name;
|
502 | } else if (t.isStringLiteral(propObj.key)) {
|
503 |
|
504 | if (/^\w[\w-]+$/.test(propObj.key.value)) {
|
505 | key = propObj.key.value;
|
506 | } else {
|
507 | errorCallback(
|
508 | '`props` prop contains an invalid key: `%s`',
|
509 | propObj.key.value
|
510 | );
|
511 | errorCount++;
|
512 | continue;
|
513 | }
|
514 | } else {
|
515 | errorCallback(
|
516 | 'unhandled object property key type: `%s`',
|
517 | propObj.type
|
518 | );
|
519 | errorCount++;
|
520 | }
|
521 |
|
522 | if (ALL_SPECIAL_PROPS.hasOwnProperty(key)) {
|
523 | errorCallback(
|
524 | '`props` prop cannot contain `%s` as it is used by jsxstyle and will be overwritten.',
|
525 | key
|
526 | );
|
527 | errorCount++;
|
528 | continue;
|
529 | }
|
530 |
|
531 | if (t.isStringLiteral(propObj.value)) {
|
532 |
|
533 | attributes.push(
|
534 | t.jSXAttribute(
|
535 | t.jSXIdentifier(key),
|
536 | t.stringLiteral(propObj.value.value)
|
537 | )
|
538 | );
|
539 | } else {
|
540 |
|
541 | attributes.push(
|
542 | t.jSXAttribute(
|
543 | t.jSXIdentifier(key),
|
544 | t.jSXExpressionContainer(propObj.value)
|
545 | )
|
546 | );
|
547 | }
|
548 | } else if (t.isSpreadProperty(propObj)) {
|
549 | attributes.push(t.jSXSpreadAttribute(propObj.argument));
|
550 | } else {
|
551 | errorCallback(
|
552 | 'unhandled object property value type: `%s`',
|
553 | propObj.type
|
554 | );
|
555 | errorCount++;
|
556 | }
|
557 | }
|
558 |
|
559 | if (errorCount > 0) {
|
560 | inlinePropCount++;
|
561 | } else {
|
562 | propsAttributes = attributes;
|
563 | }
|
564 |
|
565 | return true;
|
566 | }
|
567 |
|
568 | if (
|
569 |
|
570 |
|
571 | t.isCallExpression(value) ||
|
572 |
|
573 | t.isMemberExpression(value) ||
|
574 |
|
575 | t.isIdentifier(value)
|
576 | ) {
|
577 | propsAttributes = [t.jSXSpreadAttribute(value)];
|
578 | return true;
|
579 | }
|
580 |
|
581 |
|
582 | errorCallback('props prop is an unhandled type: `%s`', value.type);
|
583 | inlinePropCount++;
|
584 | return true;
|
585 | }
|
586 |
|
587 | if (name === 'mediaQueries') {
|
588 | if (canEvaluateObject(staticNamespace, value)) {
|
589 | staticAttributes[name] = vm.runInContext(
|
590 |
|
591 | '(' + generate(value).code + ')',
|
592 | evalContext
|
593 | );
|
594 | } else if (canEvaluate(staticNamespace, value)) {
|
595 | staticAttributes[name] = vm.runInContext(
|
596 | generate(value).code,
|
597 | evalContext
|
598 | );
|
599 | } else {
|
600 | inlinePropCount++;
|
601 | return true;
|
602 | }
|
603 | return false;
|
604 | }
|
605 |
|
606 |
|
607 | if (canEvaluate(staticNamespace, value)) {
|
608 | staticAttributes[name] = vm.runInContext(
|
609 | generate(value).code,
|
610 | evalContext
|
611 | );
|
612 | return false;
|
613 | }
|
614 |
|
615 | if (t.isConditionalExpression(value)) {
|
616 |
|
617 | if (
|
618 | canEvaluate(staticNamespace, value.consequent) &&
|
619 | canEvaluate(staticNamespace, value.alternate)
|
620 | ) {
|
621 | staticTernaries.push({ name, ternary: value });
|
622 |
|
623 | staticAttributes[name] = null;
|
624 | return false;
|
625 | }
|
626 | } else if (t.isLogicalExpression(value)) {
|
627 |
|
628 | if (
|
629 | value.operator === '&&' &&
|
630 | canEvaluate(staticNamespace, value.right)
|
631 | ) {
|
632 | staticTernaries.push({
|
633 | name,
|
634 | ternary: {
|
635 | test: value.left,
|
636 | consequent: value.right,
|
637 | alternate: t.nullLiteral(),
|
638 | },
|
639 | });
|
640 | staticAttributes[name] = null;
|
641 | return false;
|
642 | }
|
643 | }
|
644 |
|
645 | if (removeAllTrace) {
|
646 | errorCallback(
|
647 | 'jsxstyle-loader encountered a dynamic prop (`%s`) on a jsxstyle ' +
|
648 | 'component that was imported from `%s`. If you would like to pass ' +
|
649 | 'dynamic styles to this component, specify them in the `style` prop.',
|
650 | generate(attribute).code,
|
651 | jsxstyleSrc
|
652 | );
|
653 | return false;
|
654 | }
|
655 |
|
656 |
|
657 | inlinePropCount++;
|
658 | return true;
|
659 | });
|
660 |
|
661 | let classNamePropValue;
|
662 | const classNamePropIndex = node.attributes.findIndex(
|
663 | attr => attr.name && attr.name.name === classPropName
|
664 | );
|
665 | if (classNamePropIndex > -1 && Object.keys(staticAttributes).length > 0) {
|
666 | classNamePropValue = getPropValueFromAttributes(
|
667 | classPropName,
|
668 | node.attributes
|
669 | );
|
670 | node.attributes.splice(classNamePropIndex, 1);
|
671 | }
|
672 |
|
673 |
|
674 |
|
675 | if (inlinePropCount === 0) {
|
676 | const propsPropIndex = node.attributes.findIndex(
|
677 | attr => attr.name && attr.name.name === 'props'
|
678 | );
|
679 |
|
680 | if (propsPropIndex > -1) {
|
681 | if (propsAttributes) {
|
682 | propsAttributes.forEach(a => node.attributes.push(a));
|
683 | }
|
684 |
|
685 | node.attributes.splice(propsPropIndex, 1);
|
686 | }
|
687 |
|
688 | const componentPropIndex = node.attributes.findIndex(
|
689 | attr => attr.name && attr.name.name === 'component'
|
690 | );
|
691 | if (componentPropIndex > -1) {
|
692 | const attribute = node.attributes[componentPropIndex];
|
693 | const componentPropValue = t.isJSXExpressionContainer(attribute.value)
|
694 | ? attribute.value.expression
|
695 | : attribute.value;
|
696 |
|
697 | if (
|
698 | t.isLiteral(componentPropValue) &&
|
699 | typeof componentPropValue.value === 'string'
|
700 | ) {
|
701 | node.name.name = componentPropValue.value;
|
702 | } else if (t.isIdentifier(componentPropValue)) {
|
703 | node.name.name = componentPropValue.name;
|
704 | } else if (t.isMemberExpression(componentPropValue)) {
|
705 | node.name.name = generate(componentPropValue).code;
|
706 | }
|
707 |
|
708 |
|
709 | node.attributes.splice(componentPropIndex, 1);
|
710 | } else {
|
711 | node.name.name = 'div';
|
712 | }
|
713 | } else {
|
714 | if (lastSpreadIndex !== null) {
|
715 |
|
716 |
|
717 | Object.keys(staticAttributes).forEach(attr => {
|
718 | node.attributes.push(
|
719 | t.jSXAttribute(
|
720 | t.jSXIdentifier(attr),
|
721 | t.jSXExpressionContainer(t.nullLiteral())
|
722 | )
|
723 | );
|
724 | });
|
725 | }
|
726 | }
|
727 |
|
728 | if (path.node.closingElement) {
|
729 | path.node.closingElement.name.name = node.name.name;
|
730 | }
|
731 |
|
732 | if (!addCSSRequire) {
|
733 | for (const key in staticAttributes) {
|
734 | const value = staticAttributes[key];
|
735 | if (typeof value !== 'string' || value.indexOf('url(') === -1)
|
736 | continue;
|
737 | staticAttributes[key] = cssRelativeURL(value, sourceDir, rootDir);
|
738 | }
|
739 | }
|
740 |
|
741 | const stylesByClassName = getStylesByClassName(
|
742 | styleGroups,
|
743 | namedStyleGroups,
|
744 | staticAttributes,
|
745 | cacheObject
|
746 | );
|
747 |
|
748 | const extractedStyleClassNames = Object.keys(stylesByClassName).join(' ');
|
749 |
|
750 | const classNameObjects = [];
|
751 |
|
752 | if (classNamePropValue) {
|
753 | if (canEvaluate(null, classNamePropValue)) {
|
754 |
|
755 | classNameObjects.push(
|
756 | t.stringLiteral(
|
757 | vm.runInNewContext(generate(classNamePropValue).code)
|
758 | )
|
759 | );
|
760 | } else {
|
761 | classNameObjects.push(classNamePropValue);
|
762 | }
|
763 | }
|
764 |
|
765 | if (staticTernaries.length > 0) {
|
766 | const ternaryObj = extractStaticTernaries(
|
767 | staticTernaries,
|
768 | evalContext,
|
769 | cacheObject
|
770 | );
|
771 |
|
772 |
|
773 | if (ternaryObj !== null) {
|
774 |
|
775 | Object.assign(stylesByClassName, ternaryObj.stylesByClassName);
|
776 | classNameObjects.push(ternaryObj.ternaryExpression);
|
777 | }
|
778 | }
|
779 |
|
780 | if (extractedStyleClassNames) {
|
781 | classNameObjects.push(t.stringLiteral(extractedStyleClassNames));
|
782 | }
|
783 |
|
784 | const classNamePropValueForReals = classNameObjects.reduce((acc, val) => {
|
785 | if (!acc) {
|
786 | if (
|
787 |
|
788 | t.isConditionalExpression(val) ||
|
789 |
|
790 | (t.isLiteral(val) && val.value !== null)
|
791 | ) {
|
792 | return val;
|
793 | }
|
794 | return t.logicalExpression('||', val, t.stringLiteral(''));
|
795 | }
|
796 |
|
797 | const accIsString = t.isLiteral(acc) && typeof acc.value === 'string';
|
798 |
|
799 | let inner;
|
800 | if (t.isLiteral(val)) {
|
801 | if (typeof val.value === 'string') {
|
802 | if (accIsString) {
|
803 |
|
804 | return t.stringLiteral(`${acc.value} ${val.value}`);
|
805 | }
|
806 | inner = t.stringLiteral(` ${val.value}`);
|
807 | } else {
|
808 | inner = t.binaryExpression('+', t.stringLiteral(' '), val);
|
809 | }
|
810 | } else if (
|
811 | t.isConditionalExpression(val) ||
|
812 | t.isBinaryExpression(val)
|
813 | ) {
|
814 | if (accIsString) {
|
815 | return t.binaryExpression(
|
816 | '+',
|
817 | t.stringLiteral(`${acc.value} `),
|
818 | val
|
819 | );
|
820 | }
|
821 | inner = t.binaryExpression('+', t.stringLiteral(' '), val);
|
822 | } else if (t.isIdentifier(val) || t.isMemberExpression(val)) {
|
823 |
|
824 | inner = t.conditionalExpression(
|
825 | val,
|
826 | t.binaryExpression('+', t.stringLiteral(' '), val),
|
827 | t.stringLiteral('')
|
828 | );
|
829 | } else {
|
830 | if (accIsString) {
|
831 | return t.binaryExpression(
|
832 | '+',
|
833 | t.stringLiteral(`${acc.value} `),
|
834 | t.logicalExpression('||', val, t.stringLiteral(''))
|
835 | );
|
836 | }
|
837 |
|
838 | inner = t.binaryExpression(
|
839 | '+',
|
840 | t.stringLiteral(' '),
|
841 | t.logicalExpression('||', val, t.stringLiteral(''))
|
842 | );
|
843 | }
|
844 | return t.binaryExpression('+', acc, inner);
|
845 | }, null);
|
846 |
|
847 | if (classNamePropValueForReals) {
|
848 | if (
|
849 | t.isLiteral(classNamePropValueForReals) &&
|
850 | typeof classNamePropValueForReals.value === 'string'
|
851 | ) {
|
852 | node.attributes.push(
|
853 | t.jSXAttribute(
|
854 | t.jSXIdentifier(classPropName),
|
855 | t.stringLiteral(classNamePropValueForReals.value)
|
856 | )
|
857 | );
|
858 | } else {
|
859 | node.attributes.push(
|
860 | t.jSXAttribute(
|
861 | t.jSXIdentifier(classPropName),
|
862 | t.jSXExpressionContainer(classNamePropValueForReals)
|
863 | )
|
864 | );
|
865 | }
|
866 | }
|
867 |
|
868 | const lineNumbers =
|
869 | node.loc.start.line +
|
870 | (node.loc.start.line !== node.loc.end.line
|
871 | ? `-${node.loc.end.line}`
|
872 | : '');
|
873 |
|
874 | const comment = util.format(
|
875 | '/* %s:%s (%s) */',
|
876 | sourceFileName.replace(process.cwd(), '.'),
|
877 | lineNumbers,
|
878 | originalNodeName
|
879 | );
|
880 |
|
881 | for (const className in stylesByClassName) {
|
882 | if (cssMap.has(className)) {
|
883 | if (comment) {
|
884 | const val = cssMap.get(className);
|
885 | val.commentTexts.push(comment);
|
886 | cssMap.set(className, val);
|
887 | }
|
888 | } else {
|
889 | let css = '';
|
890 | const styleProps = stylesByClassName[className];
|
891 |
|
892 |
|
893 | const styleObjects = getStyleKeysForProps(styleProps, true);
|
894 | delete styleObjects.classNameKey;
|
895 | const styleObjectKeys = Object.keys(styleObjects).sort();
|
896 |
|
897 | for (let idx = -1, len = styleObjectKeys.length; ++idx < len; ) {
|
898 | const k = styleObjectKeys[idx];
|
899 | const item = styleObjects[k];
|
900 | let itemCSS =
|
901 | `.${className}` +
|
902 | (item.pseudoclass ? ':' + item.pseudoclass : '') +
|
903 | (item.pseudoelement ? '::' + item.pseudoelement : '') +
|
904 | ` {${item.styles}}`;
|
905 |
|
906 | if (item.mediaQuery) {
|
907 | itemCSS = `@media ${item.mediaQuery} { ${itemCSS} }`;
|
908 | }
|
909 | css += itemCSS + '\n';
|
910 | }
|
911 |
|
912 | cssMap.set(className, { css, commentTexts: [comment] });
|
913 | }
|
914 | }
|
915 | },
|
916 | };
|
917 |
|
918 | traverse(ast, traverseOptions);
|
919 |
|
920 | const css = Array.from(cssMap.values())
|
921 | .map(n => n.commentTexts.map(t => `${t}\n`).join('') + n.css)
|
922 | .join('');
|
923 |
|
924 | const baseName = path.basename(sourceFileName, '.js');
|
925 | const cssRelativeFileName = `./${baseName}.jsxstyle.css`;
|
926 | const cssFileName = path.join(sourceDir, cssRelativeFileName);
|
927 |
|
928 | if (addCSSRequire && css !== '') {
|
929 |
|
930 |
|
931 | ast.program.body.unshift(
|
932 | t.expressionStatement(
|
933 | t.callExpression(t.identifier('require'), [
|
934 | t.stringLiteral(cssRelativeFileName),
|
935 | ])
|
936 | )
|
937 | );
|
938 | }
|
939 |
|
940 | const result = generate(
|
941 | ast,
|
942 | {
|
943 | fileName: sourceFileName,
|
944 | retainLines: false,
|
945 | compact: 'auto',
|
946 | concise: false,
|
947 | sourceMaps: true,
|
948 | sourceFileName,
|
949 | quotes: 'single',
|
950 | },
|
951 | src
|
952 | );
|
953 |
|
954 | return {
|
955 | js: result.code,
|
956 | css,
|
957 | cssFileName,
|
958 | ast: result.ast,
|
959 | map: result.map,
|
960 | };
|
961 | }
|
962 |
|
963 | module.exports = extractStyles;
|