Skip to content
This repository has been archived by the owner on Dec 13, 2018. It is now read-only.

feat: allow importing built-in glamorous components #131

Merged
merged 1 commit into from
May 25, 2017
Merged

feat: allow importing built-in glamorous components #131

merged 1 commit into from
May 25, 2017

Conversation

marzelin
Copy link
Collaborator

@marzelin marzelin commented May 24, 2017

What:
allow something like import { Div, Span } from 'glamorous'

Why:
Convenient helpers improve user experience.

How:
Create a build script that appends exports to the es file.

Checklist:

  • Documentation
  • Tests
  • Code complete
  • Added myself to contributors table
  • Followed the commit message format

Closes #123

Sidenote:
This is not related directly to this commit, but my liter shows an error in dist/glamorous.es.js:
'A module cannot have multiple default exports.'
That's caused by line 1177: exports.default = reactHtmlAttributes$2;

@kentcdodds
Copy link
Contributor

kentcdodds commented May 24, 2017

Hi! Thanks so much for this! I'm going to pull it down and run it to double check, but it looks pretty great.

Here are the linting failures. You may want to run npm start validate to check things before you push your changes :)

screen shot 2017-05-24 at 3 14 49 pm

@kentcdodds
Copy link
Contributor

I would love it if we could also get a test in the dist-tests to check whether you can import things as we want to. Can do it with just one or two elements I think. Also documentation updates would be useful.

@kentcdodds
Copy link
Contributor

As for your linter issues, I think your linter is busted unless I'm missing something. That file is fine I think.

.map(tag => {
const validJsName = dashToCamelCase(tag)
const capitalName = capitalize(validJsName)
return `export const ${capitalName} = glamorous['${tag}']`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a semicolon here because this is our transpiled code, we should never rely on ASI.

@codecov-io
Copy link

codecov-io commented May 24, 2017

Codecov Report

Merging #131 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@          Coverage Diff          @@
##           master   #131   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files          10     10           
  Lines         130    130           
  Branches       33     33           
=====================================
  Hits          130    130

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b742428...57c8a4a. Read the comment docs.

@marzelin
Copy link
Collaborator Author

  • fix bug where component names collide with JavaScript constructors
  • tests added
  • documentation added

Copy link
Contributor

@kentcdodds kentcdodds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! Just a few comments :)

return s.replace(/-([a-z])/g, ([, char]) => char.toUpperCase())
}

const jsBuiltInObjectNames = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether there's an npm package (probably by Sindre Sorhus) that has these so we don't have to hard-code them 🤔

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the reserved package wasn't updated in 3 years and is a bit outdated. I can use node.js to generate the list.

]

function unCollide(name) {
const prefix = 'html'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very interesting! Makes total sense. Could you explain why (perhaps in a code comment) the prefix html is used? Seems like a good one, just curious to know why.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, also, this should probably be capitalized. All the others have the first letter capitalized so it's:

import {HtmlObject, Article} from 'glamorous'

Rather than:

import {htmlObject, Article} from 'glamorous'

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose html because users may already be familiar with this prefix since it is used in JavaScript (htmlFor). But since component names have to be capitalized (otherwise React/JSX treats the tag as custom element), I think postfix Tag (ie ObjectTag) is a nicer alternative. What do you think?

@@ -0,0 +1,88 @@
const appendToFile = require('fs').appendFile

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you getting a warning with this? I had to switch to appendFileSync to get rid of it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting no warnings. I think async execution is not super useful here, so I can switch to sync.

@marzelin
Copy link
Collaborator Author

  • change html prefix to Tag postfix when name collisions
  • automatically create a constructor names list
  • change appendFile to appendFileSync

Copy link
Contributor

@kentcdodds kentcdodds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking so great. Thank you again for working on it. Just a few comments. Also, I wonder if there's a way for us to verify that the tree shaking works 🤔 Maybe we could add an example to the /examples? We could probably do that in a separate PR. @souporserious, would you be willing to work on that? It should be something really simple. Could even be with CRA and we commit the bundle.js where you can see that it's been tree-shaken.

README.md Outdated
@@ -131,6 +131,8 @@ If you're transpiling (and/or using the `jsnext:main`):

```javascript
import glamorous, {ThemeProvider} from 'glamorous'
// you can also import specific components
import {Div, H2} from 'glamorous'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you include a line about the special case for JavaScript built-ins like Object? Something like:

// Tags with the same name as built-in JavaScript objects are importable with a Tag suffix, IE: ObjectTag.

return `export const ${tagName} = glamorous['${tag}'];`
}).join`\n`

const toWrite = `\n${elementExports}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be handy to add a comment for the first line that says:

// these exports below are generated and will be tree-shaken if you're using Webpack 2 or Rollup

@marzelin
Copy link
Collaborator Author

  • add a comment preceding exports
  • provide info about Tag suffix in documentation

Copy link
Contributor

@kentcdodds kentcdodds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reeeally close! Thanks for your tireless iteration on this 👍

}

// node version <= 7 does not support trailing commas in function calls
// prettier-ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL :)

return !!s && s[0] === s[0].toUpperCase()
}

// node version <= 7 does not support trailing commas in function calls
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally, I'll add this at the end of files which are not transpiled:

// this is not transpiled
/*
  eslint
  max-len: 0,
  comma-dangle: [
    2,
    {
      arrays: 'always-multiline',
      objects: 'always-multiline',
      functions: 'never'
    }
  ]
 */

// node version <= 7 does not support trailing commas in function calls
// prettier-ignore
const jsBuiltInConstructorNames = Object.getOwnPropertyNames(global)
.filter(isCapitalized)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure whether this is really necessary 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not necessary but reduces the number of items to compare from 63 to 40 which improves performance a bit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm more concerned about readability/maintainability of this file than performance and would prefer to not include anything in here that's only here for performance reasons. The script is really fast as it is :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I think this code is quite readable but of course less code is always a plus. I'll remove it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's not about how much code, it's about the fact that you need to decide why this matters, and the fact is that it doesn't ;-)

Thanks so much!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're discussing it so it kind of matters (or it's just bikeshedding) ;)

const capitalName = capitalize(validJsName)
// add postfix if name collides with js constructors
const tagName = unCollide(capitalName)
return `export const ${tagName} = glamorous['${tag}'];`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! Close call, this should actually be:

return `export const ${tagName} = glamorous['${capitalName}'];`

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering what to put there. Isn't glamorous.div equal to glamourous.Div?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not. Here's some terminology that should hopefully be helpful:

  1. glamorous is a "componentFactoryFactory"
  2. glamorous.div is a "componentFactory" which is created by calling glamorous('div')
  3. glamorous.Div is a "component" which is created by calling (effectively) glamorous('div')() (in addition to a few extra things)

We don't (and wont?) support exporting the component factories, but exporting the components themselves could be useful.

I hope that helps clarify :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should've RTFM 😊 sorry

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we just remove componentFactory and move its competencies onto a glamorous component? That would make things simpler.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've considered this, and I'm not totally against it. I think that it could make things even more complex from both an API and implementation standpoint, but I'm open to talk about it in another issue if you'd like to bring it up.

README.md Outdated
@@ -131,6 +131,12 @@ If you're transpiling (and/or using the `jsnext:main`):

```javascript
import glamorous, {ThemeProvider} from 'glamorous'

// you can also import specific components
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably call out the fact that these are the built-in components by saying:

Please read the section below on "Built-in" components.

@marzelin
Copy link
Collaborator Author

  • export GlamorousComponents instead of GlamorousComponentFactories
  • remove unnecessary function
  • improve documentation
  • make tests more robust


// tags with the same name as built-in JavaScript objects are importable with a Tag suffix
// and tag names that contain dashes are transformed to CamelCase
import {MapTag, ColorProfile} from 'glamorous'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is perfect!

@kentcdodds
Copy link
Contributor

Wonderful! Thank you!

@kentcdodds kentcdodds merged commit 789b97e into paypal:master May 25, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow importing built-in glamorous components
4 participants