Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a yeoman generator for React+Typescript components #1548

Closed
wants to merge 19 commits into from

Conversation

bryceosterhaus
Copy link
Member

@bryceosterhaus bryceosterhaus commented Feb 13, 2019

Our previous generator was named generator-metal-clay so I renamed it and removed the metal reference and then added the react and ts support.

"compile": "tsc",
"prepublish": "npm run compile",
"start": "webpack-dev-server --mode development",
"test": "jest"
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 build a ".d.ts" file as well (something like I am doing here in my test side-project).


## Contribute

We'd love to get contributions from you! Please, check our [Contributing Guidelines](CONTRIBUTING.md) to see how you can help us improve.
Copy link
Member

Choose a reason for hiding this comment

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

We can update the CONTRIBUTING.md link.

Suggested change
We'd love to get contributions from you! Please, check our [Contributing Guidelines](CONTRIBUTING.md) to see how you can help us improve.
We'd love to get contributions from you! Please, check our [Contributing Guidelines](https://github.com/liferay/clay/blob/master/CONTRIBUTING.md) to see how you can help us improve.

@bryceosterhaus
Copy link
Member Author

@wincent @matuzalemsteles

I updated the structure a bit and moved some of the dependencies around. I couldn't find any great examples of this sort of monorepo that both the benefits of a monorepo and also the individual autonomy of the package. So I moved the testing infrastructure up to the root of the project and then also provided shared configs at the root.

@wincent I didn't fully copy your example you provided because I thought it required too much interdependency between packages for building. Reason I avoided that is because packages could technically be independent of other packages in this repo. Such as clay-badge, the only dependency would be on clay-css which isn't a build dependency.

Feel free to test it out by using the generator from the root of the repo.

I'm open to more suggestions in changing this around though. I'm not totally satisfied with this solution yet.

@bryceosterhaus
Copy link
Member Author

Decided to make additional changes... I wasn't quite happy with the last change.

Changes:

  • Hoisted jest/ts/babel configurations to root level
  • Each package has its own dependencies
  • Each package has its own build/test scripts. These can be run by...
    • cd ./packages/some-clay-component && yarn test
    • yarn workspace @clayui/come-clay-component run test
    • yarn test from root. This runs lerna run test which recursively runs that script in each module.

This way each module is autonomous in development, but also can be developed in parallel to other components with lerna.

IMO this is the way that seems to make most sense to me.

Let me know if you guys have any concerns or thoughts

Copy link
Contributor

@wincent wincent 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 really great @bryceosterhaus. I left a few comments because I think it is important to get this as close to perfect as possible before we open the floodgates and start generating packages with it, but this looks pretty darn close to ready.


let path = require('path');
let assert = require('yeoman-generator').assert;
let helpers = require('yeoman-generator').test;
Copy link
Contributor

Choose a reason for hiding this comment

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

These let look like they should all be const.

);
assert.fileContent('src/MyComponent.tsx', /export default MyComponent/);
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Just as style feedback, I think in tests we should favor arrow functions over function () {} for brevity, and value of this is irrelevant here.

]);
});

