├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── babel.config.json ├── jest.config.js ├── lerna.json ├── package-lock.json ├── package.json └── packages ├── create-react-pkg ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src │ ├── index.js │ ├── pkgTemplate.js │ └── utils.js ├── templates │ ├── baseFiles │ │ ├── LICENSE │ │ ├── README.md │ │ ├── gitignore │ │ └── playground │ │ │ ├── index.html │ │ │ └── index.js │ ├── basic-storybook │ │ ├── .storybook │ │ │ ├── main.js │ │ │ └── preview.js │ │ ├── src │ │ │ └── index.js │ │ ├── stories │ │ │ └── MyComponent.stories.jsx │ │ └── test │ │ │ └── index.test.js │ ├── basic │ │ ├── src │ │ │ └── index.js │ │ └── test │ │ │ └── index.test.js │ ├── typescript-storybook │ │ ├── .storybook │ │ │ ├── main.js │ │ │ └── preview.js │ │ ├── src │ │ │ └── index.tsx │ │ ├── stories │ │ │ └── MyComponent.stories.tsx │ │ ├── test │ │ │ └── index.test.tsx │ │ └── tsconfig.json │ └── typescript │ │ ├── src │ │ └── index.tsx │ │ ├── test │ │ └── index.test.tsx │ │ └── tsconfig.json └── test │ └── utils.test.js └── react-pkg-scripts ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── config.js ├── eslint │ └── eslintFormatter.js ├── index.js ├── jest │ ├── babelTransform.js │ └── cssTransform.js ├── paths.js ├── rollup │ ├── rollupConfig.js │ ├── rollupESLintPlugin.js │ └── rollupGenerateHtml.js ├── scripts │ ├── build.js │ ├── preview.js │ ├── test.js │ └── watch.js ├── types │ └── index.d.ts └── utils.js └── test └── utils.test.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | packages/create-react-pkg/templates 3 | lib 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2021": true, 4 | "node": true, 5 | "jest": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "ecmaVersion": "latest", 10 | "sourceType": "module" 11 | }, 12 | "rules": {} 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | test: 11 | name: Test on Node ${{ matrix.node-version }} and ${{ matrix.os }} 12 | runs-on: ${{ matrix.os }} 13 | 14 | strategy: 15 | matrix: 16 | node-version: [14.x, 16.x] 17 | os: [ubuntu-latest] 18 | # os: [ubuntu-latest, windows-latest, macOS-latest] 19 | 20 | steps: 21 | - name: Checkout repo 22 | uses: actions/checkout@v2 23 | 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | 30 | - name: Install deps 31 | run: npm ci 32 | 33 | - name: Install package deps 34 | run: lerna bootstrap 35 | 36 | - name: Lint 37 | run: npm run lint 38 | 39 | - name: Build 40 | run: npm run build 41 | 42 | - name: Test 43 | run: npm test 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-present Haseeb Anwar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Create React Package 2 | 3 | Create React packages with no build configuration. 4 | 5 | ## Quick Overview 6 | 7 | ```sh 8 | npx create-react-pkg my-package 9 | cd my-package 10 | npm start 11 | ``` 12 | 13 | You don’t need to install or configure tools like Rollup, Babel, or ESLint. They are pre-configured and hidden so that you can focus on the code. 14 | 15 | ## Contents 16 | 17 | - [Why](#why) 18 | - [Getting Started](#getting-started) 19 | - [Choose Package Manager](#choose-package-manager) 20 | - [npm](#npm) 21 | - [yarn](#yarn) 22 | - [Options](#options) 23 | - [Available Scripts](#available-scripts) 24 | - [`npm start` or `yarn start`](#npm-start-or-yarn-start) 25 | - [`npm test` or `yarn test`](#npm-test-or-yarn-test) 26 | - [`npm run build` or `yarn build`](#npm-run-build-or-yarn-build) 27 | - [`npm run preview` or `yarn preview`](#npm-run-preview-or-yarn-preview) 28 | - [Building your Package](#building-your-package) 29 | - [Install a Dependency](#install-a-dependency) 30 | - [Manage External Dependencies](#manage-external-dependencies) 31 | - [Preview](#preview) 32 | - [Build and Publish](#build-and-publish) 33 | - [Integrated Playground](#integrated-playground) 34 | - [Specify port for Server](#specify-port-for-server) 35 | - [Use CJS build with Playground](#use-cjs-build-with-playground) 36 | - [Philosophy](#philosophy) 37 | - [Customization](#customization) 38 | - [Config Intellisense](#config-intellisense) 39 | - [Config Options](#config-options) 40 | - [Rollup](#rollup) 41 | - [Conditional Rollup Config](#conditional-rollup-config) 42 | - [Example: Import images](#example-import-images) 43 | - [Babel](#babel) 44 | - [Example: Optimize Lodash](#example-optimize-lodash) 45 | - [ESLint](#eslint) 46 | - [Jest](#jest) 47 | - [CLI Options](#cli-options) 48 | - [Styling](#styling) 49 | - [Sass/Stylus/Less Files](#sassstylusless-files) 50 | - [Post-Processing CSS](#post-processing-css) 51 | - [Advanced Usage](#advanced-usage) 52 | - [Code Splitting](#code-splitting) 53 | - [Configure Supported Browsers](#configure-supported-browsers) 54 | - [Author](#author) 55 | - [License](#license) 56 | 57 | ## Why 58 | 59 | - Get started in seconds, easy to maintain, just one dependency 60 | - CJS, ESM, and UMD modules support 61 | - Pre-configured Rollup, Babel, Jest, and ESLint 62 | - Completely customizable 63 | - Integrated Playground 64 | - Tree-shaking 65 | - Code-splitting 66 | - Dev/Production builds 67 | - TypeScript support 68 | - Storybook support 69 | - Compile-time linting with ESLint 70 | - Out-of-the-box support for CSS, SASS, and JSON files 71 | - Pre-configured Browserslist, Sourcemaps, and Minification 72 | - VSCode friendly errors 73 | 74 | ## Getting Started 75 | 76 | ### Choose Package Manager 77 | 78 | **You’ll need to have Node 14.17.0 or a later version on your local development machine**. It is recommended to use the latest LTS version. You can use [nvm](https://github.com/creationix/nvm#installation) (macOS/Linux) or [nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows) to switch Node versions between different projects. 79 | 80 | To create a new package, you may choose one of the following methods: 81 | 82 | #### npm 83 | 84 | ```sh 85 | npx create-react-pkg my-package 86 | ``` 87 | 88 | or 89 | 90 | ```sh 91 | npm create react-pkg my-package 92 | ``` 93 | 94 | #### yarn 95 | 96 | ```sh 97 | yarn create react-pkg my-package 98 | ``` 99 | 100 | ### Options 101 | 102 | `create-react-pkg` comes with the following options: 103 | 104 | - **--ts, --typescript**: Initialize a TypeScript project. 105 | - **--sb, --storybook**: Add storybook support. 106 | 107 | ```shell 108 | # npx 109 | npx create-react-pkg my-package --ts 110 | 111 | # npm 6.x 112 | npm create react-pkg my-package --ts 113 | 114 | # npm 7+, extra double dash is needed 115 | npm create react-pkg my-package -- --ts 116 | 117 | # yarn 118 | yarn create react-pkg my-package --ts 119 | ``` 120 | 121 | ### Available Scripts 122 | 123 | Inside the newly created project, you can run some built-in commands: 124 | 125 | #### `npm start` or `yarn start` 126 | 127 | Runs the project in development mode, watches for file changes, and rebuilds on change. The build errors and lint warnings are printed in the console as you go. 128 | 129 |

130 | npm start 131 |

132 | 133 | #### `npm test` or `yarn test` 134 | 135 | Runs your tests with Jest test runner. 136 | 137 | #### `npm run build` or `yarn build` 138 | 139 | Creates an optimized production build of your package in CommonJS, ES, and UMD module formats. 140 | 141 | #### `npm run preview` or `yarn preview` 142 | 143 | Opens the integrated playground app from `playground/index.js` in the browser for developing and previewing your library. The development server comes with live reload that makes development much easier. 144 | 145 | Read more in [Integrated Playground](#integrated-playground) section. 146 | 147 | ## Building your Package 148 | 149 | ### Install a Dependency 150 | 151 | The generated project includes `react` and `react-dom` along with the scripts used by Create React Package as development dependencies. 152 | 153 | You may install other dependencies, for example, Material UI: 154 | 155 | ```sh 156 | npm i -D @mui/material 157 | ``` 158 | 159 | Since you are building a library, you probably need to install Material UI or other related frameworks as dev dependencies. It is the responsibility of the app consuming your library to have these dependencies installed. 160 | 161 | It is important that you define such dependencies as external dependencies. 162 | 163 | ### Manage External Dependencies 164 | 165 | External dependencies are those that should not be included in the bundled code of your library and should be installed by the consumer of the library. 166 | 167 | To specify external dependencies, add them to `peerDependencies` key in your package.json 168 | 169 | ```json 170 | "peerDependencies": { 171 | "react": ">=17", 172 | "react-dom": ">=17", 173 | "@mui/material": "^5.9.2" 174 | }, 175 | ``` 176 | 177 | Create React package already specifies `react` and `react-dom` as peer dependencies. 178 | 179 | ### Preview 180 | 181 | To preview and test your library before publishing, you can use: 182 | 183 | - [Integrated Playground](#integrated-playground) 184 | - [Storybook](https://storybook.js.org/) 185 | - [npm-link](https://docs.npmjs.com/cli/v8/commands/npm-link) with your React app 186 | 187 | Using Storybook with Create React Package is simple. Initialize a new project with Storybook using `--sb` or `--storybook` flag. 188 | 189 | ```sh 190 | npx create-react-pkg my-package --sb 191 | ``` 192 | 193 | Or, add Storybook to your existing project by running: 194 | 195 | ```sh 196 | npx storybook init 197 | ``` 198 | 199 | ### Build and Publish 200 | 201 | Create an optimized production build by running the `build` script. This will create a `dist` folder that may contain any or all of the folders based on your project setup and configuration: 202 | 203 | - cjs: Your library bundled in CommonJS format. 204 | - esm: Your library bundled in ECMAScript Modules format. 205 | - umd: Your library bundled in Universal Module Definition. 206 | - types: TypeScript declarations. 207 | - css: Minified CSS Bundle. 208 | 209 | > Note: If you use CJS as one of the module formats, it will create a file `dist/index.js` that loads CJS dev/prod builds based on the NodeJS environment. 210 | 211 | Create React Package adds the following NPM configuration to your package.json. 212 | 213 | ```json 214 | { 215 | "main": "dist/index.js (path to CJS build)", 216 | "module": "dist/esm/{your-package-name}.js (path to ES Module build)", 217 | "types": "dist/types/index.d.ts (path to TypeScript declarations)", 218 | "files": ["dist (files/folders that will be published to the NPM registry)"] 219 | } 220 | ``` 221 | 222 | This build can now be published to NPM. 223 | 224 | ## Integrated Playground 225 | 226 | Integrated playground is a React app development server that makes it significantly easier to build and view your React library in browser. 227 | 228 | To get started with the playground, first run: 229 | 230 | ```shell 231 | npm start 232 | ``` 233 | 234 | This builds your library to `/dist` and starts the project in watch mode so any edits you save inside `/src` causes a rebuild to `/dist`. 235 | 236 | Then run the playground inside another terminal: 237 | 238 | ```shell 239 | npm run preview 240 | ``` 241 | 242 | The playground imports and live reloads your library from `/dist`, so if you see an out-of-date component, make sure the project is running in watch mode with either ESM or CJS build, or both. **No symlinking required**. 243 | 244 |

245 | npm start 246 |

247 | 248 | > Tip: Use [`playground.rollupOptions`](#playgroundrollupoptions) config option for customizing the app bundle. For example, if you need to import images in your playground app. 249 | 250 | ### Specify port for Server 251 | 252 | By default, the server runs on port 10001, you can change it by creating a file `crp.config.js` at the root of package with the following. 253 | 254 | ```js 255 | const { defineConfig } = require('react-pkg-scripts'); 256 | 257 | module.exports = defineConfig({ 258 | playground: { 259 | server: { 260 | port: 3000, // define port of your choice 261 | }, 262 | }, 263 | }); 264 | ``` 265 | 266 | ### Use CJS build with Playground 267 | 268 | By default, the playground tries to use the ESM build of your library from `/dist`. But you can use the CJS build by removing the `module` property from package.json 269 | 270 | ```diff 271 | { 272 | "name": "foo", 273 | "main": "dist/index.js" 274 | - "module": "dist/esm/foo.js" 275 | } 276 | ``` 277 | 278 | Or you can point the module entry to CJS entry point 279 | 280 | ```diff 281 | { 282 | "name": "foo", 283 | "main": "dist/index.js" 284 | - "module": "dist/esm/foo.js" 285 | + "module": "dist/index.js" 286 | } 287 | ``` 288 | 289 | ## Philosophy 290 | 291 | Create React Package is divided into two packages: 292 | 293 | - `create-react-pkg` is a command line tool to set up a new React package. 294 | - `react-pkg-scripts` is a development dependency in the generated projects that encapsulates all the build tools. 295 | 296 | ## Customization 297 | 298 | Create React Package uses Rollup, Babel, Jest, and ESLint under the hood. These tools are pre-configured, and the default configuration is enough for most packages but you can customize them to your needs. 299 | 300 | > Customization can invalidate the default behavior of Create React Package. Please use with discretion. 301 | 302 | Create a file called `crp.config.js` at the root of your project like so: 303 | 304 | ```js 305 | const { defineConfig } = require('react-pkg-scripts'); 306 | 307 | module.exports = defineConfig({ 308 | // options 309 | }); 310 | ``` 311 | 312 | > Note: Create React Package does not support ES modules syntax in the config file, so use plain Node.js 313 | 314 | ### Config Intellisense 315 | 316 | Since Create React Package ships with TypeScript typings, you can leverage your IDE's IntelliSense with JSDoc type hints: 317 | 318 | ```js 319 | // crp.config.js 320 | 321 | /** 322 | * @type {import('react-pkg-scripts').UserConfig} 323 | */ 324 | const config = { 325 | // options 326 | }; 327 | 328 | module.exports = config; 329 | ``` 330 | 331 | Alternatively, you can use the `defineConfig` helper which should provide IntelliSense without the need for JSDoc annotations: 332 | 333 | ```js 334 | // crp.config.js 335 | 336 | const { defineConfig } = require('react-pkg-scripts'); 337 | 338 | module.exports = defineConfig({ 339 | // options 340 | }); 341 | ``` 342 | 343 | ### Config Options 344 | 345 | You can provide the following options to customize the build. 346 | 347 | #### input 348 | 349 | - **Type**: `string` 350 | - **Default**: `src/index` 351 | 352 | Entry point 353 | 354 | #### outDir 355 | 356 | - **Type**: `string` 357 | - **Default**: `dist` 358 | 359 | Directory relative from root where build output will be placed. If the directory exists, it will be removed before the build. 360 | 361 | #### formats 362 | 363 | - **Type**: `string[]` 364 | - **Default**: `['cjs', 'esm']` 365 | 366 | Bundle formats. Available formats are `cjs`, `esm`, and `umd` 367 | 368 | #### name 369 | 370 | - **Type**: `string` 371 | - **Default**: `camel-cased version of your package name` 372 | 373 | Name to expose in the UMD build. Use this option when you are using `umd` as one of the build formats. 374 | 375 | #### disableESLint 376 | 377 | - **Type**: `boolean` 378 | - **Default**: `false` 379 | 380 | Disable code linting with ESLint. 381 | 382 | #### babelHelpers 383 | 384 | - **Type**: `'bundled' | 'runtime'` 385 | - **Default**: `bundled` 386 | 387 | How Babel helpers are inserted into the Rollup bundle. If you select `runtime`, then you must add [@babel/runtime](https://www.npmjs.com/package/@babel/runtime) as a dependency to your package.json. 388 | 389 | > Note: Babel helpers for UMD module format are always bundled. 390 | 391 | #### rollupOptions 392 | 393 | - **Type**: `RollupOptions | ((config: RollupOptions, options) => RollupOptions)` 394 | 395 | Directly customize the underlying Rollup bundle. These options will be merged with Create React Package's internal Rollup options. See [Rollup options docs](https://rollupjs.org/guide/en/#big-list-of-options) for more details. 396 | 397 | #### playground.server 398 | 399 | - **Type**: `RollupServeOptions` 400 | 401 | Development server configuration. See options [here](https://github.com/thgh/rollup-plugin-serve#options) 402 | 403 | #### playground.livereload 404 | 405 | - **Type**: `RollupLivereloadOptions` 406 | 407 | Development server livereload configuration. See options [here](https://github.com/thgh/rollup-plugin-livereload#options) 408 | 409 | #### playground.rollupOptions 410 | 411 | - **Type**: `RollupOptions | ((config: RollupOptions) => RollupOptions)` 412 | 413 | Rollup options for playground app bundle. See [Rollup options docs](https://rollupjs.org/guide/en/#big-list-of-options) for more details. 414 | 415 | ### Rollup 416 | 417 | Create React Package uses Rollup to bundle your library. To customize the rollup configuration, create a file `crp.config.js` at the root of your package and pass any rollup options. 418 | 419 | ```js 420 | const { defineConfig } = require('react-pkg-scripts'); 421 | 422 | module.exports = defineConfig({ 423 | rollupOptions: { 424 | // rollup options 425 | }, 426 | }); 427 | ``` 428 | 429 | #### Conditional Rollup Config 430 | 431 | If the config needs to conditionally determine options based on the module format or the mode being used, pass a function to `rollupOptions`. 432 | 433 | ```js 434 | const { defineConfig } = require('react-pkg-scripts'); 435 | 436 | module.exports = defineConfig({ 437 | rollupOptions: (config, { format, mode }) => { 438 | if (format === 'cjs' && mode === 'production') { 439 | // config options only for the CJS Production build 440 | } 441 | return config; 442 | }, 443 | }); 444 | ``` 445 | 446 | #### Example: Import images 447 | 448 | To import and ship your package with JPG, PNG, GIF, SVG, and WebP files, use [@rollup/plugin-image](https://www.npmjs.com/package/@rollup/plugin-image). First, install it as a dev dependency 449 | 450 | ```sh 451 | npm i -D @rollup/plugin-image 452 | ``` 453 | 454 | And use it in the `crp.config.js` 455 | 456 | ```js 457 | const { defineConfig } = require('react-pkg-scripts'); 458 | const images = require('@rollup/plugin-image'); 459 | 460 | module.exports = defineConfig({ 461 | rollupOptions: { 462 | plugins: [images()], 463 | }, 464 | }); 465 | ``` 466 | 467 | Now, you can import images like 468 | 469 | ```jsx 470 | import React from 'react'; 471 | import image from './image.png'; 472 | 473 | return ; 474 | ``` 475 | 476 | > Note: If you are using TypeScript, create a folder and file `types/index.d.ts` at the root of your project with the following to make it work with TypeScript compiler. 477 | 478 | ```ts 479 | declare module '*.png'; 480 | declare module '*.jpg'; 481 | ``` 482 | 483 | ### Babel 484 | 485 | Create React Package respects [Babel configuration files](https://babeljs.io/docs/en/config-files). 486 | 487 | #### Example: Optimize Lodash 488 | 489 | If you use a lodash function in your library like `import { cloneDeep } from 'lodash'` the compiled bundle will contain all of the lodash's library. 490 | 491 | Ideally, your compiled bundle should only contain what you use in the source of your library. Create React Package helps you do that with some Babel configuration. 492 | 493 | Install [lodash](https://www.npmjs.com/package/lodash) and [babel-plugin-import](https://www.npmjs.com/package/babel-plugin-import) in your package. 494 | 495 | ```sh 496 | npm i lodash 497 | npm i -D babel-plugin-import 498 | ``` 499 | 500 | Create a file `.babelrc` at the root of your project with the following. 501 | 502 | ```json 503 | { 504 | "plugins": [ 505 | [ 506 | "import", 507 | { 508 | "libraryName": "lodash", 509 | "libraryDirectory": "", 510 | "camel2DashComponentName": false 511 | } 512 | ] 513 | ] 514 | } 515 | ``` 516 | 517 | This Babel configuration will be merged with Create React Package's internal config. Now, your bundle will not include all lodash functions, just the functions you import into your project. 518 | 519 | ### ESLint 520 | 521 | Create React Package respects [ESLint configuration files](https://eslint.org/docs/latest/user-guide/configuring/configuration-files). 522 | 523 | To disable linting, pass `disableESLint: true` option to `crp.config.js` 524 | 525 | ```js 526 | const { defineConfig } = require('react-pkg-scripts'); 527 | 528 | module.exports = defineConfig({ 529 | disableESLint: true, 530 | }); 531 | ``` 532 | 533 | To ignore any files, create a `.eslintignore` file at the root of the package. 534 | 535 | ### Jest 536 | 537 | Create React Package executes the following files with Jest test runner: 538 | 539 | - Files with `.js` suffix in `__tests__` folders. (under any level, not only in src) 540 | - Files with `.test.js` suffix. 541 | - Files with `.spec.js` suffix. 542 | 543 | > Note: `.js`, `.jsx`, `.ts`, `.tsx` file extensions are supported. 544 | 545 | You can override Create React Package's [default Jest configuration](https://github.com/haseebanwar/create-react-pkg/blob/master/packages/react-pkg-scripts/src/scripts/test.js) by adding any of the [Jest Options](https://jestjs.io/docs/27.x/configuration#options) to package.json. 546 | 547 | Example package.json 548 | 549 | ```json 550 | { 551 | "name": "your-package", 552 | "jest": { 553 | "collectCoverage": true, 554 | "collectCoverageFrom": [ 555 | "**/*.{js,jsx}", 556 | "!**/node_modules/**", 557 | "!**/vendor/**" 558 | ] 559 | } 560 | } 561 | ``` 562 | 563 | Note that this config is shallow merged. 564 | 565 | #### CLI Options 566 | 567 | You can pass [Jest CLI options](https://jestjs.io/docs/27.x/cli) to the `test` script in your package.json. 568 | 569 | ```diff 570 | "scripts": { 571 | - "test": "react-pkg-scripts test" 572 | + "test": "react-pkg-scripts test --watchAll" 573 | } 574 | ``` 575 | 576 | ## Styling 577 | 578 | Create React Package lets you ship your package with CSS assets. You can import stylesheets in your JavaScript files straight away without doing any additional setup. 579 | 580 | ```css 581 | /* Button.css */ 582 | 583 | .Button { 584 | padding: 15px; 585 | } 586 | ``` 587 | 588 | And then in your JavaScript file 589 | 590 | ```jsx 591 | // Button.js 592 | 593 | import React from 'react'; 594 | import './Button.css'; 595 | 596 | const Button = () => { 597 | return
; 598 | }; 599 | ``` 600 | 601 | Create React Package concatenates all your stylesheets into a single minified `.css` file and places it in the build output. 602 | 603 | > Tip: CSS Modules are also supported. 604 | 605 | ### Sass/Stylus/Less Files 606 | 607 | - For Sass, install [sass](https://www.npmjs.com/package/sass): `npm i -D sass` 608 | - For Stylus, install [stylus](https://www.npmjs.com/package/stylus): `npm i -D stylus` 609 | - For Less, install [less](https://www.npmjs.com/package/less): `npm i -D less` 610 | 611 | That's it, you can now import `.styl` `.scss` `.sass` `.less` files in your project. 612 | 613 | ### Post-Processing CSS 614 | 615 | This project uses [rollup-plugin-postcss 616 | ](https://www.npmjs.com/package/rollup-plugin-postcss) that integrates Rollup with [PostCSS](https://github.com/postcss/postcss). In addition to that, it adds vendor prefixes automatically to the bundled CSS through [Autoprefixer](https://github.com/postcss/autoprefixer) so you don’t need to worry about it. 617 | 618 | You can customize your target support browsers by adjusting the `browserslist` key in package.json. You can read more about Browserslist configuration [here](#configure-supported-browsers). 619 | 620 | For example, this: 621 | 622 | ```css 623 | div { 624 | user-select: none; 625 | } 626 | ``` 627 | 628 | is transformed into this: 629 | 630 | ```css 631 | div { 632 | -webkit-user-select: none; 633 | user-select: none; 634 | } 635 | ``` 636 | 637 | If you need to disable autoprefixing, follow [autoprefixer disabling section](https://github.com/postcss/autoprefixer#disabling). 638 | 639 | ## Advanced Usage 640 | 641 | ### Code Splitting 642 | 643 | It is recommended that you do code splitting in your app and not in the library. But if you still need to code split your library for some reason, Create React Package got your back. 644 | 645 | This project supports code splitting via dynamic `import()`. 646 | 647 | For example: 648 | 649 | ```js 650 | // greeting.js 651 | 652 | const greeting = 'Hi there!'; 653 | 654 | export { greeting }; 655 | ``` 656 | 657 | ```jsx 658 | // index.js 659 | 660 | import React from 'react'; 661 | 662 | const MyComponent = () => { 663 | const handleClick = async () => { 664 | try { 665 | const { greeting } = await import('./greeting'); 666 | console.log(greeting); // prints: Hi there! 667 | } catch (error) { 668 | // handle failure 669 | } 670 | }; 671 | 672 | return ; 673 | }; 674 | ``` 675 | 676 | You can also use React `lazy` and `Suspense` to load components lazily. 677 | 678 | > Note: Code-splitting is not supported for UMD format. 679 | 680 | ### Configure Supported Browsers 681 | 682 | Create React Package uses [Browserslist](https://github.com/browserslist/browserslist) to target a broad range of browsers. By default, the generated project includes the following Browserslist configuration in package.json. 683 | 684 | ```json 685 | "browserslist": { 686 | "production": [ 687 | ">0.2%", 688 | "not dead", 689 | "not op_mini all" 690 | ], 691 | "development": [ 692 | "last 1 chrome version", 693 | "last 1 firefox version", 694 | "last 1 safari version" 695 | ] 696 | }, 697 | ``` 698 | 699 | The `browserslist` configuration controls the outputted JavaScript and CSS so that the emitted code will be compatible with the browsers specified. The `production` list will be used when creating a production build with the `build` script, and the `development` list will be used with the `watch` script. 700 | 701 | You can adjust this configuration according to the [Browserslist specification](https://github.com/browserslist/browserslist#readme). 702 | 703 | > Note: This configuration does not include polyfills automatically. 704 | 705 | ## Author 706 | 707 | - [Haseeb Anwar](https://haseebanwar.net/) 708 | 709 | ## License 710 | 711 | Create React Package is open-source software [licensed as MIT](https://github.com/haseebanwar/create-react-pkg/blob/master/LICENSE). 712 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": { 3 | "node": 14 4 | }, 5 | "presets": ["@babel/preset-env"], 6 | "plugins": ["@babel/transform-runtime"] 7 | } 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | testMatch: ['/**/*.test.js'], 4 | testPathIgnorePatterns: ['/templates/', '/node_modules/'], 5 | }; 6 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "independent" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "start": "lerna run start --stream", 5 | "build": "lerna run build", 6 | "test": "jest", 7 | "lint": "eslint packages", 8 | "release": "lerna publish" 9 | }, 10 | "devDependencies": { 11 | "@babel/cli": "^7.19.3", 12 | "@babel/core": "^7.19.3", 13 | "@babel/plugin-transform-runtime": "^7.19.1", 14 | "@babel/preset-env": "^7.19.4", 15 | "babel-jest": "^27.5.1", 16 | "eslint": "^8.25.0", 17 | "jest": "^27.5.1", 18 | "lerna": "^6.0.1", 19 | "rimraf": "^3.0.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/create-react-pkg/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-present Haseeb Anwar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/create-react-pkg/README.md: -------------------------------------------------------------------------------- 1 | # create-react-pkg 2 | 3 | This package includes the global command for [Create React Package](https://github.com/haseebanwar/create-react-pkg). A zero-config CLI for creating react packages. 4 | 5 | Please refer to its documentation: 6 | 7 | - [Getting Started](https://github.com/haseebanwar/create-react-pkg) – How to create a new package. 8 | - [User Guide](https://github.com/haseebanwar/create-react-pkg) – How to develop packages bootstrapped with Create React Package. 9 | -------------------------------------------------------------------------------- /packages/create-react-pkg/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-react-pkg", 3 | "version": "1.0.6", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "create-react-pkg", 9 | "version": "1.0.6", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@babel/runtime": "^7.19.4", 13 | "chalk": "^4.1.2", 14 | "commander": "^9.0.0", 15 | "cross-spawn": "^7.0.3", 16 | "fs-extra": "^10.1.0", 17 | "prompts": "^2.4.2", 18 | "semver": "^7.3.8", 19 | "validate-npm-package-name": "^4.0.0" 20 | }, 21 | "bin": { 22 | "create-react-pkg": "lib/index.js" 23 | }, 24 | "engines": { 25 | "node": ">=14.17.0" 26 | } 27 | }, 28 | "node_modules/@babel/runtime": { 29 | "version": "7.19.4", 30 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.4.tgz", 31 | "integrity": "sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA==", 32 | "dependencies": { 33 | "regenerator-runtime": "^0.13.4" 34 | }, 35 | "engines": { 36 | "node": ">=6.9.0" 37 | } 38 | }, 39 | "node_modules/ansi-styles": { 40 | "version": "4.3.0", 41 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 42 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 43 | "dependencies": { 44 | "color-convert": "^2.0.1" 45 | }, 46 | "engines": { 47 | "node": ">=8" 48 | }, 49 | "funding": { 50 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 51 | } 52 | }, 53 | "node_modules/builtins": { 54 | "version": "5.0.1", 55 | "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", 56 | "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", 57 | "dependencies": { 58 | "semver": "^7.0.0" 59 | } 60 | }, 61 | "node_modules/chalk": { 62 | "version": "4.1.2", 63 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 64 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 65 | "dependencies": { 66 | "ansi-styles": "^4.1.0", 67 | "supports-color": "^7.1.0" 68 | }, 69 | "engines": { 70 | "node": ">=10" 71 | }, 72 | "funding": { 73 | "url": "https://github.com/chalk/chalk?sponsor=1" 74 | } 75 | }, 76 | "node_modules/color-convert": { 77 | "version": "2.0.1", 78 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 79 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 80 | "dependencies": { 81 | "color-name": "~1.1.4" 82 | }, 83 | "engines": { 84 | "node": ">=7.0.0" 85 | } 86 | }, 87 | "node_modules/color-name": { 88 | "version": "1.1.4", 89 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 90 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 91 | }, 92 | "node_modules/commander": { 93 | "version": "9.3.0", 94 | "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz", 95 | "integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==", 96 | "engines": { 97 | "node": "^12.20.0 || >=14" 98 | } 99 | }, 100 | "node_modules/cross-spawn": { 101 | "version": "7.0.3", 102 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 103 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 104 | "dependencies": { 105 | "path-key": "^3.1.0", 106 | "shebang-command": "^2.0.0", 107 | "which": "^2.0.1" 108 | }, 109 | "engines": { 110 | "node": ">= 8" 111 | } 112 | }, 113 | "node_modules/fs-extra": { 114 | "version": "10.1.0", 115 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", 116 | "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", 117 | "dependencies": { 118 | "graceful-fs": "^4.2.0", 119 | "jsonfile": "^6.0.1", 120 | "universalify": "^2.0.0" 121 | }, 122 | "engines": { 123 | "node": ">=12" 124 | } 125 | }, 126 | "node_modules/graceful-fs": { 127 | "version": "4.2.10", 128 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", 129 | "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" 130 | }, 131 | "node_modules/has-flag": { 132 | "version": "4.0.0", 133 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 134 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 135 | "engines": { 136 | "node": ">=8" 137 | } 138 | }, 139 | "node_modules/isexe": { 140 | "version": "2.0.0", 141 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 142 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" 143 | }, 144 | "node_modules/jsonfile": { 145 | "version": "6.1.0", 146 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", 147 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", 148 | "dependencies": { 149 | "universalify": "^2.0.0" 150 | }, 151 | "optionalDependencies": { 152 | "graceful-fs": "^4.1.6" 153 | } 154 | }, 155 | "node_modules/kleur": { 156 | "version": "3.0.3", 157 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", 158 | "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", 159 | "engines": { 160 | "node": ">=6" 161 | } 162 | }, 163 | "node_modules/lru-cache": { 164 | "version": "6.0.0", 165 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 166 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 167 | "dependencies": { 168 | "yallist": "^4.0.0" 169 | }, 170 | "engines": { 171 | "node": ">=10" 172 | } 173 | }, 174 | "node_modules/path-key": { 175 | "version": "3.1.1", 176 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 177 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 178 | "engines": { 179 | "node": ">=8" 180 | } 181 | }, 182 | "node_modules/prompts": { 183 | "version": "2.4.2", 184 | "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", 185 | "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", 186 | "dependencies": { 187 | "kleur": "^3.0.3", 188 | "sisteransi": "^1.0.5" 189 | }, 190 | "engines": { 191 | "node": ">= 6" 192 | } 193 | }, 194 | "node_modules/regenerator-runtime": { 195 | "version": "0.13.9", 196 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", 197 | "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" 198 | }, 199 | "node_modules/semver": { 200 | "version": "7.3.8", 201 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", 202 | "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", 203 | "dependencies": { 204 | "lru-cache": "^6.0.0" 205 | }, 206 | "bin": { 207 | "semver": "bin/semver.js" 208 | }, 209 | "engines": { 210 | "node": ">=10" 211 | } 212 | }, 213 | "node_modules/shebang-command": { 214 | "version": "2.0.0", 215 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 216 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 217 | "dependencies": { 218 | "shebang-regex": "^3.0.0" 219 | }, 220 | "engines": { 221 | "node": ">=8" 222 | } 223 | }, 224 | "node_modules/shebang-regex": { 225 | "version": "3.0.0", 226 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 227 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 228 | "engines": { 229 | "node": ">=8" 230 | } 231 | }, 232 | "node_modules/sisteransi": { 233 | "version": "1.0.5", 234 | "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", 235 | "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" 236 | }, 237 | "node_modules/supports-color": { 238 | "version": "7.2.0", 239 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 240 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 241 | "dependencies": { 242 | "has-flag": "^4.0.0" 243 | }, 244 | "engines": { 245 | "node": ">=8" 246 | } 247 | }, 248 | "node_modules/universalify": { 249 | "version": "2.0.0", 250 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", 251 | "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", 252 | "engines": { 253 | "node": ">= 10.0.0" 254 | } 255 | }, 256 | "node_modules/validate-npm-package-name": { 257 | "version": "4.0.0", 258 | "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz", 259 | "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==", 260 | "dependencies": { 261 | "builtins": "^5.0.0" 262 | }, 263 | "engines": { 264 | "node": "^12.13.0 || ^14.15.0 || >=16.0.0" 265 | } 266 | }, 267 | "node_modules/which": { 268 | "version": "2.0.2", 269 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 270 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 271 | "dependencies": { 272 | "isexe": "^2.0.0" 273 | }, 274 | "bin": { 275 | "node-which": "bin/node-which" 276 | }, 277 | "engines": { 278 | "node": ">= 8" 279 | } 280 | }, 281 | "node_modules/yallist": { 282 | "version": "4.0.0", 283 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 284 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 285 | } 286 | }, 287 | "dependencies": { 288 | "@babel/runtime": { 289 | "version": "7.19.4", 290 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.4.tgz", 291 | "integrity": "sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA==", 292 | "requires": { 293 | "regenerator-runtime": "^0.13.4" 294 | } 295 | }, 296 | "ansi-styles": { 297 | "version": "4.3.0", 298 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 299 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 300 | "requires": { 301 | "color-convert": "^2.0.1" 302 | } 303 | }, 304 | "builtins": { 305 | "version": "5.0.1", 306 | "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", 307 | "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", 308 | "requires": { 309 | "semver": "^7.0.0" 310 | } 311 | }, 312 | "chalk": { 313 | "version": "4.1.2", 314 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 315 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 316 | "requires": { 317 | "ansi-styles": "^4.1.0", 318 | "supports-color": "^7.1.0" 319 | } 320 | }, 321 | "color-convert": { 322 | "version": "2.0.1", 323 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 324 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 325 | "requires": { 326 | "color-name": "~1.1.4" 327 | } 328 | }, 329 | "color-name": { 330 | "version": "1.1.4", 331 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 332 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 333 | }, 334 | "commander": { 335 | "version": "9.3.0", 336 | "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz", 337 | "integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==" 338 | }, 339 | "cross-spawn": { 340 | "version": "7.0.3", 341 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 342 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 343 | "requires": { 344 | "path-key": "^3.1.0", 345 | "shebang-command": "^2.0.0", 346 | "which": "^2.0.1" 347 | } 348 | }, 349 | "fs-extra": { 350 | "version": "10.1.0", 351 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", 352 | "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", 353 | "requires": { 354 | "graceful-fs": "^4.2.0", 355 | "jsonfile": "^6.0.1", 356 | "universalify": "^2.0.0" 357 | } 358 | }, 359 | "graceful-fs": { 360 | "version": "4.2.10", 361 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", 362 | "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" 363 | }, 364 | "has-flag": { 365 | "version": "4.0.0", 366 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 367 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 368 | }, 369 | "isexe": { 370 | "version": "2.0.0", 371 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 372 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" 373 | }, 374 | "jsonfile": { 375 | "version": "6.1.0", 376 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", 377 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", 378 | "requires": { 379 | "graceful-fs": "^4.1.6", 380 | "universalify": "^2.0.0" 381 | } 382 | }, 383 | "kleur": { 384 | "version": "3.0.3", 385 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", 386 | "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" 387 | }, 388 | "lru-cache": { 389 | "version": "6.0.0", 390 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 391 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 392 | "requires": { 393 | "yallist": "^4.0.0" 394 | } 395 | }, 396 | "path-key": { 397 | "version": "3.1.1", 398 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 399 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" 400 | }, 401 | "prompts": { 402 | "version": "2.4.2", 403 | "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", 404 | "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", 405 | "requires": { 406 | "kleur": "^3.0.3", 407 | "sisteransi": "^1.0.5" 408 | } 409 | }, 410 | "regenerator-runtime": { 411 | "version": "0.13.9", 412 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", 413 | "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" 414 | }, 415 | "semver": { 416 | "version": "7.3.8", 417 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", 418 | "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", 419 | "requires": { 420 | "lru-cache": "^6.0.0" 421 | } 422 | }, 423 | "shebang-command": { 424 | "version": "2.0.0", 425 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 426 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 427 | "requires": { 428 | "shebang-regex": "^3.0.0" 429 | } 430 | }, 431 | "shebang-regex": { 432 | "version": "3.0.0", 433 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 434 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" 435 | }, 436 | "sisteransi": { 437 | "version": "1.0.5", 438 | "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", 439 | "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" 440 | }, 441 | "supports-color": { 442 | "version": "7.2.0", 443 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 444 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 445 | "requires": { 446 | "has-flag": "^4.0.0" 447 | } 448 | }, 449 | "universalify": { 450 | "version": "2.0.0", 451 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", 452 | "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" 453 | }, 454 | "validate-npm-package-name": { 455 | "version": "4.0.0", 456 | "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz", 457 | "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==", 458 | "requires": { 459 | "builtins": "^5.0.0" 460 | } 461 | }, 462 | "which": { 463 | "version": "2.0.2", 464 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 465 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 466 | "requires": { 467 | "isexe": "^2.0.0" 468 | } 469 | }, 470 | "yallist": { 471 | "version": "4.0.0", 472 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 473 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 474 | } 475 | } 476 | } 477 | -------------------------------------------------------------------------------- /packages/create-react-pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-react-pkg", 3 | "version": "1.0.6", 4 | "description": "Zero config CLI for creating react packages", 5 | "keywords": [ 6 | "react", 7 | "cli", 8 | "package", 9 | "library", 10 | "rollup" 11 | ], 12 | "homepage": "https://github.com/haseebanwar/create-react-pkg/tree/master/packages/create-react-pkg", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/haseebanwar/create-react-pkg.git", 16 | "directory": "packages/create-react-pkg" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/haseebanwar/create-react-pkg/issues" 20 | }, 21 | "author": "Haseeb Anwar (https://haseebanwar.net/)", 22 | "license": "MIT", 23 | "bin": { 24 | "create-react-pkg": "lib/index.js" 25 | }, 26 | "files": [ 27 | "lib", 28 | "templates" 29 | ], 30 | "engines": { 31 | "node": ">=14.17.0" 32 | }, 33 | "scripts": { 34 | "start": "npm run clean && babel src -d lib --root-mode upward --watch", 35 | "build": "npm run clean && babel src -d lib --root-mode upward", 36 | "prepublishOnly": "npm run build", 37 | "clean": "rimraf lib" 38 | }, 39 | "dependencies": { 40 | "@babel/runtime": "^7.19.4", 41 | "chalk": "^4.1.2", 42 | "commander": "^9.0.0", 43 | "cross-spawn": "^7.0.3", 44 | "fs-extra": "^10.1.0", 45 | "prompts": "^2.4.2", 46 | "semver": "^7.3.8", 47 | "validate-npm-package-name": "^4.0.0" 48 | }, 49 | "gitHead": "8d5e7ac1c028829aa70e4ae1256141679b21167b" 50 | } 51 | -------------------------------------------------------------------------------- /packages/create-react-pkg/src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import path from 'path'; 4 | import semver from 'semver'; 5 | import fs from 'fs-extra'; 6 | import { program } from 'commander'; 7 | import prompts from 'prompts'; 8 | import chalk from 'chalk'; 9 | import validatePackageName from 'validate-npm-package-name'; 10 | import { 11 | checkForLatestVersion, 12 | getAuthorName, 13 | composePackageJSON, 14 | isUsingYarn, 15 | makePackageDeps, 16 | makeInstallArgs, 17 | getTemplateName, 18 | sanitizePackageName, 19 | executeInstallCommand, 20 | getNPMVersion, 21 | setLegacyPeerDeps, 22 | } from './utils'; 23 | import packageJSON from '../package.json'; 24 | 25 | program.name(packageJSON.name); 26 | program.version(packageJSON.version); 27 | 28 | program 29 | .argument('[package-directory]') 30 | .usage(`${chalk.green('')} [options]`) 31 | .option('--ts, --typescript', 'initialize a typescript package') 32 | .option('--sb, --storybook', 'add storybook') 33 | .action(async (projectDirectory, flags) => { 34 | try { 35 | // prompt if package directory is not specified 36 | if (!projectDirectory) { 37 | const projectDirectoryInput = await prompts( 38 | { 39 | type: 'text', 40 | name: 'projectDirectory', 41 | message: 'What is your package named?', 42 | initial: 'my-package', 43 | }, 44 | { 45 | onCancel: () => { 46 | console.log('Please specify the package directory'); 47 | console.log( 48 | ` ${chalk.cyan(program.name())} ${chalk.green( 49 | `` 50 | )}` 51 | ); 52 | 53 | console.log('\nFor example:'); 54 | console.log( 55 | ` ${chalk.cyan(program.name())} ${chalk.green(`my-package`)}` 56 | ); 57 | 58 | console.log( 59 | `\nRun ${chalk.cyan( 60 | `${program.name()} --help` 61 | )} to see all options.` 62 | ); 63 | 64 | process.exit(1); 65 | }, 66 | } 67 | ); 68 | projectDirectory = projectDirectoryInput.projectDirectory; 69 | } 70 | 71 | // check if user is on non-supported node version 72 | const currentNodeVersion = process.versions.node; 73 | const packageNodeVersion = packageJSON.engines.node; 74 | if (!semver.satisfies(currentNodeVersion, packageNodeVersion)) { 75 | console.error( 76 | `You are running Node ${currentNodeVersion}.\nCreate React Package requires Node ${ 77 | semver.minVersion(packageNodeVersion).version 78 | } or higher.\nPlease update your version of Node.` 79 | ); 80 | process.exit(1); 81 | } 82 | 83 | const { storybook, typescript } = flags; 84 | 85 | const projectPath = path.resolve(projectDirectory); 86 | const packageName = sanitizePackageName(path.basename(projectPath)); 87 | 88 | // validate package name 89 | const { 90 | validForNewPackages, 91 | errors: packageNameErrors, 92 | warnings: packageNameWarnings, 93 | } = validatePackageName(packageName); 94 | 95 | if (!validForNewPackages) { 96 | console.error( 97 | chalk.red(`Invalid package name ${chalk.cyan(`"${packageName}"`)}`) 98 | ); 99 | 100 | [...(packageNameErrors || []), ...(packageNameWarnings || [])].forEach( 101 | (error) => { 102 | console.log(chalk.red(` - ${error}`)); 103 | } 104 | ); 105 | console.log('\nPlease use a different package name'); 106 | process.exit(1); 107 | } 108 | 109 | // create package directory if it doesn't exist 110 | fs.ensureDirSync(projectPath); 111 | 112 | // throw an error if package folder contains files except valid files 113 | const validFiles = [ 114 | '.DS_Store', 115 | '.git', 116 | '.gitattributes', 117 | '.gitignore', 118 | '.gitlab-ci.yml', 119 | '.hg', 120 | '.hgcheck', 121 | '.hgignore', 122 | '.idea', 123 | '.npmignore', 124 | '.travis.yml', 125 | 'docs', 126 | 'mkdocs.yml', 127 | 'Thumbs.db', 128 | ]; 129 | const files = fs 130 | .readdirSync(projectPath) 131 | .filter((file) => !validFiles.includes(file)); 132 | if (files.length) { 133 | console.error( 134 | chalk.red( 135 | `Please make sure that your package directory ${chalk.cyan( 136 | `"${packageName}"` 137 | )} is empty.\nRemove any hidden directories/files as well.` 138 | ) 139 | ); 140 | process.exit(1); 141 | } 142 | 143 | // check if user is creating a new package with an old version of CLI 144 | try { 145 | const latestVersionOfCLI = await checkForLatestVersion(); 146 | 147 | if ( 148 | latestVersionOfCLI && 149 | semver.lt(packageJSON.version, latestVersionOfCLI) 150 | ) { 151 | console.error( 152 | chalk.red( 153 | `You are running \`create-react-pkg\` ${chalk.cyan( 154 | packageJSON.version 155 | )} which is behind the latest release ${chalk.cyan( 156 | latestVersionOfCLI 157 | )}` 158 | ) 159 | ); 160 | console.log( 161 | '\nPlease remove any global installs with one of the following commands:\n' + 162 | `- npm uninstall -g ${packageJSON.name}\n` + 163 | `- yarn global remove ${packageJSON.name}` 164 | ); 165 | console.log( 166 | '\nThe latest instructions for creating a new package can be found here:\n' + 167 | 'https://github.com/haseebanwar/create-react-pkg#getting-started' 168 | ); 169 | process.exit(1); 170 | } 171 | } catch (error) { 172 | // ignore and let user continue with old version 173 | } 174 | 175 | // start creation of package 176 | console.log( 177 | `\nCreating a new package ${chalk.green(packageName)} in ${chalk.green( 178 | projectPath 179 | )}` 180 | ); 181 | 182 | const template = getTemplateName(typescript, storybook); 183 | 184 | // copy the template 185 | await fs.copy( 186 | path.resolve(__dirname, `../templates/${template}`), 187 | projectPath 188 | ); 189 | 190 | // copy base files 191 | await fs.copy( 192 | path.resolve(__dirname, '../templates/baseFiles'), 193 | projectPath 194 | ); 195 | 196 | // fix gitignore 197 | await fs.move( 198 | path.resolve(projectPath, './gitignore'), 199 | path.resolve(projectPath, './.gitignore') 200 | ); 201 | 202 | // get author name 203 | let author = getAuthorName(); 204 | 205 | // prompt to get author name if not present 206 | if (!author) { 207 | const authorInput = await prompts({ 208 | type: 'text', 209 | name: 'author', 210 | message: 'Package author', 211 | }); 212 | 213 | author = authorInput.author; 214 | } 215 | 216 | // fix license 217 | const licensePath = path.resolve(projectPath, 'LICENSE'); 218 | let license = fs.readFileSync(licensePath, { encoding: 'utf-8' }); 219 | 220 | license = license.replace(/\[year\]/g, new Date().getFullYear()); 221 | license = license.replace(/\[author\]/g, author); 222 | fs.writeFileSync(licensePath, license, { encoding: 'utf-8' }); 223 | 224 | // generate package.json 225 | const pkg = composePackageJSON( 226 | packageName, 227 | author, 228 | typescript, 229 | storybook 230 | ); 231 | fs.outputJSONSync(path.resolve(projectPath, 'package.json'), pkg, { 232 | spaces: 2, 233 | }); 234 | 235 | // decide whether to use npm or yarn for installing deps 236 | const useYarn = isUsingYarn(); 237 | const packageManager = useYarn ? 'yarn' : 'npm'; 238 | 239 | // if user is setting up storybook with npm v7+, use legacy peer deps until storybook 7 is released 240 | // more https://github.com/storybookjs/storybook/issues/18298#issuecomment-1136158953 241 | let useLegacyPeerDeps = false; 242 | let setNPMRCForLegacyPeerDeps = false; 243 | if (packageManager === 'npm' && storybook) { 244 | const npmVersion = getNPMVersion(); 245 | 246 | if (semver.gte(npmVersion, '7.0.0')) { 247 | // use legacy peer deps (for this install) even if user says no to the prompt below 248 | useLegacyPeerDeps = true; 249 | 250 | console.log( 251 | `\nWe've detected you are running npm version ${chalk.cyan( 252 | npmVersion 253 | )} which has peer dependency semantics which Storybook is incompatible with.` 254 | ); 255 | console.log( 256 | `In order to work with Storybook's package structure, you'll need to run \`npm\` with the \`--legacy-peer-deps=true\` flag` 257 | ); 258 | console.log( 259 | `\nMore info: ${chalk.yellow( 260 | 'https://github.com/storybookjs/storybook/issues/18298' 261 | )}\n` 262 | ); 263 | 264 | const legacyPeerDepsInput = await prompts({ 265 | type: 'confirm', 266 | name: 'setNPMRCForLegacyPeerDeps', 267 | message: `Generate an \`.npmrc\` to run \`npm\` with the \`--legacy-peer-deps=true\` flag?`, 268 | initial: true, 269 | }); 270 | 271 | setNPMRCForLegacyPeerDeps = 272 | legacyPeerDepsInput.setNPMRCForLegacyPeerDeps; 273 | } 274 | } 275 | 276 | console.log( 277 | '\nInstalling dependencies. This might take a couple of minutes.' 278 | ); 279 | console.log( 280 | `Installing ${chalk.cyan('react')}, ${chalk.cyan( 281 | 'react-dom' 282 | )}, and ${chalk.cyan('react-pkg-scripts')}${ 283 | typescript || storybook ? ' with' : '' 284 | }` 285 | ); 286 | typescript && console.log(`- ${chalk.cyan('typescript')}`); 287 | storybook && console.log(`- ${chalk.cyan('storybook')}`); 288 | useYarn && console.log(''); // line break for yarn only 289 | 290 | // install deps 291 | const dependencies = makePackageDeps(typescript, storybook); 292 | process.chdir(projectPath); 293 | 294 | if (setNPMRCForLegacyPeerDeps) { 295 | setLegacyPeerDeps(); 296 | } 297 | 298 | const installArgs = makeInstallArgs( 299 | packageManager, 300 | dependencies, 301 | useLegacyPeerDeps 302 | ); 303 | await executeInstallCommand(packageManager, installArgs); 304 | 305 | console.log('\nInstalled dependencies'); 306 | 307 | console.log(`\nSuccess! Created ${packageName} at ${projectPath}`); 308 | console.log( 309 | 'Inside that directory, you can run the following commands:\n' 310 | ); 311 | 312 | console.log(chalk.cyan(` ${packageManager} start`)); 313 | console.log(' Watches for changes as you build.\n'); 314 | 315 | console.log( 316 | chalk.cyan( 317 | ` ${packageManager}${packageManager === 'npm' ? ' run' : ''} build` 318 | ) 319 | ); 320 | console.log(' Creates an optimized production build.\n'); 321 | 322 | console.log(chalk.cyan(` ${packageManager} test`)); 323 | console.log(' Runs tests with Jest.\n'); 324 | 325 | console.log('Build Something Great!'); 326 | 327 | process.exit(0); 328 | } catch (error) { 329 | console.error(chalk.red(`Failed to create package: ${error.message}`)); 330 | console.log('error', error); 331 | process.exit(1); 332 | } 333 | }); 334 | 335 | program.parse(process.argv); 336 | -------------------------------------------------------------------------------- /packages/create-react-pkg/src/pkgTemplate.js: -------------------------------------------------------------------------------- 1 | // name and author fields will be dynamic 2 | export const basePackageJSON = { 3 | version: '0.1.0', 4 | description: '', 5 | files: ['dist'], 6 | license: 'MIT', 7 | scripts: { 8 | start: `react-pkg-scripts watch`, 9 | build: `react-pkg-scripts build`, 10 | test: `react-pkg-scripts test`, 11 | preview: `react-pkg-scripts preview`, 12 | }, 13 | peerDependencies: { 14 | react: '>=17', 15 | 'react-dom': '>=17', 16 | }, 17 | peerDependenciesMeta: { 18 | 'react-dom': { 19 | optional: true, 20 | }, 21 | }, 22 | keywords: ['react'], 23 | eslintConfig: { 24 | extends: ['react-app', 'react-app/jest'], 25 | }, 26 | browserslist: { 27 | production: ['>0.2%', 'not dead', 'not op_mini all'], 28 | development: [ 29 | 'last 1 chrome version', 30 | 'last 1 firefox version', 31 | 'last 1 safari version', 32 | ], 33 | }, 34 | }; 35 | 36 | export const dependencies = ['react-pkg-scripts', 'react', 'react-dom']; 37 | export const tsDependencies = [ 38 | '@types/react', 39 | '@types/react-dom', 40 | '@types/jest@^27', 41 | '@types/node', 42 | // TODO: remove hard-coded version when this is fixed 43 | // https://github.com/facebook/create-react-app/issues/12150 44 | 'typescript@~4.5.0', 45 | 'tslib', 46 | 'ts-jest@^27', 47 | ]; 48 | export const storybookDependencies = [ 49 | '@babel/core', 50 | '@storybook/addon-actions', 51 | '@storybook/addon-essentials', 52 | '@storybook/addon-interactions', 53 | '@storybook/addon-links', 54 | '@storybook/builder-webpack4', 55 | '@storybook/manager-webpack4', 56 | '@storybook/react', 57 | '@storybook/testing-library', 58 | 'babel-loader', 59 | ]; 60 | export const tsStorybookDependencies = ['@types/babel__core']; 61 | -------------------------------------------------------------------------------- /packages/create-react-pkg/src/utils.js: -------------------------------------------------------------------------------- 1 | import https from 'https'; 2 | import { execSync } from 'child_process'; 3 | import spawn from 'cross-spawn'; 4 | import { 5 | basePackageJSON, 6 | dependencies, 7 | tsDependencies, 8 | storybookDependencies, 9 | tsStorybookDependencies, 10 | } from './pkgTemplate'; 11 | import packageJSON from '../package.json'; 12 | 13 | export function checkForLatestVersion() { 14 | return new Promise((resolve, reject) => { 15 | https 16 | .get( 17 | `https://registry.npmjs.org/-/package/${packageJSON.name}/dist-tags`, 18 | (res) => { 19 | if (res.statusCode === 200) { 20 | let body = ''; 21 | res.on('data', (data) => (body += data)); 22 | res.on('end', () => { 23 | resolve(JSON.parse(body).latest); 24 | }); 25 | } else { 26 | reject(new Error('Failed to check for the latest version of CLI')); 27 | } 28 | } 29 | ) 30 | .on('error', () => { 31 | reject(new Error('Failed to check for the latest version of CLI')); 32 | }); 33 | }); 34 | } 35 | 36 | export function getTemplateName(useTypescript, useStorybook) { 37 | if (useTypescript && useStorybook) { 38 | return 'typescript-storybook'; 39 | } else if (useTypescript) { 40 | return 'typescript'; 41 | } else if (useStorybook) { 42 | return 'basic-storybook'; 43 | } 44 | 45 | return 'basic'; 46 | } 47 | 48 | export function getAuthorName() { 49 | let author = ''; 50 | 51 | author = execSync('npm config get init-author-name').toString().trim(); 52 | if (author) return author; 53 | 54 | author = execSync('git config --global user.name').toString().trim(); 55 | if (author) return author; 56 | 57 | return author; 58 | } 59 | 60 | export function sanitizePackageName(packageName) { 61 | return packageName 62 | .toLowerCase() 63 | .replace(/(^@.*\/)|((^[^a-z]+)|[^\w.-])|([^a-z0-9]+$)/g, ''); 64 | } 65 | 66 | export function composePackageJSON( 67 | packageName, 68 | authorName, 69 | useTypescript, 70 | useStorybook 71 | ) { 72 | const safePackageName = sanitizePackageName(packageName); 73 | return { 74 | name: packageName, 75 | author: authorName, 76 | main: `dist/index.js`, // CJS entry 77 | module: `dist/esm/${safePackageName}.js`, // ES entry 78 | ...(useTypescript && { 79 | types: 'dist/types/index.d.ts', 80 | }), 81 | // spreading after so name fields appear above base fields in created package 82 | ...basePackageJSON, 83 | ...(useStorybook && { 84 | scripts: { 85 | ...basePackageJSON.scripts, 86 | storybook: 'start-storybook -p 6006', 87 | 'build-storybook': 'build-storybook', 88 | }, 89 | }), 90 | }; 91 | } 92 | 93 | export function getNPMVersion() { 94 | return execSync('npm --version').toString().trim(); 95 | } 96 | 97 | export function setLegacyPeerDeps() { 98 | execSync('npm config set legacy-peer-deps=true --location=project'); 99 | } 100 | 101 | // taken from create-react-app 102 | // https://github.com/facebook/create-react-app/blob/main/packages/create-react-app/createReactApp.js 103 | export function isUsingYarn() { 104 | return (process.env.npm_config_user_agent || '').indexOf('yarn') === 0; 105 | } 106 | 107 | export function makePackageDeps(useTypescript, useStorybook) { 108 | const deps = [...dependencies]; 109 | if (useTypescript) { 110 | deps.push(...tsDependencies); 111 | } 112 | if (useStorybook) { 113 | deps.push(...storybookDependencies); 114 | if (useTypescript) deps.push(...tsStorybookDependencies); 115 | } 116 | 117 | return deps; 118 | } 119 | 120 | export function makeInstallArgs(cmd, dependencies, useLegacyPeerDeps) { 121 | switch (cmd) { 122 | case 'npm': 123 | return [ 124 | 'install', 125 | ...dependencies, 126 | '--save-dev', 127 | '--no-audit', // https://github.com/facebook/create-react-app/issues/11174 128 | '--loglevel', 129 | 'error', 130 | useLegacyPeerDeps ? '--legacy-peer-deps' : '', 131 | ].filter(Boolean); 132 | case 'yarn': 133 | return ['add', ...dependencies, '--dev']; 134 | default: 135 | throw new Error('Unkown package manager'); 136 | } 137 | } 138 | 139 | export function executeInstallCommand(command, args) { 140 | return new Promise((resolve, reject) => { 141 | const child = spawn(command, args, { 142 | stdio: 'inherit', 143 | }); 144 | child.on('close', (code) => { 145 | if (code !== 0) { 146 | reject(new Error('Error installing dependencies')); 147 | return; 148 | } 149 | resolve(); 150 | }); 151 | }); 152 | } 153 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/baseFiles/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [author] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/baseFiles/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React Package 2 | 3 | This package was bootstrapped with [Create React Package](https://github.com/haseebanwar/create-react-pkg). A zero-config tool for creating React libraries. 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` or `yarn start` 10 | 11 | Watches for changes and rebuilds. To see your bundled React component in a browser, use integrated playground with [npm run preview](#npm-run-preview-or-yarn-preview) 12 | 13 | Note that this build is not optimized. Use `npm run build` to create an optimized build. 14 | 15 | ### `npm test` or `yarn test` 16 | 17 | Runs tests with Jest. 18 | 19 | To launch the test runner in the interactive watch mode, change the `test` script in `package.json` 20 | 21 | ```diff 22 | "scripts": { 23 | - "test": "react-pkg-scripts test" 24 | + "test": "react-pkg-scripts test --watch" 25 | } 26 | ``` 27 | 28 | ### `npm run build` or `yarn build` 29 | 30 | Builds the package for production to the `dist` folder. By default, it bundles your package in two module formats, CJS and ESM. But you can also create a UMD build by creating a file `crp.config.js` at the root of project with the following. 31 | 32 | ```js 33 | const { defineConfig } = require('react-pkg-scripts'); 34 | 35 | module.exports = defineConfig({ 36 | formats: ['cjs', 'esm', 'umd'], 37 | }); 38 | ``` 39 | 40 | ### `npm run preview` or `yarn preview` 41 | 42 | Opens React app development server from `playground/index.js` for previewing your library in browser. It comes with live reload that makes development much easier. 43 | 44 | Note that the integrated playground depends on the ESM output of your library. So, please run `npm start` with ESM build before running `npm run preview` 45 | 46 | ## Learn More 47 | 48 | You can learn more in the [Create React Package documentation](https://github.com/haseebanwar/create-react-pkg#readme). 49 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/baseFiles/gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # production 5 | /dist 6 | 7 | # misc 8 | .DS_Store 9 | .env.local 10 | .env.development.local 11 | .env.test.local 12 | .env.production.local 13 | 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/baseFiles/playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ${metas} 5 | Package Playground 6 | ${links} 7 | 8 | 9 |
10 | ${scripts} 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/baseFiles/playground/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import { MyComponent } from '../'; 4 | 5 | const container = document.getElementById('root'); 6 | const root = createRoot(container); 7 | root.render(); 8 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/basic-storybook/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: [ 3 | '../stories/**/*.stories.mdx', 4 | '../stories/**/*.stories.@(js|jsx|ts|tsx)', 5 | ], 6 | addons: [ 7 | '@storybook/addon-links', 8 | '@storybook/addon-essentials', 9 | '@storybook/addon-interactions', 10 | ], 11 | framework: '@storybook/react', 12 | }; 13 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/basic-storybook/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | actions: { argTypesRegex: '^on[A-Z].*' }, 3 | controls: { 4 | matchers: { 5 | color: /(background|color)$/i, // display a color picker for the args matching this regex 6 | date: /Date$/, // display a date picker for the args matching this regex 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/basic-storybook/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const MyComponent = (props) => { 4 | return
Find a way or make one!
; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/basic-storybook/stories/MyComponent.stories.jsx: -------------------------------------------------------------------------------- 1 | import { MyComponent } from '../src'; 2 | 3 | const meta = { 4 | title: 'My Component', 5 | component: MyComponent, 6 | }; 7 | 8 | export default meta; 9 | 10 | const Template = (args) => ; 11 | 12 | export const Default = Template.bind({}); 13 | Default.args = {}; 14 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/basic-storybook/test/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import { MyComponent } from '../src'; 4 | 5 | describe('it', () => { 6 | it('renders', () => { 7 | const container = document.createElement('div'); 8 | const root = createRoot(container); 9 | root.render(); 10 | root.unmount(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/basic/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const MyComponent = (props) => { 4 | return
Find a way or make one!
; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/basic/test/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import { MyComponent } from '../src'; 4 | 5 | describe('it', () => { 6 | it('renders', () => { 7 | const container = document.createElement('div'); 8 | const root = createRoot(container); 9 | root.render(); 10 | root.unmount(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/typescript-storybook/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: [ 3 | '../stories/**/*.stories.mdx', 4 | '../stories/**/*.stories.@(js|jsx|ts|tsx)', 5 | ], 6 | addons: [ 7 | '@storybook/addon-links', 8 | '@storybook/addon-essentials', 9 | '@storybook/addon-interactions', 10 | ], 11 | framework: '@storybook/react', 12 | }; 13 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/typescript-storybook/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | actions: { argTypesRegex: '^on[A-Z].*' }, 3 | controls: { 4 | matchers: { 5 | color: /(background|color)$/i, // display a color picker for the args matching this regex 6 | date: /Date$/, // display a date picker for the args matching this regex 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/typescript-storybook/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | export const MyComponent: FC = () => { 4 | return
Find a way or make one!
; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/typescript-storybook/stories/MyComponent.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ComponentStory, ComponentMeta } from '@storybook/react'; 3 | import { MyComponent } from '../src'; 4 | 5 | const meta: ComponentMeta = { 6 | title: 'My Component', 7 | component: MyComponent, 8 | }; 9 | 10 | export default meta; 11 | 12 | const Template: ComponentStory = (args) => ( 13 | 14 | ); 15 | 16 | export const Default = Template.bind({}); 17 | Default.args = {}; 18 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/typescript-storybook/test/index.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import { MyComponent } from '../src'; 4 | 5 | describe('it', () => { 6 | it('renders', () => { 7 | const container = document.createElement('div'); 8 | const root = createRoot(container); 9 | root.render(); 10 | root.unmount(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/typescript-storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "target": "es5", 5 | "lib": ["dom", "esnext"], 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "rootDir": "./src", 9 | "declaration": true, 10 | "declarationDir": "dist/types", // .d.ts directory 11 | "sourceMap": true, 12 | "importHelpers": true, 13 | "strict": true, 14 | // transpile JSX to React.createElement 15 | "jsx": "react", 16 | // interop between ESM and CJS modules. Recommended by TS 17 | "esModuleInterop": true, 18 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 19 | "skipLibCheck": true, 20 | // error out if import and file system have a casing mismatch. Recommended by TS 21 | "forceConsistentCasingInFileNames": true, 22 | // do not emit build when type-checking with tsc 23 | "noEmit": true, 24 | // resolve any JSON files imported 25 | "resolveJsonModule": true, 26 | // code best practices 27 | "noImplicitReturns": true, 28 | "noFallthroughCasesInSwitch": true, 29 | "noUnusedLocals": true, 30 | "noUnusedParameters": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/typescript/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | export const MyComponent: FC = () => { 4 | return
Find a way or make one!
; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/typescript/test/index.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import { MyComponent } from '../src'; 4 | 5 | describe('it', () => { 6 | it('renders', () => { 7 | const container = document.createElement('div'); 8 | const root = createRoot(container); 9 | root.render(); 10 | root.unmount(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/create-react-pkg/templates/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "target": "es5", 5 | "lib": ["dom", "esnext"], 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "rootDir": "./src", 9 | "declaration": true, 10 | "declarationDir": "dist/types", // .d.ts directory 11 | "sourceMap": true, 12 | "importHelpers": true, 13 | "strict": true, 14 | // transpile JSX to React.createElement 15 | "jsx": "react", 16 | // interop between ESM and CJS modules. Recommended by TS 17 | "esModuleInterop": true, 18 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 19 | "skipLibCheck": true, 20 | // error out if import and file system have a casing mismatch. Recommended by TS 21 | "forceConsistentCasingInFileNames": true, 22 | // do not emit build when type-checking with tsc 23 | "noEmit": true, 24 | // resolve any JSON files imported 25 | "resolveJsonModule": true, 26 | // code best practices 27 | "noImplicitReturns": true, 28 | "noFallthroughCasesInSwitch": true, 29 | "noUnusedLocals": true, 30 | "noUnusedParameters": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/create-react-pkg/test/utils.test.js: -------------------------------------------------------------------------------- 1 | import { sanitizePackageName } from '../src/utils'; 2 | 3 | describe('utils', () => { 4 | it('should generate safe package name', () => { 5 | expect(sanitizePackageName('@foo/bar')).toBe('bar'); 6 | expect(sanitizePackageName('@foo/bar-')).toBe('bar'); 7 | expect(sanitizePackageName('@foo/bar-baz')).toBe('bar-baz'); 8 | expect(sanitizePackageName('@FOO/BAR')).toBe('bar'); 9 | expect(sanitizePackageName('bar')).toBe('bar'); 10 | expect(sanitizePackageName('foo-baR')).toBe('foo-bar'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-present Haseeb Anwar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/README.md: -------------------------------------------------------------------------------- 1 | # react-pkg-scripts 2 | 3 | This package includes scripts and configuration used by [Create React Package](https://github.com/haseebanwar/create-react-pkg). A zero-config CLI for creating react packages. 4 | 5 | Please refer to its documentation: 6 | 7 | - [Getting Started](https://github.com/haseebanwar/create-react-pkg) – How to create a new package. 8 | - [User Guide](https://github.com/haseebanwar/create-react-pkg) – How to develop packages bootstrapped with Create React Package. 9 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-pkg-scripts", 3 | "version": "1.0.9", 4 | "description": "Scripts for Create React Package", 5 | "keywords": [ 6 | "react", 7 | "package", 8 | "library", 9 | "rollup", 10 | "bundle", 11 | "eslint" 12 | ], 13 | "homepage": "https://github.com/haseebanwar/create-react-pkg/tree/master/packages/react-pkg-scripts", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/haseebanwar/create-react-pkg.git", 17 | "directory": "packages/react-pkg-scripts" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/haseebanwar/create-react-pkg/issues" 21 | }, 22 | "author": "Haseeb Anwar (https://haseebanwar.net/)", 23 | "license": "MIT", 24 | "bin": { 25 | "react-pkg-scripts": "lib/index.js" 26 | }, 27 | "main": "lib/config.js", 28 | "types": "lib/types/index.d.ts", 29 | "files": [ 30 | "lib" 31 | ], 32 | "engines": { 33 | "node": ">=14.17.0" 34 | }, 35 | "scripts": { 36 | "start": "npm run clean && babel src -d lib --root-mode upward --copy-files --watch", 37 | "build": "npm run clean && babel src -d lib --root-mode upward --copy-files", 38 | "prepublishOnly": "npm run build", 39 | "clean": "rimraf lib" 40 | }, 41 | "dependencies": { 42 | "@babel/core": "^7.19.3", 43 | "@babel/plugin-transform-runtime": "^7.19.1", 44 | "@babel/preset-env": "^7.19.4", 45 | "@babel/preset-react": "^7.18.6", 46 | "@babel/runtime": "^7.19.4", 47 | "@rollup/plugin-babel": "^5.3.1", 48 | "@rollup/plugin-commonjs": "^22.0.1", 49 | "@rollup/plugin-html": "^0.2.4", 50 | "@rollup/plugin-json": "^4.1.0", 51 | "@rollup/plugin-node-resolve": "^13.3.0", 52 | "@rollup/plugin-replace": "^4.0.0", 53 | "autoprefixer": "^10.4.12", 54 | "babel-jest": "^27.5.1", 55 | "browserslist": "^4.21.4", 56 | "camelcase": "^6.3.0", 57 | "chalk": "^4.1.2", 58 | "eslint": "^8.25.0", 59 | "eslint-config-react-app": "^7.0.0", 60 | "fs-extra": "^10.1.0", 61 | "jest": "^27.5.1", 62 | "jest-watch-typeahead": "^1.0.0", 63 | "lodash.template": "^4.5.0", 64 | "postcss": "^8.4.18", 65 | "rollup": "^2.79.1", 66 | "rollup-plugin-livereload": "^2.0.5", 67 | "rollup-plugin-postcss": "^4.0.2", 68 | "rollup-plugin-serve": "^2.0.1", 69 | "rollup-plugin-terser": "^7.0.2", 70 | "rollup-plugin-typescript2": "^0.32.1", 71 | "strip-ansi": "^6.0.1", 72 | "text-table": "^0.2.0" 73 | }, 74 | "peerDependencies": { 75 | "ts-jest": "^27", 76 | "tslib": "^2.0.0", 77 | "typescript": "^4.0.0" 78 | }, 79 | "peerDependenciesMeta": { 80 | "typescript": { 81 | "optional": true 82 | }, 83 | "tslib": { 84 | "optional": true 85 | }, 86 | "ts-jest": { 87 | "optional": true 88 | } 89 | }, 90 | "gitHead": "8d5e7ac1c028829aa70e4ae1256141679b21167b" 91 | } 92 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/config.js: -------------------------------------------------------------------------------- 1 | export function defineConfig(config) { 2 | return config; 3 | } 4 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/eslint/eslintFormatter.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import chalk from 'chalk'; 3 | import stripAnsi from 'strip-ansi'; 4 | import table from 'text-table'; 5 | 6 | const emitErrorsAsWarnings = 7 | process.env.NODE_ENV === 'development' && 8 | process.env.ESLINT_NO_DEV_ERRORS === 'true'; 9 | 10 | function isError(message) { 11 | if (message.fatal || message.severity === 2) { 12 | return true; 13 | } 14 | return false; 15 | } 16 | 17 | function getRelativePath(filePath) { 18 | return path.relative(process.cwd(), filePath); 19 | } 20 | 21 | // taken from react-dev-utils 22 | // https://github.com/facebook/create-react-app/blob/main/packages/react-dev-utils/eslintFormatter.js 23 | export function eslintFormatter(results) { 24 | let output = '\n'; 25 | let hasErrors = false; 26 | let reportContainsErrorRuleIDs = false; 27 | 28 | results.forEach((result) => { 29 | let messages = result.messages; 30 | if (messages.length === 0) { 31 | return; 32 | } 33 | 34 | messages = messages.map((message) => { 35 | let messageType; 36 | if (isError(message) && !emitErrorsAsWarnings) { 37 | messageType = 'error'; 38 | hasErrors = true; 39 | if (message.ruleId) { 40 | reportContainsErrorRuleIDs = true; 41 | } 42 | } else { 43 | messageType = 'warn'; 44 | } 45 | 46 | let line = message.line || 0; 47 | if (message.column) { 48 | line += ':' + message.column; 49 | } 50 | let position = chalk.bold('Line ' + line + ':'); 51 | return [ 52 | '', 53 | position, 54 | messageType, 55 | message.message.replace(/\.$/, ''), 56 | chalk.underline(message.ruleId || ''), 57 | ]; 58 | }); 59 | 60 | // if there are error messages, we want to show only errors 61 | if (hasErrors) { 62 | messages = messages.filter((m) => m[2] === 'error'); 63 | } 64 | 65 | // add color to rule keywords 66 | messages.forEach((m) => { 67 | m[4] = m[2] === 'error' ? chalk.red(m[4]) : chalk.yellow(m[4]); 68 | m.splice(2, 1); 69 | }); 70 | 71 | let outputTable = table(messages, { 72 | align: ['l', 'l', 'l'], 73 | stringLength(str) { 74 | return stripAnsi(str).length; 75 | }, 76 | }); 77 | 78 | // print the filename and relative path 79 | output += `${getRelativePath(result.filePath)}\n`; 80 | 81 | // print the errors 82 | output += `${outputTable}\n\n`; 83 | }); 84 | 85 | if (reportContainsErrorRuleIDs) { 86 | // Unlike with warnings, we have to do it here. 87 | // We have similar code in react-scripts for warnings, 88 | // but warnings can appear in multiple files so we only 89 | // print it once at the end. For errors, however, we print 90 | // it here because we always show at most one error, and 91 | // we can only be sure it's an ESLint error before exiting 92 | // this function. 93 | output += 94 | 'Search for the ' + 95 | chalk.underline(chalk.red('keywords')) + 96 | ' to learn more about each error.'; 97 | } 98 | 99 | return output; 100 | } 101 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import chalk from 'chalk'; 4 | import { watch } from './scripts/watch'; 5 | import { preview } from './scripts/preview'; 6 | import { build } from './scripts/build'; 7 | import { test } from './scripts/test'; 8 | 9 | const commands = { 10 | watch: 'watch', 11 | preview: 'preview', 12 | build: 'build', 13 | test: 'test', 14 | }; 15 | 16 | const args = process.argv.slice(2); 17 | const command = args.find((arg) => Object.keys(commands).includes(arg)); 18 | 19 | let cleanArgs = []; 20 | if (command) { 21 | cleanArgs = args.filter((arg) => arg !== command); 22 | } 23 | 24 | switch (command) { 25 | case commands.watch: 26 | watch(); 27 | break; 28 | case commands.preview: 29 | preview(); 30 | break; 31 | case commands.build: 32 | build(); 33 | break; 34 | case commands.test: 35 | test(cleanArgs); 36 | break; 37 | default: 38 | console.error( 39 | `Unkown command. Valid commands are`, 40 | Object.entries(commands).reduce((acc, [, validCommand]) => { 41 | acc += `\n- ${chalk.cyan(validCommand)}`; 42 | return acc; 43 | }, '') 44 | ); 45 | process.exit(1); 46 | } 47 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/jest/babelTransform.js: -------------------------------------------------------------------------------- 1 | import babelJest from 'babel-jest'; 2 | 3 | // custom jest transformer for transforming js and jsx files with babel 4 | export default babelJest.createTransformer({ 5 | presets: [ 6 | [require.resolve('@babel/preset-env')], 7 | [require.resolve('@babel/preset-react')], 8 | ], 9 | plugins: ['@babel/plugin-transform-runtime'], 10 | babelrc: false, 11 | configFile: false, 12 | }); 13 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | // custom jest transformer for handling css, scss, etc. files 2 | export default { 3 | process() { 4 | // these files are transformed to this 5 | return 'module.exports = {};'; 6 | }, 7 | getCacheKey() { 8 | // The output is always the same. 9 | return 'cssTransform'; 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/paths.js: -------------------------------------------------------------------------------- 1 | import { resolvePath } from './utils'; 2 | 3 | export const paths = { 4 | packageRoot: resolvePath('.'), 5 | packagePackageJson: resolvePath('package.json'), 6 | packageDist: resolvePath('dist'), 7 | packageTSConfig: resolvePath('tsconfig.json'), 8 | packageConfig: resolvePath('crp.config.js'), 9 | playgroundEntry: resolvePath('playground/index.js'), 10 | playgroundHTML: resolvePath('playground/index.html'), 11 | playgroundDist: resolvePath('playground/build'), 12 | }; 13 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/rollup/rollupConfig.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import json from '@rollup/plugin-json'; 4 | import { DEFAULT_EXTENSIONS as DEFAULT_BABEL_EXTENSIONS } from '@babel/core'; 5 | import { babel } from '@rollup/plugin-babel'; 6 | import typescript from 'rollup-plugin-typescript2'; 7 | import replace from '@rollup/plugin-replace'; 8 | import { terser } from 'rollup-plugin-terser'; 9 | import postcss from 'rollup-plugin-postcss'; 10 | import html from '@rollup/plugin-html'; 11 | import serve from 'rollup-plugin-serve'; 12 | import live from 'rollup-plugin-livereload'; 13 | import autoprefixer from 'autoprefixer'; 14 | import camelCase from 'camelcase'; 15 | import eslint from './rollupESLintPlugin'; 16 | import { eslintFormatter } from '../eslint/eslintFormatter'; 17 | import { generateHTML } from './rollupGenerateHtml'; 18 | import { 19 | checkTypescriptSetup, 20 | sanitizePackageName, 21 | writeCjsEntryFile, 22 | readPackageJsonOfPackage, 23 | } from '../utils'; 24 | import { paths } from '../paths'; 25 | 26 | const allBuildFormats = [ 27 | { 28 | format: 'cjs', 29 | mode: 'development', 30 | }, 31 | { 32 | format: 'cjs', 33 | mode: 'production', 34 | }, 35 | { 36 | format: 'esm', 37 | mode: 'development', 38 | }, 39 | { 40 | format: 'umd', 41 | mode: 'development', 42 | }, 43 | { 44 | format: 'umd', 45 | mode: 'production', 46 | }, 47 | ]; 48 | 49 | export function createRollupConfig(customConfig) { 50 | const packagePackageJson = readPackageJsonOfPackage(); 51 | const useTypescript = checkTypescriptSetup(); 52 | const safePackageName = sanitizePackageName(packagePackageJson.name); 53 | 54 | const config = { 55 | outDir: paths.packageDist, 56 | input: `src/index.${useTypescript ? 'tsx' : 'js'}`, 57 | name: camelCase(safePackageName), 58 | formats: ['cjs', 'esm'], 59 | disableESLint: false, 60 | babelHelpers: 'bundled', 61 | ...customConfig, 62 | }; 63 | const { 64 | input, 65 | outDir, 66 | formats, 67 | name, 68 | disableESLint, 69 | rollupOptions, 70 | babelHelpers, 71 | } = config; 72 | 73 | const buildFormats = allBuildFormats.filter((buildModule) => 74 | formats.includes(buildModule.format) 75 | ); 76 | 77 | if (buildFormats.find(({ format }) => format === 'cjs')) { 78 | writeCjsEntryFile(packagePackageJson.name, config.outDir); 79 | } 80 | 81 | return buildFormats.map((buildModule, idx) => { 82 | const { format, mode } = buildModule; 83 | // for UMD, always bundle babel helpers 84 | const babelHelpersExplicitUMD = format === 'umd' ? 'bundled' : babelHelpers; 85 | 86 | let output = { 87 | dir: outDir, 88 | format, 89 | sourcemap: true, 90 | freeze: false, // do not call Object.freeze on imported objects with import * syntax 91 | exports: 'named', 92 | assetFileNames: '[name][extname]', 93 | ...(format === 'umd' && { 94 | name, 95 | // inline dynamic imports for umd modules 96 | // because rollup doesn't support code-splitting for IIFE/UMD 97 | inlineDynamicImports: true, 98 | // tell rollup that external module like 'react' should be named this in IIFE/UMD 99 | // for example 'react' will be bound to the window object (in browser) like 100 | // window.React = // react 101 | globals: { react: 'React', 'react-native': 'ReactNative' }, 102 | }), 103 | }; 104 | 105 | switch (mode) { 106 | case 'production': { 107 | output = { 108 | ...output, 109 | entryFileNames: `${format}/${safePackageName}.min.js`, 110 | chunkFileNames: `${format}/[name]-[hash].min.js`, 111 | }; 112 | break; 113 | } 114 | case 'development': 115 | default: { 116 | output = { 117 | ...output, 118 | entryFileNames: `${format}/${safePackageName}.js`, 119 | chunkFileNames: `${format}/[name]-[hash].js`, 120 | }; 121 | break; 122 | } 123 | } 124 | 125 | const config = { 126 | input, 127 | // don't include package peer deps in the bundled code 128 | external: [ 129 | ...Object.keys(packagePackageJson.peerDependencies || []), 130 | babelHelpersExplicitUMD === 'runtime' && /@babel\/runtime/, 131 | ].filter(Boolean), 132 | // allow user defined rollup root options 133 | ...(rollupOptions || {}), 134 | plugins: [ 135 | idx === 0 && 136 | disableESLint === false && 137 | eslint({ 138 | formatter: eslintFormatter, 139 | }), 140 | nodeResolve(), 141 | commonjs({ include: /node_modules/ }), 142 | json(), 143 | useTypescript && 144 | typescript({ 145 | tsconfig: paths.packageTSConfig, 146 | tsconfigDefaults: { 147 | exclude: [ 148 | // all test files 149 | '**/*.spec.ts', 150 | '**/*.test.ts', 151 | '**/*.spec.tsx', 152 | '**/*.test.tsx', 153 | '**/*.spec.js', 154 | '**/*.test.js', 155 | '**/*.spec.jsx', 156 | '**/*.test.jsx', 157 | // '**/*.+(spec|test).{ts,tsx,js,jsx}', 158 | // TS defaults below 159 | 'node_modules', 160 | 'bower_components', 161 | 'jspm_packages', 162 | 'dist', // outDir is default 163 | ], 164 | }, 165 | // write declaration files only once (not again and again for all build formats) 166 | useTsconfigDeclarationDir: idx === 0, 167 | ...(idx !== 0 && { 168 | tsconfigOverride: { 169 | compilerOptions: { 170 | declaration: false, 171 | }, 172 | }, 173 | }), 174 | }), 175 | // babel plugins run before presets. Plugin ordering is first to last. Preset ordering is reversed (last to first). 176 | babel({ 177 | exclude: ['node_modules/**'], 178 | extensions: [...DEFAULT_BABEL_EXTENSIONS, '.ts', '.tsx'], 179 | ...(!useTypescript && { 180 | presets: [ 181 | [require.resolve('@babel/preset-env')], 182 | [require.resolve('@babel/preset-react')], 183 | ], 184 | }), 185 | babelHelpers: babelHelpersExplicitUMD, 186 | // replace reference of the babal helper functions to the @babel/runtime version 187 | // more: https://babeljs.io/docs/en/babel-runtime#why 188 | plugins: [ 189 | babelHelpersExplicitUMD === 'runtime' && 190 | '@babel/plugin-transform-runtime', 191 | ].filter(Boolean), 192 | }), 193 | postcss({ 194 | extract: idx === 0 ? `css/${safePackageName}.min.css` : false, // extract css file only once 195 | inject: false, 196 | minimize: true, 197 | plugins: [autoprefixer()], 198 | sourceMap: true, 199 | config: false, // do not load postcss config 200 | // css modules are by default supported for .module.css, .module.scss, etc 201 | }), 202 | replace({ 203 | 'process.env.NODE_ENV': JSON.stringify(mode), 204 | preventAssignment: true, 205 | }), 206 | mode === 'production' && terser(), 207 | // push user defined rollup plugins 208 | ...(rollupOptions?.plugins || []), 209 | ].filter(Boolean), 210 | // allow user defined rollup options for output 211 | output: { 212 | ...output, 213 | ...(rollupOptions?.output || {}), 214 | }, 215 | }; 216 | 217 | if (typeof rollupOptions === 'function') { 218 | return rollupOptions(config, { format, mode }); 219 | } 220 | 221 | return config; 222 | }); 223 | } 224 | 225 | export function createRollupPlaygroundConfig( 226 | customConfig, 227 | packageDistPicomatch 228 | ) { 229 | const { disableESLint = false, playground } = customConfig; 230 | 231 | const config = { 232 | input: paths.playgroundEntry, 233 | external: [], 234 | // allow user defined rollup root options 235 | ...(playground?.rollupOptions || {}), 236 | plugins: [ 237 | !disableESLint && 238 | eslint({ 239 | formatter: eslintFormatter, 240 | packageDistPicomatch, 241 | }), 242 | nodeResolve(), 243 | /** 244 | * it is important to include package's dist because the bundle for preview app depends on it 245 | * and if package is emitting a CJS bundle then commonjs plugin will transform CJS exports from package's dist to ESM for preview app bundle 246 | */ 247 | commonjs({ 248 | include: [/node_modules/, packageDistPicomatch], 249 | }), 250 | json(), 251 | // babel plugins run before presets. Plugin ordering is first to last. Preset ordering is reversed (last to first). 252 | babel({ 253 | exclude: ['node_modules/**', packageDistPicomatch], 254 | extensions: [...DEFAULT_BABEL_EXTENSIONS], 255 | presets: [ 256 | [require.resolve('@babel/preset-env')], 257 | [require.resolve('@babel/preset-react')], 258 | ], 259 | babelHelpers: 'bundled', 260 | }), 261 | postcss({ 262 | extract: 'styles.css', 263 | inject: false, 264 | plugins: [autoprefixer()], 265 | sourceMap: true, 266 | config: false, // do not load postcss config 267 | // css modules are by default supported for .module.css, .module.scss, etc 268 | }), 269 | replace({ 270 | 'process.env.NODE_ENV': JSON.stringify('development'), 271 | preventAssignment: true, 272 | }), 273 | html({ 274 | template: generateHTML, 275 | }), 276 | serve({ 277 | open: true, 278 | contentBase: paths.playgroundDist, 279 | ...(playground?.server || {}), 280 | }), 281 | live({ 282 | ...(playground?.livereload || {}), 283 | }), 284 | // user defined rollup plugins 285 | ...(playground?.rollupOptions?.plugins || []), 286 | ].filter(Boolean), 287 | output: { 288 | dir: paths.playgroundDist, 289 | format: 'esm', 290 | sourcemap: true, 291 | freeze: false, // do not call Object.freeze on imported objects with import * syntax 292 | assetFileNames: '[name][extname]', 293 | entryFileNames: `[name].js`, 294 | chunkFileNames: `[name]-[hash].js`, 295 | ...(playground?.rollupOptions?.output || {}), 296 | }, 297 | }; 298 | 299 | if (typeof playground?.rollupOptions === 'function') { 300 | return playground?.rollupOptions(config); 301 | } 302 | 303 | return config; 304 | } 305 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/rollup/rollupESLintPlugin.js: -------------------------------------------------------------------------------- 1 | import { ESLint } from 'eslint'; 2 | import { createFilter } from '@rollup/pluginutils'; 3 | 4 | function getLintingReport(results) { 5 | return results.reduce( 6 | (acc, result) => { 7 | acc.errorCount += result.errorCount; 8 | acc.warningCount += result.warningCount; 9 | return acc; 10 | }, 11 | { errorCount: 0, warningCount: 0 } 12 | ); 13 | } 14 | 15 | function eslint(options = {}) { 16 | const defaultFormatter = 'stylish'; 17 | const { 18 | throwOnError = true, 19 | throwOnWarning = false, 20 | formatter = defaultFormatter, 21 | include = [], 22 | exclude = [ 23 | /node_modules/, 24 | /\.json|\.s?css$/, 25 | options.packageDistPicomatch, 26 | ].filter(Boolean), 27 | } = options; 28 | 29 | // creating a new instance of this class automatically loads any eslint config/ignore files 30 | const eslint = new ESLint({ 31 | baseConfig: { 32 | extends: ['react-app', 'react-app/jest'], 33 | }, 34 | }); 35 | 36 | const filter = createFilter(include, exclude); 37 | 38 | return { 39 | name: 'eslint', 40 | shouldTransformCachedModule({ id, meta }) { 41 | // returning falsy value from this hook will skip the next transform hook for current module 42 | 43 | // if current module is a node_module or .json/.css then skip 44 | if (!filter(id)) return; 45 | 46 | if (meta.eslint && (meta.eslint.warningCount || meta.eslint.errorCount)) { 47 | this.warn({ 48 | message: 'Lint warnings', 49 | lintWarnings: meta.eslint.resultText, 50 | }); 51 | } 52 | }, 53 | transform: async function transform(code, id) { 54 | // if current module is a node_module or .json/.css then skip 55 | if (!filter(id)) return; 56 | 57 | // results would be an empty array if current module (with filepath as id) 58 | // is present in .eslintignore file at the root of the package 59 | const results = await eslint.lintText(code, { 60 | filePath: id, 61 | }); 62 | 63 | const { errorCount, warningCount } = getLintingReport(results); 64 | 65 | let lintFormatter; 66 | switch (typeof formatter) { 67 | case 'string': { 68 | lintFormatter = (await eslint.loadFormatter(formatter)).format; 69 | break; 70 | } 71 | case 'function': { 72 | lintFormatter = formatter; 73 | break; 74 | } 75 | default: { 76 | lintFormatter = (await eslint.loadFormatter(defaultFormatter)).format; 77 | break; 78 | } 79 | } 80 | const resultText = lintFormatter(results); 81 | 82 | if (throwOnError && errorCount) { 83 | const error = new Error('Lint errors'); 84 | error.lintErrors = resultText; 85 | throw error; 86 | } 87 | 88 | if (throwOnWarning && warningCount) { 89 | const error = new Error('Lint warnings'); 90 | error.lintWarnings = resultText; 91 | throw error; 92 | } 93 | 94 | if (warningCount) { 95 | this.warn({ 96 | message: 'Lint warnings', 97 | lintWarnings: resultText, 98 | }); 99 | } 100 | 101 | // store lint errors/warnings in the meta field for this module 102 | // so in the shouldTransformCachedModule hook we don't have to lint cached modules again 103 | // instead show cached errors/warnings for that module 104 | return { meta: { eslint: { resultText, warningCount, errorCount } } }; 105 | }, 106 | }; 107 | } 108 | 109 | export default eslint; 110 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/rollup/rollupGenerateHtml.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import { makeHtmlAttributes } from '@rollup/plugin-html'; 3 | import template from 'lodash.template'; 4 | import { paths } from '../paths'; 5 | 6 | export async function generateHTML({ attributes, files, meta, publicPath }) { 7 | const htmlTemplate = await fs.readFile(paths.playgroundHTML, { 8 | encoding: 'utf-8', 9 | }); 10 | 11 | const compileTemplate = template(htmlTemplate); 12 | 13 | const scripts = (files.js || []) 14 | .map(({ fileName, isDynamicEntry }) => { 15 | if (isDynamicEntry) return ''; 16 | const attrs = makeHtmlAttributes(attributes.script); 17 | return ``; 18 | }) 19 | .filter(Boolean) 20 | .join('\n'); 21 | 22 | const links = (files.css || []) 23 | .map(({ fileName }) => { 24 | const attrs = makeHtmlAttributes(attributes.link); 25 | return ``; 26 | }) 27 | .join('\n'); 28 | 29 | const metas = meta 30 | .map((input) => { 31 | const attrs = makeHtmlAttributes(input); 32 | return ``; 33 | }) 34 | .join('\n'); 35 | 36 | const source = compileTemplate({ metas, links, scripts }); 37 | 38 | return source; 39 | } 40 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/scripts/build.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import { rollup } from 'rollup'; 3 | import chalk from 'chalk'; 4 | import { createRollupConfig } from '../rollup/rollupConfig'; 5 | import { logBuildError, logBuildWarnings, clearConsole } from '../utils'; 6 | import { paths } from '../paths'; 7 | 8 | export async function build() { 9 | // node env is used by tools like browserslist, babel, etc. 10 | process.env.NODE_ENV = 'production'; 11 | process.env.BABEL_ENV = 'production'; 12 | 13 | let buildFailed = false; 14 | let hasWarnings = false; 15 | 16 | try { 17 | clearConsole(); 18 | console.log(chalk.cyan('Creating an optimized build...')); 19 | 20 | let customConfig = {}; 21 | if (fs.existsSync(paths.packageConfig)) { 22 | customConfig = require(paths.packageConfig); 23 | } 24 | 25 | if (customConfig.outDir) { 26 | fs.emptyDirSync(customConfig.outDir); 27 | } else { 28 | fs.emptyDirSync(paths.packageDist); 29 | } 30 | 31 | const rollupBuilds = createRollupConfig(customConfig); 32 | 33 | await Promise.all( 34 | rollupBuilds.map(async (buildConfig, idx) => { 35 | const bundle = await rollup({ 36 | ...buildConfig, 37 | onwarn: (warning, warn) => { 38 | // log warnings only for the first bundle (prevents duplicate warnings) 39 | if (idx !== 0) return; 40 | 41 | // print this message only when there are no previous warnings for this build 42 | if (!hasWarnings) { 43 | console.log(chalk.yellow('Compiled with warnings.')); 44 | } 45 | hasWarnings = true; 46 | logBuildWarnings(warning, warn); 47 | }, 48 | }); 49 | await bundle.write(buildConfig.output); 50 | await bundle.close(); 51 | }) 52 | ); 53 | 54 | console.log(chalk.green('Build succeeded!')); 55 | } catch (error) { 56 | buildFailed = true; 57 | clearConsole(); 58 | console.error(chalk.red('Failed to compile.')); 59 | logBuildError(error); 60 | } finally { 61 | process.exit(buildFailed ? 1 : 0); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/scripts/preview.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import chalk from 'chalk'; 4 | import { watch as rollupWatch } from 'rollup'; 5 | import { createRollupPlaygroundConfig } from '../rollup/rollupConfig'; 6 | import { logBuildError, logBuildWarnings, clearConsole } from '../utils'; 7 | import { paths } from '../paths'; 8 | 9 | export function preview() { 10 | try { 11 | // node env is used by tools like browserslist, babel, etc. 12 | process.env.NODE_ENV = 'development'; 13 | process.env.BABEL_ENV = 'development'; 14 | 15 | let hasErrors = false; 16 | let hasWarnings = false; 17 | 18 | fs.emptyDirSync(paths.playgroundDist); 19 | 20 | let customConfig = {}; 21 | if (fs.existsSync(paths.packageConfig)) { 22 | customConfig = require(paths.packageConfig); 23 | } 24 | 25 | const { outDir = paths.packageDist } = customConfig; 26 | const packageDistPicomatch = `${path.basename(outDir)}/**`; 27 | 28 | const rollupPlaygroundBuild = createRollupPlaygroundConfig( 29 | customConfig, 30 | packageDistPicomatch 31 | ); 32 | 33 | const watcher = rollupWatch({ 34 | ...rollupPlaygroundBuild, 35 | onwarn: (warning, warn) => { 36 | // ignoring because eslint already picked them 37 | if (warning.code === 'UNUSED_EXTERNAL_IMPORT') return; 38 | 39 | // print this message only when there were no previous warnings for this build 40 | if (!hasWarnings) { 41 | clearConsole(); 42 | console.log(chalk.yellow('Compiled with warnings.')); 43 | } 44 | hasWarnings = true; 45 | 46 | logBuildWarnings(warning, warn); 47 | }, 48 | watch: { 49 | include: ['playground/**', packageDistPicomatch], 50 | exclude: ['node_modules/**'], 51 | }, 52 | }); 53 | 54 | watcher.on('event', (evt) => { 55 | if (evt.result) { 56 | evt.result.close(); 57 | } 58 | 59 | if (evt.code === 'START') { 60 | clearConsole(); 61 | console.log(chalk.yellow(`Compiling playground...`)); 62 | } 63 | 64 | if (evt.code === 'ERROR') { 65 | hasErrors = true; 66 | clearConsole(); 67 | console.log(chalk.red(`Failed to compile.`)); 68 | logBuildError(evt.error); 69 | } 70 | 71 | if (evt.code === 'END') { 72 | if (!hasErrors && !hasWarnings) { 73 | console.log(chalk.green('Compiled successfully!')); 74 | } 75 | 76 | // reset for the next round of build 77 | hasErrors = false; 78 | hasWarnings = false; 79 | } 80 | }); 81 | } catch (error) { 82 | console.error(chalk.red(`Failed to build preview: ${error.message}`)); 83 | console.error('error', error); 84 | process.exit(1); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/scripts/test.js: -------------------------------------------------------------------------------- 1 | import { run as jestRun } from 'jest'; 2 | import chalk from 'chalk'; 3 | import { paths } from '../paths'; 4 | import { checkTypescriptSetup, readPackageJsonOfPackage } from '../utils'; 5 | 6 | export function test(cleanArgs) { 7 | try { 8 | process.env.NODE_ENV = 'test'; 9 | process.env.BABEL_ENV = 'test'; // because we're using babel for transforming JSX 10 | 11 | const isTypescriptConfigured = checkTypescriptSetup(); 12 | const packagePackageJSON = readPackageJsonOfPackage(); 13 | 14 | const jestConfig = { 15 | testEnvironment: 'jsdom', 16 | transform: { 17 | '.(js|jsx)$': require.resolve('../jest/babelTransform.js'), 18 | ...(isTypescriptConfigured && { 19 | '.(ts|tsx)$': require.resolve('ts-jest'), 20 | }), 21 | '.(css|scss|sass|styl|stylus|less)$': require.resolve( 22 | '../jest/cssTransform.js' 23 | ), 24 | }, 25 | // transformIgnorePatterns already includes node_modules 26 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'node'], // it is default, explicitly specifying 27 | collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'], 28 | testMatch: [ 29 | '/**/__tests__/**/*.{js,jsx,ts,tsx}', 30 | '/**/*.(spec|test).{ts,tsx,js,jsx}', 31 | ], 32 | rootDir: paths.packageRoot, 33 | watchPlugins: [ 34 | require.resolve('jest-watch-typeahead/filename'), 35 | require.resolve('jest-watch-typeahead/testname'), 36 | ], 37 | }; 38 | 39 | cleanArgs.push( 40 | '--config', 41 | JSON.stringify({ ...jestConfig, ...packagePackageJSON.jest }) 42 | ); 43 | 44 | // pass any other options directly to jest 45 | jestRun(cleanArgs); 46 | } catch (error) { 47 | console.error(chalk.red(`Failed to run tests: ${error.message}`)); 48 | console.error('error', error); 49 | process.exit(1); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/scripts/watch.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import chalk from 'chalk'; 3 | import { watch as rollupWatch } from 'rollup'; 4 | import { createRollupConfig } from '../rollup/rollupConfig'; 5 | import { logBuildError, logBuildWarnings, clearConsole } from '../utils'; 6 | import { paths } from '../paths'; 7 | 8 | export function watch() { 9 | try { 10 | // node env is used by tools like browserslist, babel, etc. 11 | process.env.NODE_ENV = 'development'; 12 | process.env.BABEL_ENV = 'development'; 13 | 14 | let hasErrors = false; 15 | let hasWarnings = false; 16 | 17 | let customConfig = {}; 18 | if (fs.existsSync(paths.packageConfig)) { 19 | customConfig = require(paths.packageConfig); 20 | } 21 | 22 | if (customConfig.outDir) { 23 | fs.emptyDirSync(customConfig.outDir); 24 | } else { 25 | fs.emptyDirSync(paths.packageDist); 26 | } 27 | 28 | const rollupBuilds = createRollupConfig(customConfig); 29 | 30 | const watcher = rollupWatch( 31 | rollupBuilds.map((buildConfig, idx) => ({ 32 | ...buildConfig, 33 | onwarn: (warning, warn) => { 34 | // ignoring because eslint already picked them 35 | if (warning.code === 'UNUSED_EXTERNAL_IMPORT') return; 36 | 37 | // log warnings only for the first bundle (prevents duplicate warnings) 38 | if (idx !== 0) return; 39 | 40 | // print this message only when there were no previous warnings for this build 41 | if (!hasWarnings) { 42 | clearConsole(); 43 | console.log(chalk.yellow('Compiled with warnings.')); 44 | } 45 | hasWarnings = true; 46 | 47 | logBuildWarnings(warning, warn); 48 | }, 49 | watch: { 50 | include: ['src/**'], 51 | exclude: ['node_modules/**'], 52 | }, 53 | })) 54 | ); 55 | 56 | watcher.on('event', (evt) => { 57 | if (evt.result) { 58 | evt.result.close(); 59 | } 60 | 61 | if (evt.code === 'START') { 62 | clearConsole(); 63 | console.log(chalk.yellow(`Compiling...`)); 64 | } 65 | 66 | if (evt.code === 'ERROR') { 67 | hasErrors = true; 68 | clearConsole(); 69 | console.log(chalk.red(`Failed to compile.`)); 70 | logBuildError(evt.error); 71 | } 72 | 73 | if (evt.code === 'END') { 74 | if (!hasErrors && !hasWarnings) { 75 | clearConsole(); 76 | console.log(chalk.green('Compiled successfully!')); 77 | console.log('\nNote that the development build is not optimized.'); 78 | console.log( 79 | `To create a production build, use ${chalk.cyan('npm run build')}.` 80 | ); 81 | } 82 | 83 | // reset for the next round of build 84 | hasErrors = false; 85 | hasWarnings = false; 86 | } 87 | }); 88 | } catch (error) { 89 | console.error(chalk.red(`Failed to run watch mode: ${error.message}`)); 90 | console.error('error', error); 91 | process.exit(1); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { RollupOptions } from 'rollup'; 2 | import { RollupServeOptions } from 'rollup-plugin-serve'; 3 | import { RollupLivereloadOptions } from 'rollup-plugin-livereload'; 4 | 5 | type RollupOptionsResolver = ( 6 | config: Omit, 7 | options: { 8 | format: 'esm' | 'cjs' | 'umd'; 9 | mode: 'development' | 'production'; 10 | } 11 | ) => RollupOptions; 12 | 13 | type RollupPlaygroundOptionsResolver = ( 14 | config: Omit 15 | ) => RollupOptions; 16 | 17 | export declare interface UserConfig { 18 | /** 19 | * Entry point 20 | * @default 'src/index' 21 | */ 22 | input?: string; 23 | /** 24 | * Directory relative from root where build output will be placed. If the 25 | * directory exists, it will be removed before the build. 26 | * @default 'dist' 27 | */ 28 | outDir?: string; 29 | /** 30 | * Bundle formats 31 | * @default ['cjs','esm'] 32 | */ 33 | formats?: ['esm' | 'cjs' | 'umd']; 34 | /** 35 | * Name to expose in the UMD build. Use this option when you are using `umd` as one of the build formats. 36 | * @default 'camel-cased version of your package name' 37 | */ 38 | name?: string; 39 | /** 40 | * Disable code linting with ESLint. 41 | * @default false 42 | */ 43 | disableESLint?: boolean; 44 | /** 45 | * How Babel helpers are inserted into the Rollup bundle. 46 | * @default 'bundled' 47 | */ 48 | babelHelpers?: 'runtime' | 'bundled'; 49 | /** 50 | * Directly customize the underlying Rollup bundle. These options will be merged with Create React Package's internal Rollup options. 51 | * https://rollupjs.org/guide/en/#big-list-of-options 52 | */ 53 | rollupOptions?: Omit | RollupOptionsResolver; 54 | /** 55 | * Integrated playground configuration 56 | */ 57 | playground: { 58 | /** 59 | * Development server configuration 60 | * https://github.com/thgh/rollup-plugin-serve#options 61 | */ 62 | server: RollupServeOptions; 63 | /** 64 | * Configure development server livereload 65 | * https://github.com/thgh/rollup-plugin-livereload#options 66 | */ 67 | livereload: RollupLivereloadOptions; 68 | /** 69 | * Customize Rollup playground app bundle 70 | * https://rollupjs.org/guide/en/#big-list-of-options 71 | */ 72 | rollupOptions?: 73 | | Omit 74 | | RollupPlaygroundOptionsResolver; 75 | }; 76 | } 77 | 78 | /** 79 | * Type helper to make it easier to use crp.config.js 80 | * accepts a direct {@link UserConfig} object 81 | */ 82 | export declare function defineConfig(config: UserConfig): UserConfig; 83 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/src/utils.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import chalk from 'chalk'; 4 | import { paths } from './paths'; 5 | 6 | export function writeCjsEntryFile(packageName, filepath) { 7 | const safePackageName = sanitizePackageName(packageName); 8 | const contents = `'use strict'; 9 | if (process.env.NODE_ENV === 'production') { 10 | module.exports = require('./cjs/${safePackageName}.min.js'); 11 | } else { 12 | module.exports = require('./cjs/${safePackageName}.js'); 13 | }`; 14 | return fs.outputFileSync( 15 | path.join(filepath || paths.packageDist, 'index.js'), 16 | contents 17 | ); 18 | } 19 | 20 | export function resolvePath(relativePath) { 21 | return path.resolve(process.cwd(), relativePath); 22 | } 23 | 24 | export function checkTypescriptSetup() { 25 | return fs.existsSync(paths.packageTSConfig); 26 | } 27 | 28 | export function readPackageJsonOfPackage() { 29 | return fs.readJSONSync(paths.packagePackageJson); 30 | } 31 | 32 | export function sanitizePackageName(packageName) { 33 | return packageName 34 | .toLowerCase() 35 | .replace(/(^@.*\/)|((^[^a-z]+)|[^\w.-])|([^a-z0-9]+$)/g, ''); 36 | } 37 | 38 | function logError(error) { 39 | console.error( 40 | chalk.red( 41 | `${error.plugin === 'rpt2' ? 'typescript' : error.plugin || ''} ${ 42 | error.message 43 | }` 44 | ) 45 | ); 46 | 47 | if (error.frame) { 48 | console.log(error.frame); 49 | } else if (error.stack) { 50 | console.log(error.stack.replace(error.message, '')); 51 | } 52 | } 53 | 54 | export function logBuildError(error) { 55 | switch (error.plugin) { 56 | case 'eslint': 57 | // lintErrors are from custom eslint rollup plugin, already formatted 58 | if (error.lintErrors) console.error(error.lintErrors); 59 | else logError(error); 60 | return; 61 | default: 62 | logError(error); 63 | return; 64 | } 65 | } 66 | 67 | export function logBuildWarnings(warning, warn) { 68 | switch (warning.plugin) { 69 | case 'eslint': { 70 | const { lintWarnings } = warning; 71 | console.log(lintWarnings || warning); 72 | return; 73 | } 74 | case 'typescript': 75 | case 'rpt2': { 76 | const { loc, message } = warning; 77 | console.log(`\n${path.relative(process.cwd(), loc.file)}:`); 78 | console.log( 79 | ` ${chalk.bold(`Line ${loc.line}:${loc.column}:`)} ${message}` 80 | ); 81 | return; 82 | } 83 | default: 84 | // Use default for everything else 85 | warn(warning); 86 | return; 87 | } 88 | } 89 | 90 | // taken from react-dev-utils 91 | // https://github.com/facebook/create-react-app/blob/main/packages/react-dev-utils/clearConsole.js 92 | export function clearConsole() { 93 | process.stdout.write( 94 | process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H' 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /packages/react-pkg-scripts/test/utils.test.js: -------------------------------------------------------------------------------- 1 | import { sanitizePackageName } from '../src/utils'; 2 | 3 | describe('utils', () => { 4 | it('should generate safe package name', () => { 5 | expect(sanitizePackageName('@foo/bar')).toBe('bar'); 6 | expect(sanitizePackageName('@foo/bar-')).toBe('bar'); 7 | expect(sanitizePackageName('@foo/bar-baz')).toBe('bar-baz'); 8 | expect(sanitizePackageName('@FOO/BAR')).toBe('bar'); 9 | expect(sanitizePackageName('bar')).toBe('bar'); 10 | expect(sanitizePackageName('foo-baR')).toBe('foo-bar'); 11 | }); 12 | }); 13 | --------------------------------------------------------------------------------