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

Question: Are there any guidelines for creating an adjacent React package within @app? #69

Closed
hcharley opened this issue Dec 20, 2019 · 10 comments · Fixed by #81
Closed

Comments

@hcharley
Copy link
Contributor

hcharley commented Dec 20, 2019

Looking for an easy way to create a generic React package to supply my @app/client package with "dumb" components, but it seems like there is a lot of boilerplate needed that is slightly different from @app/graphql or @app/config, meaning that for newbies to the project, the overhead of creating a new React-specific module is non-trivial, and will probably leave others and myself creating sub-optimal implementations.

@benjie
Copy link
Member

benjie commented Dec 20, 2019

For React components it should be similar to (but simpler than) @app/graphql (since that's effectively a React components package, except the components are autogenerated). Namely you create the folder (@app/components) with a package.json and tsconfig.json in it; then you add @app/components as dependencies to the relevant other packages package.json files, and also add the path to it to the dependent packages' references in tsconfig.json. Does this make sense, and what issues are you facing?

@hcharley
Copy link
Contributor Author

That makes sense @benjie. My trouble was specifically getting the build commands just right. I gave up at first, and just dumped the components I wanted in a different modle inside of the @app/client module.

I'm now revisiting this, and will try again. I suspect the build commands, the tsconfigs, running yarn, getting sequential dependency installs just right, and probably some unforseen troubles will come my way. But I think it shouldn't be too hard to figure out and hopefully optimize.

@hcharley
Copy link
Contributor Author

Oh, one thing I'll need to sort out shortly is how to share the babel config. Or if I even need to share the babel config.

@hcharley
Copy link
Contributor Author

hcharley commented Dec 22, 2019

I ended up with an adjacent module (@app/client-foobar) with some of these files:

@app/client-foobar/tsconfig.json
@app/client-foobar/package.json
@app/client-foobar/src/index.ts
@app/client-foobar/src/FoobarComp.tsx
@app/client-foobar/src/BarFooComp.tsx
// @app/client-foobar/package.json

{
  "name": "@app/client-foobar",
  "private": true,
  "main": "dist/index.js",
  "version": "0.0.0",
  "scripts": {
    "build": "tsc",
    "watch": "yarn build --watch"
  },
  "dependencies": {
    "react": "^16.9.0",
    "antd": "^3.24.3",
    "@app/utils": "0.0.0" // Another module I created
  },
  "devDependencies": {
    "@babel/cli": "^7.7.7" // I don't think I need this. TODO: Consider removing.
  }
}
// @app/client-foobar/tsconfig.json

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "jsx": "react",
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "esModuleInterop": true,
    "declaration": true,
    "declarationMap": true,
    "isolatedModules": true,
    "noEmit": false,
    "outDir": "dist",
    "module": "commonjs"
  },
  "include": ["src"]
}

I also hoisted the .babelrc file to the top-level of the repo and renamed to babel.config.js because that seems like the suggested root file name to be used in Babel. I probably didn't need to do this, but I also simultaneously added Storybook to my project (I can help with #9 at a later point based on my implementation).

I renamed @app/graphql to be @app/client-graphql so I keep all of @app/client's dependencies super clear.

@hcharley
Copy link
Contributor Author

hcharley commented Dec 22, 2019

Update to my config:

I created a /tsconfig.client-submodule.json at the top-level, which my (now several) client dependencies extend off of:

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "jsx": "preserve",
    // TODO: Figure out when Zeit can support these in modules outside of `pages`:
    // "target": "esnext",
    // "module": "esnext",
    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    "target": "es5",
    "module": "commonjs",
    "lib": ["dom", "dom.iterable", "esnext"],
    "esModuleInterop": true,
    "isolatedModules": true,
    "noEmit": false,
    "noEmitOnError": false
  },
  "include": ["./types-client/**/*.d.ts"],
  "exclude": ["node_modules"]
}

The graphql module needs to have its main module be the dist/index.jsx (I'd like to figure out how to turn off jsx, which I think means I have to set jsx to be react. But this is not a top priority for me, since it works.). This is what its tsconfig looks like:

// @app/client-graphql/tsconfig.json
{
  "extends": "../../tsconfig.client-submodule.json",
  "compilerOptions": {
    "outDir": "dist"
  },
  "include": ["./src"]
}

Or another module:

// @app/client-elements/tsconfig.json
{
  "extends": "../../tsconfig.client-submodule.json",
  "include": ["src"],
  "compilerOptions": {
    "outDir": "./dist",
    "jsx": "react"
  }
}

Or another module (I need to remove the uesless include):

// @app/client-backbone/tsconfig.json
{
  "extends": "../../tsconfig.client-submodule.json",
  "include": ["./src", "../../types-client/**/*.d.ts"],
  "compilerOptions": {
    "outDir": "dist",
    "jsx": "react"
  }
}

Or another module:

// @app/client-atoms/tsconfig.json
{
  "extends": "../../tsconfig.client-submodule.json",
  "include": ["src"],
  "compilerOptions": {
    "outDir": "dist",
    "jsx": "react"
  }
}

Edit: Looking at this, it looks like I probably just need to set jsx at the top level config to be react.

@hcharley
Copy link
Contributor Author

A problem I've had is that despite the code compiling and working across sibling dependencies, is that the type checker seems to fail to pick up on the changes. Not exactly sure why though, but I suppose it's because babel sees the changes, but TS doesn't.

Does this trigger any lightbulbs for folks as to why?

@hcharley
Copy link
Contributor Author

hcharley commented Dec 28, 2019

This guide has been helpful in learning how to share babel.config.js file and transpiling modules across the (yarn) workspace, giving my monorepo more of a robust client-side configuration.

Creating a Next.js mono repository project with TypeScript by @josephluck

@josephluck
Copy link

@Charlex - It's the holidays atm, but I would be happy to lend a hand where I can!

@hcharley
Copy link
Contributor Author

@josephluck Nothing to request at this moment. I think I'm on my way to my ideal setup, just noting your work as being helpful!


Another update in my log:

I've switched over from having tsc actually building my modules (like how @app/graphql is built). I couldn't get emotion to work in sibling modules that were generated by tsc.

So I'm now having babel build all of my modules, and having tsc -b build only the declaration files.

So most of my sibling modules have a package.json that looks like:

{
  "name": "@app/sibling-module",
  "private": true,
  "main": "dist/index.js",
  "version": "0.0.0",
  "scripts": {
    "watch": "node ../../scripts/babel-watch",
    "build:babel": "node ../../scripts/babel-build"
  },
  "dependencies": {},
  "devDependencies": {}
}

With scripts/babel-watch and scripts/babel-build looking like:

#!/usr/bin/env node

const { exec } = require('shelljs');

exec('babel src --root-mode upward --out-dir dist --extensions ".ts,.tsx,.js"');

and

const { exec } = require('shelljs');

exec(
  'babel src --root-mode upward --out-dir dist --watch --extensions ".ts,.tsx,.js"'
);

I could have probably just used .sh files, or child_process. I was just lazy and used a library I was familiar with: shelljs.

My tsconfig files look as follows. They are very messy and could be consolidated into 1-2 configs, but just posting for completion and to record the evolution of my work in case it helps others. Will try and follow up on this and post what I end up with.

@app/*/tsconfig.json files look like:

{
  "extends": "../../tsconfig.client-submodule.json",
  "include": ["src"],
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
  }
}

tsconfig.client-submodule.json

{
  "extends": "./tsconfig.submodule.json",
  "compilerOptions": {
    "jsx": "react",
    // TODO: Figure out when Zeit can support these in modules outside of `pages`:
    // "target": "esnext",
    // "module": "esnext",
    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    "module": "commonjs",
    "lib": ["dom", "dom.iterable", "es2018", "esnext.asynciterable"],
    "noEmit": false,
    "emitDeclarationOnly": true
  }
}

tsconfig.backend-submodule.json

{
  "extends": "./tsconfig.submodule.json",
  "compilerOptions": {
    "module": "commonjs",
    "noEmit": false,
    "emitDeclarationOnly": true,
    "lib": ["es2018", "esnext.asynciterable"]
  }
}

tsconfig.submodule.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "noEmit": true,
    "emitDeclarationOnly": false,
    "target": "es2018"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "composite": true,
    "incremental": true,
    "noEmitOnError": true,
    "allowJs": false,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "jsx": "preserve",
    "lib": ["dom", "es2017"],
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "noFallthroughCasesInSwitch": true,
    "noImplicitAny": true,
    "preserveConstEnums": true,
    "resolveJsonModule": true,
    "sourceMap": true,
    "removeComments": false,
    "strict": true,
    "strictNullChecks": true,
    "importHelpers": true,
    "suppressImplicitAnyIndexErrors": true,
    "preserveWatchOutput": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noEmit": true,
    "declaration": true,
    "declarationMap": true,
    "baseUrl": ".",
    "paths": {
      "@app/*": ["@app/*"]
    }
  },
  "include": [],
  "exclude": ["@app/e2e"],
  "references": [
    { "path": "@app/xyz" },
    { "path": "@app/client-abc" },
    { "path": "@app/client-xxx" },
    { "path": "@app/client-yyy" },
    { "path": "@app/client-zzz" },
    { "path": "@app/core-config" },
    { "path": "@app/core-utils" },
    { "path": "@app/server" },
    { "path": "@app/foobar" },
    { "path": "@app/server-worker" }
  ]
}

I also switched over to using babel-jest

My package.json:

{
  "name": "@app/foobar",
  "version": "2.0.1",
  "private": true,
  "description": "Description of project here",
  "scripts": {
    "setup": "yarn && node ./scripts/setup.js",
    "start": "node ./scripts/start.js",
    "depcheck": "lerna exec --stream 'yarn depcheck --ignores=\"@app/coreconfig,@app/client,babel-plugin-import,source-map-support,@graphql-codegen/*,graphql-toolkit,net,tls\" --ignore-dirs=\".next\"'",
    "---------------------------- TEST ------------------------------------": "----------------------------------------------",
    "test": "node scripts/test.js",
    "test:watch": "node scripts/test.js --watch",
    "---------------------------- LINT ------------------------------------": "----------------------------------------------",
    "lint": "yarn build:ts && yarn prettier:check && eslint --ext .js,.jsx,.ts,.tsx,.graphql .",
    "lint:fix": "prettier --ignore-path .eslintignore --write '**/*.{js,jsx,ts,tsx,graphql,md}'; eslint --ext .js,.jsx,.ts,.tsx,.graphql . --fix",
    "prettier:check": "prettier --ignore-path .eslintignore --check '**/*.{js,jsx,ts,tsx,graphql,md}'",
    "--------------------------- HELPERS ----------------------------------": "----------------------------------------------",
    "licenses": "yarn --silent licenses generate-disclaimer > LICENSES.md",
    "---------------------------- CLEAN -----------------------------------": "----------------------------------------------",
    "clean": "node ./scripts/clean.js",
    "reset": "yarn clean && node ./scripts/delete-env-file.js",
    "----------------------------- DEV ------------------------------------": "----------------------------------------------",
    "dev": "yarn && lerna run codegen --stream && yarn lerna:build && concurrently --kill-others --names 'WATCH,DEV,TSC,TEST' --prefix '({name})' --prefix-colors 'yellow.bold,cyan.bold,greenBright.bold,magenta.bold' 'yarn:lerna:watch' 'yarn:lerna:dev' 'tsc -b -w --preserveWatchOutput' 'sleep 10; yarn test:watch'",
    "------------------------- DEV BUILD STEPS ----------------------------": "----------------------------------------------",
    "lerna:build": "lerna run --parallel build:babel",
    "lerna:watch": "lerna run --parallel watch",
    "lerna:dev": "lerna run --parallel dev",
    "---------------------- SHORTCUTS TO WORKSPACES -----------------------": "----------------------------------------------",
    "client": "yarn workspace @app /client",
    "db": "yarn workspace @app/db",
    "e2e": "yarn workspace @app/e2e",
    "react": "yarn workspace @app/client-atoms",
    "graphql": "yarn workspace @app/client-graphql",
    "server": "yarn workspace @app/server",
    "parser": "yarn workspace @app/parser",
    "worker": "yarn workspace @app/server-worker",
    "docker": "yarn --cwd ./docker",
    "docker-compose": "yarn --cwd ./docker compose"
  },
  "author": "H. Charley Bodkin <>",
  "license": "SEE LICENSE IN LICENSE.md",
  "dependencies": {
    "lerna": "^3.19.0"
  },
  "devDependencies": {
    "@types/jest": "^24.0.20",
    "@typescript-eslint/eslint-plugin": "^2.5.0",
    "@typescript-eslint/parser": "^2.5.0",
    "babel-jest": "^24.9.0",
    "babel-plugin-import-graphql": "^2.7.0",
    "babel-plugin-transform-react-remove-prop-types": "^0.4.24",
    "@emotion/babel-preset-css-prop": "^10.0.27",
    "@babel/plugin-proposal-optional-chaining": "^7.7.5",
    "concurrently": "^5.0.0",
    "depcheck": "^0.9.1",
    "dotenv": "^8.2.0",
    "eslint": "^6.8.0",
    "eslint-config-prettier": "^6.8.0",
    "eslint-plugin-cypress": "^2.8.1",
    "eslint-plugin-graphql": "^3.0.3",
    "eslint-plugin-jest": "^23.1.1",
    "eslint-plugin-prettier": "^3.1.2",
    "eslint-plugin-react": "^7.17.0",
    "eslint-plugin-react-hooks": "^2.3.0",
    "eslint_d": "^8.0.0",
    "inquirer": "^7.0.1",
    "jest": "^24.9.0",
    "mock-req": "^0.2.0",
    "mock-res": "^0.5.0",
    "nodemon": "^2.0.2",
    "prettier": "^1.19.1",
    "rimraf": "^3.0.0",
    "ts-jest": "^24.2.0",
    "ts-loader": "^6.1.0",
    "typescript": "^3.7.4",
    "update-dotenv": "^1.1.1",
    "@babel/node": "^7.7.7",
    "@babel/cli": "^7.7.7",
    "shelljs": "^0.8.3"
  },
  "resolutions": {
    "graphql": "14.x"
  },
  "workspaces": {
    "packages": [
      "@app/*"
    ],
    "nohoist": [
      "**/cypress"
    ]
  },
  "prettier": {
    "trailingComma": "es5",
    "proseWrap": "always"
  },
  "jest": {
    "roots": [
      "<rootDir>/@app"
    ],
    "transform": {
      "^.+\\.tsx?$": "babel-jest"
    },
    "testMatch": [
      "**/__tests__/**/*.test.[jt]s?(x)"
    ]
  }
}

Definitely could optimize this, but this is where i'm going.

I'll continue to post updates here for others who might follow a similar path. Perhaps will write up a blog post on my tweaks, and possible proposals for @graphile/starter improvments.

@benjie
Copy link
Member

benjie commented Jan 7, 2020

I've factored out some React components into their own @app/components package to show how I'd do it.

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 a pull request may close this issue.

3 participants