it('content of MyComponent.tsx', function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

This should read grammatically as an assertion; instead of "it content of MyComponent.tsx" it should be something like: "it produces MyComponent.tsx with templated content" (for example).

Suggested change
it('content of MyComponent.tsx', function() {
it('produces MyComponent.tsx with templated content', function() {

let _ = require('lodash');
let chalk = require('chalk');
let yeoman = require('yeoman-generator');
let yosay = require('yosay');
Copy link
Contributor

Choose a reason for hiding this comment

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

More let to const here, I think.

let yosay = require('yosay');

module.exports = yeoman.generators.Base.extend({
initializing: function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we assume this will be running in the context of a recent enough Node version that we can use ES6 object concise methods? ie.

module.exports = yeoman.generators.Base.extend({
        initializing() {
                // etc...

"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10",
"webpack": "^4.29.0"
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Pretty much all dev dependencies can and I would argue should be hoisted up to the repo root. The node modules resolution algorithm means that require('foo') will still be found when working inside a package directory. Rationale is that we ensure that we are using a standard set of dev dependencies across all packages without any divergent versions. I know that you want developers to be able to work within package directories, but they still need to clone the repo in order to do that, so declaring the dev dependencies at the top level doesn't make working locally any harder. Take a look at a typical monorepo project like Babel and you'll see that all the dev dependencies are at the top, with only a couple of exceptions.

If you're interested, I have a checker in that sample project that checks to make sure that dev dependencies only get declared at the top. Other checks are for things like missing deps, mismatched deps etc.

Copy link
Member Author

Choose a reason for hiding this comment

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

I had tried that originally but then I ran into issues with resolutions when I was in a package itself. Something couldve been out of wack so I will try that again though.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd say it's worth it. Babel at least has some docs on set-up for monorepos, although that might not help with the resolution issues you were having.

* SPDX-License-Identifier: BSD-3-Clause
*/
import * as React from 'react';
import getCN from 'classnames';
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should bind this to classNames, which is what the "classnames" README examples use.

import * as TestRenderer from 'react-test-renderer';
import <%= componentName %> from '../<%= componentName %>';

describe('<%= componentName %>', function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
describe('<%= componentName %>', function() {
describe('<%= componentName %>', () => {

I see you're already using arrow functions in the it examples.

import <%= componentName %> from '../<%= componentName %>';

describe('<%= componentName %>', function() {
it('should render', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Style: generally "should" should be avoided in example descriptions. You can always express it more concisely by leading directly with a verb. This is useful because it makes it more likely that you can fit the description in a single line. So in this case it would be:

Suggested change
it('should render', () => {
it('renders', () => {

tsconfig.json Outdated
"removeComments": true,
"baseUrl": "./",
"paths": {
"@clay/*": ["packages/*/src"],
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"@clay/*": ["packages/*/src"],
"@clayui/*": ["packages/*/src"],

To match the change you made elsewhere? AFAIK we've requested @clay but no idea whether that can/will happen.

Copy link
Member

@matuzalemsteles matuzalemsteles left a comment

Choose a reason for hiding this comment

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

hey @bryceosterhaus, I left some comments. I know we can improve the way we package each package, I had opened an issue about it in #1107. But I think we can improve this in the future.

"lerna": "lerna bootstrap -- --no-optional --no-package-lock",
"link": "lerna run link",
"lint": "eslint packages/**/*.js examples/**/*.js scripts/**/*.js",
"pa11y": "pa11y-ci ./packages/clay-*/demos/a11y.html",
"prettier": "prettier-eslint packages/**/**/*.js examples/**/**/*.js scripts/**/**/*.js",
"start": "http-server . -p 4000",
Copy link
Member

Choose a reason for hiding this comment

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

hey @bryceosterhaus, I know we had talked about the demos and that this would not be the best option, I've been thinking about testing https://storybook.js.org/, I've seen some use cases in a monorepo, I think it is a great experience. We can test it once we start having some components in the master.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I agree we definitely need easily accessible demos on the repo level. Storybook is pretty great, I have used it in the past and it was easy to use. One other thing I am investigating is the possibility of using "Framer X" and that could also possible be a way to run demos. I am working with some of the UX leads here in LA and gonna determine if framer fits our needs.

I think this can be a tracked in a differ issue though, so I have created one #1562

Copy link
Member

Choose a reason for hiding this comment

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

Oh great, I tested a component of Metal.js at the time with Framer X worked up, even though it was not React, I built a layer to give compatibility... The big problem was the CSS that are scoped.

package.json Outdated
"lerna": "lerna bootstrap -- --no-optional --no-package-lock",
"link": "lerna run link",
"lint": "eslint packages/**/*.js examples/**/*.js scripts/**/*.js",
"pa11y": "pa11y-ci ./packages/clay-*/demos/a11y.html",
"prettier": "prettier-eslint packages/**/**/*.js examples/**/**/*.js scripts/**/**/*.js",
"start": "http-server . -p 4000",
"test": "npm run build && npm run jest && npm run pa11y",
"test": "lerna run test && npm run pa11y",
Copy link
Member

Choose a reason for hiding this comment

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

Just putting my thoughts about it:

  • One of the points I like about having the jest running at the root is that we will only have one jest startup running and we can reap the coverage much better than terms in each package its coverage... This facilitates the information on https://coveralls.io/github/liferay/clay.
  • We will have many components here still, so running the test for each project will take a long time for you to know if you have not broken another component that depends on it.
  • When running the tests in this way, we will have the high boot time in each package, since we raise the jest all the time to test the package, maybe increase the CI time.
  • I like to run the tests at the root because I can select which tests I want to run when I'm working on just one project. yarn jest packages/clay-badge for example.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah yeah that makes sense. I think if we hoist devDependencies like Greg had suggested then we can also probably run a single instance of Jest at both the root and package levels. Ill look into this.

@bryceosterhaus
Copy link
Member Author

bryceosterhaus commented Feb 15, 2019

@matuzalemsteles @wincent just updated.

Here are the highlights

  • Moved devDependencies to root. (we can still call the individual package scripts by using yarn build in the package itself)
  • Refactored some things from review comments
  • Cleaned up package.json dependencies at root
  • Created clay-badge component via the generator to showcase use

Also I added charts to the eslint and prettier ignore for now. Gonna re-write it to use typescript and ill remove those from the ignore files in that issue.

@@ -0,0 +1,31 @@
{
"name": "@clayui/clay-badge",
Copy link
Member

Choose a reason for hiding this comment

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

hey @bryceosterhaus as we now have the scope, I think it is no longer necessary to add clay-, just leaving @clayui/badge.

.eslintrc Outdated Show resolved Hide resolved
"scripts": {
"build": "babel src --root-mode upward --out-dir lib --extensions .ts,.tsx",
"build:types": "tsc --project ./tsconfig.declarations.json",
"prepublish": "npm run build && npm run build:types",
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably be "prepublishOnly", right?

https://docs.npmjs.com/misc/scripts

@bryceosterhaus
Copy link
Member Author

@matuzalemsteles @wincent made updates in regards to your comments, re-ran the generator for clay-badge and then also added ts parser for eslint.

@wincent
Copy link
Contributor

wincent commented Feb 25, 2019

CC @marcoscv-work and @carloslancha in case you haven't seen this yet.

.eslintrc Outdated
}
]
],
"sort-vars": 2
Copy link
Contributor

Choose a reason for hiding this comment

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

A lot of this custom stuff would ideally be merged up into the "eslint-config-liferay" package, but that is currently a little hard for us to update because it is not in the Liferay org repo even though that's where the package metadata says it should be; I have an internal ticket open for sorting that out: https://issues.liferay.com/browse/IFI-482.

Copy link
Member Author

Choose a reason for hiding this comment

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

Great idea. I had just copied over some of the rules we used in Analytics cloud. We slowly added them as we worked on the project and found them beneficial.

Copy link
Contributor

Choose a reason for hiding this comment

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

We're in the process of migrating the config to its proper location so we can start adapting it... one thing I'm worried about is if these long list makes sense for all cases or just for the jsx one... should we have different config bases, like eslint-config-liferay and eslint-config-liferay-jsx?

Copy link
Contributor

Choose a reason for hiding this comment

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

One config to rule them all...

package.json Outdated
"start": "http-server . -p 4000",
"test": "npm run build && npm run jest && npm run pa11y",
"lint": "eslint \"**/*.{js,ts,tsx}\"",
"prettier": "prettier-eslint \"**/*.{js,ts,tsx}\"",
Copy link
Contributor

Choose a reason for hiding this comment

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

You can safely use single quotes here if you want to avoid the backslash escaping.

* SPDX-License-Identifier: BSD-3-Clause
*/
import * as React from 'react';
import * as ReactDOM from 'react-dom';
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need to do the * as thing here (or in the other files either). If you search, you'll find a few discussions about what React exports (and what it "should export"); this is the current reality:

And the official docs (ReactDOM, React) just tell you to import React, import ReactDOM.

Copy link
Member Author

Choose a reason for hiding this comment

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

The reason I use * as React was due to the ts compiler complaining.

screen shot 2019-02-25 at 12 17 19 pm

I looked into it and we would have to add this option to our tsconfig, "allowSyntheticDefaultImports": true. Not sure if that is the route we want to go though.

I was reading these issues about it, microsoft/TypeScript#3337 and microsoft/TypeScript-React-Starter#8

Any preference on it?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, that's a bummer. I wanted to stick to the pattern in the React docs but allowSyntheticDefaultImports seems like a bit of a hack. So * as React is probably the least horrible option.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah thats what I figured as well. Ill just leave it as is for now, and then if for some reason TS fixes this, we can make the change everywhere.

@@ -0,0 +1 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
Copy link
Contributor

Choose a reason for hiding this comment

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

I would have expected there to be some content in here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated this in the generator

const HWP = require('html-webpack-plugin');

module.exports = {
entry: path.join(__dirname, './demo/App.tsx'),
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: if you're using path.join, you don't need the "./" in "./demo/App.tsx".

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated this in the generator

.eslintrc Outdated Show resolved Hide resolved
@bryceosterhaus
Copy link
Member Author

Once the generator portion of this PR is good to go, I am going to re-generate the badge component based on the generator. I'll also squash all of the generator commits into a single commit.

@matuzalemsteles
Copy link
Member

Just started reviewing :)

:octocat: Sent from GH.

Copy link
Member

@matuzalemsteles matuzalemsteles left a comment

Choose a reason for hiding this comment

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

LGTM!

@matuzalemsteles
Copy link
Member

hey guys, I think we're ready to go with that forward, @wincent do you have anything else you want to comment on? I want to merge this so we can go ahead with #1440 and #1427 and apply the patterns we've added here.

@wincent
Copy link
Contributor

wincent commented Feb 28, 2019

Nah, I don't have anything else. I think this is a good enough base upon which we can iterate further. I wanted to make sure we didn't rush this, in order to give people time to comment, but I figure that at this point everybody who cares has probably already shared their opinions.

@jbalsas
Copy link
Contributor

jbalsas commented Feb 28, 2019

I care and I haven't! (I'll just take a quick look right away, but I'm sure it'll be to my liking 🤓)

.travis.yml Show resolved Hide resolved
@wincent
Copy link
Contributor

wincent commented Mar 1, 2019

Ah, a thing I forgot to mention. .eslintrc is deprecated; we should use .eslintrc.js instead.

@bryceosterhaus
Copy link
Member Author

Updated

@bryceosterhaus
Copy link
Member Author

@jbalsas , Let me know if there is anything else wanted in this PR.

@matuzalemsteles
Copy link
Member

hey guys, just an update about the @clay scope in npm: I received the response from the NPM team that it will not be possible for them to proceed even though the user did not reply to their emails because there are packages in the user account that are still being downloaded very frequently. I think we'll have to keep @clayui. 😞

cc @wincent, @bryceosterhaus, @jbalsas

@bryceosterhaus
Copy link
Member Author

I went ahead and created a new PR with a cleaned up commit history, seen here #1648

@jbalsas
Copy link
Contributor

jbalsas commented Mar 12, 2019

Closing in favour of #1648 to reduce noise here!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants