cssinjs / jss

JSS is an authoring tool for CSS which uses JavaScript as a host language.
https://cssinjs.org
MIT License
7.06k stars 397 forks source link

Classnaming missing component name #1390

Open Chartieer opened 3 years ago

Chartieer commented 3 years ago

Expected behavior: Wrong or Missing classnames if I follow the basic example from docu. The basic example output (https://cssinjs.org/react-jss/?v=v10.3.0#basic ) should be

<button class="Button-myButton-1-25">
    <span class="Button-myLabel-1-26">
      Submit
    </span>
  </button>

Describe the bug: if using the basic example from the name of the component/file isn´t included in classnames as described here https://cssinjs.org/react-jss/?v=v10.3.0#basic

output is:

<button class="myButton-0-2-1">
    <span class="myLabel-0-2-2">sdsds</span>
</button>

Codesandbox link: https://codesandbox.io/s/condescending-bush-lzdjv?fontsize=14&hidenavigation=1&theme=dark

Versions (please complete the following information):

Chartieer commented 3 years ago

Solved by

const useStyles = createUseStyles({
  myButton: {
    color: 'green',
    margin: {
      // jss-plugin-expand gives more readable syntax
      top: 5, // jss-plugin-default-unit makes this 5px
      right: 0,
      bottom: 0,
      left: '1rem'
    },
    '& span': {
      // jss-plugin-nested applies this to a child span
      fontWeight: 'bold' // jss-plugin-camel-case turns this into 'font-weight'
    }
  },
  myLabel: {
    fontStyle: 'italic'
  }
}, {name: "writing-documentation"})

Why not adding the displayName by default, as like within withStyles/InjectStyleSheets HOCs And maybe some documentation for the Hooks?

kof commented 3 years ago

not possible by default, there is no name to use like it is the case with withStyles

Chartieer commented 3 years ago

They did not know it was impossible so they did it https://styled-components.com/docs/tooling

kof commented 3 years ago

Impossible without a babel plugin, if you want to write it, let me know.

Chartieer commented 3 years ago
module.exports = transform;
var pathMod = require('path')

function transform (babel) {
  return {
    visitor: {
      ClassDeclaration: function (path, state) {
        if (classHasRenderMethod(path)) {
          setDisplayNameAfter(path, path.node.id, babel.types)
        }
      },
      FunctionDeclaration: function (path, state) {
        if (doesReturnJSX(path.node.body) || (path.node.id && path.node.id.name &&
                                              isKnownComponent(path.node.id.name, state.opts.knownComponents))) {
          var displayName
          if (path.parentPath.node.type === 'ExportDefaultDeclaration') {
            if (path.node.id == null) {
              // An anonymous function declaration in export default declaration.
              // Transform `export default function () { ... }`
              // to `var _uid1 = function () { .. }; export default __uid;`
              // then add displayName to _uid1
              var extension = pathMod.extname(state.file.opts.filename)
              var name = pathMod.basename(state.file.opts.filename, extension)

              var id = path.scope.generateUidIdentifier("uid");
              path.node.id = id
              displayName = 'JSS_'+name
            }
            setDisplayNameAfter(path, path.node.id, babel.types, displayName)
          }else if(path.parentPath.node.type === 'Program' || path.parentPath.node.type == 'ExportNamedDeclaration') {
            setDisplayNameAfter(path, path.node.id, babel.types, displayName)
          }
        }
      },
      FunctionExpression: function (path, state) {
        if(shouldSetDisplayNameForFuncExpr(path, state.opts.knownComponents)) {
          var id = findCandidateNameForExpression(path)
          if (id) {
            setDisplayNameAfter(path, id, babel.types)
          }
        }
      },
      ArrowFunctionExpression: function (path, state) {
        if(shouldSetDisplayNameForFuncExpr(path, state.opts.knownComponents)) {
          var id = findCandidateNameForExpression(path)
          if (id) {
            setDisplayNameAfter(path, id, babel.types)
          }
        }
      }
    }
  }
}

function isKnownComponent(name, knownComponents) {
  return (name && knownComponents && knownComponents.indexOf(name) > -1)
}

function componentNameFromFilename(filename) {
  var extension = pathMod.extname(filename);
  var name = pathMod.basename(filename, extension)
  return name
}

function shouldSetDisplayNameForFuncExpr(path, knownComponents) {
  // Parent must be either 'AssignmentExpression' or 'VariableDeclarator' or 'CallExpression' with a parent of 'VariableDeclarator'
  var id
  if (path.parentPath.node.type === 'AssignmentExpression' &&
      path.parentPath.node.left.type !== 'MemberExpression' && // skip static members
      path.parentPath.parentPath.node.type == 'ExpressionStatement' &&
      path.parentPath.parentPath.parentPath.node.type == 'Program') {
    id = path.parentPath.node.left
  }else{
    // if parent is a call expression, we have something like (function () { .. })()
    // move up, past the call expression and run the rest of the checks as usual
    if(path.parentPath.node.type === 'CallExpression') {
      path = path.parentPath
    }

    if(path.parentPath.node.type === 'VariableDeclarator') {
      if (path.parentPath.parentPath.parentPath.node.type === 'ExportNamedDeclaration' ||
          path.parentPath.parentPath.parentPath.node.type === 'Program') {
        id = path.parentPath.node.id
      }
    }
  }

  if (id) {
    if (id.name && isKnownComponent(id.name, knownComponents)) {
      return true
    }
    return doesReturnJSX(path.node.body)
  }

  return false
}

function classHasRenderMethod(path) {
  if(!path.node.body) {
    return false
  }
  var members = path.node.body.body
  for(var i = 0; i < members.length; i++) {
    if (members[i].type == 'ClassMethod' && members[i].key.name == 'render') {
      return true
    }
  }

  return false
}

// https://github.com/babel/babel/blob/master/packages/babel-plugin-transform-react-display-name/src/index.js#L62-L77
// crawl up the ancestry looking for possible candidates for displayName inference
function findCandidateNameForExpression(path) {
  var id
  path.find(function (path) {
    if (path.isAssignmentExpression()) {
      id = path.node.left;
    // } else if (path.isObjectProperty()) {
      // id = path.node.key;
    } else if (path.isVariableDeclarator()) {
      id = path.node.id;
    } else if (path.isStatement()) {
      // we've hit a statement, we should stop crawling up
      return true;
    }

    // we've got an id! no need to continue
    if (id) return true;
  });
  return id
}

function doesReturnJSX (body) {
  if (!body) return false
  if (body.type === 'JSXElement') {
    return true
  }

  var block = body.body
  if (block && block.length) {
    var lastBlock = block.slice(0).pop()

    if (lastBlock.type === 'ReturnStatement') {
      return lastBlock.argument !== null && lastBlock.argument.type === 'JSXElement'
    }
  }

  return false
}

function setDisplayNameAfter(path, nameNodeId, t, displayName) {
  if (!displayName) {
    displayName = 'JSS_'+nameNodeId.name
  }

  var blockLevelStmnt
  path.find(function (path) {
    if (path.parentPath.isBlock()) {
      blockLevelStmnt = path
      return true
    }
  })

  if (blockLevelStmnt) {
    var trailingComments = blockLevelStmnt.node.trailingComments
    delete blockLevelStmnt.node.trailingComments

    var setDisplayNameStmn = t.expressionStatement(t.assignmentExpression(
      '=',
      t.memberExpression(nameNodeId, t.identifier('displayName')),
      t.stringLiteral(displayName)
    ))

    blockLevelStmnt.insertAfter(setDisplayNameStmn)
  }
}
kof commented 3 years ago

We would need a package, tests and docs to make it work for everyone

Chartieer commented 3 years ago

Reads like https://pasteboard.co/Jqif3TEc.png

Chartieer commented 3 years ago

Currently the displayname is hardcoded prefixed with 'JSS_' Test it from here https://github.com/Chartieer/babel-plugin-react-jss

Don´t forget to add the impossible default value. ;-) There is also a Pull request for the missing docu

Hope it helps