| "use strict"; |
| const { |
| parse, |
| types, |
| traverse, |
| loadOptions, |
| } = require("@babel/core"); |
| const getTemplate = require("./get-template"); |
| const loadSyntax = require("postcss-syntax/load-syntax"); |
| |
| const isStyleSheetCreate = expectAdjacentSibling(["create"]); |
| const supports = { |
| // import styled from '@emotion/styled' |
| // import { styled } from 'glamor/styled' |
| // import { styled } from "styletron-react"; |
| // import { styled } from 'linaria/react'; |
| styled: true, |
| |
| // import { style } from "typestyle"; |
| style: true, |
| |
| // import { StyleSheet, css } from 'aphrodite'; |
| // import styled, { css } from 'astroturf'; |
| // import { css } from 'lit-css'; |
| // import { css } from 'glamor' |
| // require('css-light').css({color: 'red'}); |
| // import { css } from 'linaria'; |
| css: true, |
| |
| // import { StyleSheet, css } from 'aphrodite'; |
| // import { AppRegistry, StyleSheet, Text, View } from 'react-native'; |
| StyleSheet: isStyleSheetCreate, |
| |
| // import styled, { css } from 'astroturf'; |
| astroturf: true, |
| |
| // require('csjs')`css`; |
| csjs: true, |
| |
| // require('cssobj')({color: 'red'}) |
| cssobj: true, |
| |
| // require('electron-css')({color: 'red'}) |
| "electron-css": true, |
| |
| // import styled from "react-emotion"; |
| "react-emotion": true, |
| |
| // import styled from 'preact-emotion' |
| "preact-emotion": true, |
| |
| // https://github.com/streamich/freestyler |
| freestyler: true, |
| |
| // https://github.com/paypal/glamorous |
| glamorous: true, |
| |
| // https://github.com/irom-io/i-css |
| // "i-css": (i, nameSpace) => nameSpace[i + 1] === "addStyles" && nameSpace[i + 2] === "wrapper", |
| |
| // https://github.com/j2css/j2c |
| j2c: expectAdjacentSibling(["inline", "sheet"]), |
| |
| // var styles = StyleSheet.create({color: 'red'}) |
| "react-inline": isStyleSheetCreate, |
| "react-style": isStyleSheetCreate, |
| |
| // import reactCSS from 'reactcss' |
| reactcss: true, |
| |
| // const StyledButton = injectSheet(styles)(Button) |
| "react-jss": true, |
| |
| // import styled from 'styled-components'; |
| "styled-components": true, |
| |
| // import {withStyle} from "styletron-react"; |
| "styletron-react": expectAdjacentSibling(["withStyle"]), |
| |
| "styling": true, |
| |
| // const rule = superstyle({ color: 'blue' }) |
| "superstyle": true, |
| }; |
| |
| const plugins = [ |
| "jsx", |
| "typescript", |
| "objectRestSpread", |
| ["decorators", { "decoratorsBeforeExport": false }], |
| "classProperties", |
| "exportExtensions", |
| "asyncGenerators", |
| "functionBind", |
| "functionSent", |
| "dynamicImport", |
| "optionalCatchBinding", |
| ]; |
| |
| function expectAdjacentSibling (names) { |
| return (i, nameSpace) => ( |
| names.some(name => nameSpace[i + 1] === name) |
| ); |
| } |
| |
| function loadBabelOpts (opts) { |
| const filename = opts.from && opts.from.replace(/\?.*$/, ""); |
| opts = { |
| sourceFilename: filename, |
| sourceType: filename && /\.m[tj]sx?$/.test(filename) ? "module" : "unambiguous", |
| plugins, |
| allowImportExportEverywhere: true, |
| allowAwaitOutsideFunction: true, |
| allowReturnOutsideFunction: true, |
| allowSuperOutsideMethod: true, |
| }; |
| let fileOpts; |
| try { |
| fileOpts = filename && loadOptions({ |
| filename, |
| }); |
| } catch (ex) { |
| // |
| } |
| for (const key in fileOpts) { |
| if (Array.isArray(fileOpts[key]) && !fileOpts[key].length) { |
| continue; |
| } |
| opts[key] = fileOpts[key]; |
| } |
| return opts; |
| } |
| |
| function literalParser (source, opts, styles) { |
| let ast; |
| try { |
| ast = parse(source, { |
| parserOpts: loadBabelOpts(opts), |
| }); |
| } catch (ex) { |
| // console.error(ex); |
| return styles || []; |
| } |
| |
| const specifiers = new Map(); |
| const variableDeclarator = new Map(); |
| let objLiteral = new Set(); |
| let tplLiteral = new Set(); |
| const tplCallee = new Set(); |
| const jobs = []; |
| |
| function addObjectJob (path) { |
| jobs.push(() => { |
| addObjectValue(path); |
| }); |
| } |
| |
| function addObjectValue (path) { |
| if (path.isIdentifier()) { |
| const identifier = path.scope.getBindingIdentifier(path.node.name); |
| if (identifier) { |
| path = variableDeclarator.get(identifier); |
| if (path) { |
| variableDeclarator.delete(identifier); |
| path.forEach(addObjectExpression); |
| } |
| } |
| } else { |
| addObjectExpression(path); |
| } |
| } |
| |
| function addObjectExpression (path) { |
| if (path.isObjectExpression()) { |
| path.get("properties").forEach(prop => { |
| if (prop.isSpreadElement()) { |
| addObjectValue(prop.get("argument")); |
| } |
| }); |
| objLiteral.add(path.node); |
| return path; |
| } |
| } |
| |
| function setSpecifier (id, nameSpace) { |
| nameSpace.unshift.apply( |
| nameSpace, |
| nameSpace.shift().replace(/^\W+/, "").split(/[/\\]+/g) |
| ); |
| |
| if (types.isIdentifier(id)) { |
| specifiers.set(id.name, nameSpace); |
| specifiers.set(id, nameSpace); |
| } else if (types.isObjectPattern(id)) { |
| id.properties.forEach(property => { |
| if (types.isObjectProperty(property)) { |
| const key = property.key; |
| nameSpace = nameSpace.concat(key.name || key.value); |
| id = property.value; |
| } else { |
| id = property.argument; |
| } |
| setSpecifier(id, nameSpace); |
| }); |
| } else if (types.isArrayPattern(id)) { |
| id.elements.forEach((element, i) => { |
| setSpecifier(element, nameSpace.concat(String(i))); |
| }); |
| } |
| } |
| |
| function getNameSpace (path, nameSpace) { |
| let node = path.node; |
| if (path.isIdentifier() || path.isJSXIdentifier()) { |
| node = path.scope.getBindingIdentifier(node.name) || node; |
| const specifier = specifiers.get(node) || specifiers.get(node.name); |
| if (specifier) { |
| nameSpace.unshift.apply(nameSpace, specifier); |
| } else { |
| nameSpace.unshift(node.name); |
| } |
| } else { |
| [ |
| "name", |
| "property", |
| "object", |
| "callee", |
| ].forEach(prop => { |
| node[prop] && getNameSpace(path.get(prop), nameSpace); |
| }); |
| } |
| |
| return nameSpace; |
| } |
| |
| function isStylePath (path) { |
| return getNameSpace(path, []).some(function (name) { |
| let result = name && ((supports.hasOwnProperty(name) && supports[name]) || (opts.syntax.config.hasOwnProperty(name) && opts.syntax.config[name])); |
| switch (typeof result) { |
| case "function": { |
| result = result.apply(this, Array.prototype.slice.call(arguments, 1)); |
| } |
| // eslint-disable-next-line no-fallthrough |
| case "boolean": { |
| return result; |
| } |
| } |
| }); |
| } |
| |
| const visitor = { |
| ImportDeclaration: (path) => { |
| const moduleId = path.node.source.value; |
| path.node.specifiers.forEach(specifier => { |
| const nameSpace = [moduleId]; |
| if (specifier.imported) { |
| nameSpace.push(specifier.imported.name); |
| } |
| setSpecifier(specifier.local, nameSpace); |
| }); |
| }, |
| JSXAttribute: (path) => { |
| if (/^(?:css|style)$/.test(path.node.name.name)) { |
| addObjectJob(path.get("value.expression")); |
| } |
| }, |
| VariableDeclarator: (path) => { |
| variableDeclarator.set(path.node.id, path.node.init ? [path.get("init")] : []); |
| }, |
| AssignmentExpression: (path) => { |
| if (types.isIdentifier(path.node.left) && types.isObjectExpression(path.node.right)) { |
| const identifier = path.scope.getBindingIdentifier(path.node.left.name); |
| const variable = variableDeclarator.get(identifier); |
| const valuePath = path.get("right"); |
| if (variable) { |
| variable.push(valuePath); |
| } else { |
| variableDeclarator.set(identifier, [valuePath]); |
| } |
| } |
| }, |
| CallExpression: (path) => { |
| const callee = path.node.callee; |
| if (types.isIdentifier(callee, { name: "require" }) && !path.scope.getBindingIdentifier(callee.name)) { |
| path.node.arguments.filter(types.isStringLiteral).forEach(arg => { |
| const moduleId = arg.value; |
| const nameSpace = [moduleId]; |
| let currPath = path; |
| do { |
| let id = currPath.parent.id; |
| if (!id) { |
| id = currPath.parent.left; |
| if (id) { |
| id = path.scope.getBindingIdentifier(id.name) || id; |
| } else { |
| if (types.isIdentifier(currPath.parent.property)) { |
| nameSpace.push(currPath.parent.property.name); |
| } |
| currPath = currPath.parentPath; |
| continue; |
| } |
| }; |
| setSpecifier(id, nameSpace); |
| break; |
| } while (currPath); |
| }); |
| } else if (!tplCallee.has(callee) && isStylePath(path.get("callee"))) { |
| path.get("arguments").forEach((arg) => { |
| addObjectJob(arg.isFunction() ? arg.get("body") : arg); |
| }); |
| } |
| }, |
| TaggedTemplateExpression: (path) => { |
| if (isStylePath(path.get("tag"))) { |
| tplLiteral.add(path.node.quasi); |
| if (path.node.tag.callee) { |
| tplCallee.add(path.node.tag.callee); |
| } |
| } |
| }, |
| }; |
| |
| traverse(ast, visitor); |
| jobs.forEach(job => job()); |
| |
| objLiteral = Array.from(objLiteral).map(endNode => { |
| const objectSyntax = require("./object-syntax"); |
| let startNode = endNode; |
| if (startNode.leadingComments && startNode.leadingComments.length) { |
| startNode = startNode.leadingComments[0]; |
| } |
| let startIndex = startNode.start; |
| const before = source.slice(startNode.start - startNode.loc.start.column, startNode.start); |
| if (/^\s+$/.test(before)) { |
| startIndex -= before.length; |
| } |
| return { |
| startIndex, |
| endIndex: endNode.end, |
| skipConvert: true, |
| content: source, |
| opts: { |
| node: endNode, |
| }, |
| syntax: objectSyntax, |
| lang: "object-literal", |
| }; |
| }); |
| |
| tplLiteral = Array.from(tplLiteral).filter(node => ( |
| objLiteral.every(style => ( |
| node.start > style.endIndex || node.end < style.startIndex |
| )) |
| )).map(node => { |
| const quasis = node.quasis.map(node => ({ |
| start: node.start, |
| end: node.end, |
| })); |
| const style = { |
| startIndex: quasis[0].start, |
| endIndex: quasis[quasis.length - 1].end, |
| content: getTemplate(node, source), |
| }; |
| if (node.expressions.length) { |
| style.syntax = loadSyntax(opts, __dirname); |
| style.lang = "template-literal"; |
| style.opts = { |
| quasis: quasis, |
| }; |
| } else { |
| style.lang = "css"; |
| } |
| return style; |
| }); |
| |
| return (styles || []).concat(objLiteral).concat(tplLiteral); |
| }; |
| |
| module.exports = literalParser; |