All Projects → harrysolovay → Rescripts

harrysolovay / Rescripts

Licence: mit
💥 Use the latest react-scripts with custom configurations for Babel, ESLint, TSLint, Webpack,... ∞

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to Rescripts

Dotfiles
My configuration. Minimalist, but helps save a few thousand keystrokes a day.
Stars: ✭ 284 (-71.37%)
Mutual labels:  customization, configuration
dotfiles
🔧 .files - different setups separated in branches
Stars: ✭ 168 (-83.06%)
Mutual labels:  customization, configuration
.tmux
🇫🇷 Oh my tmux! My self-contained, pretty & versatile tmux configuration made with ❤️
Stars: ✭ 15,594 (+1471.98%)
Mutual labels:  customization, configuration
Craco
Create React App Configuration Override, an easy and comprehensible configuration layer for create-react-app
Stars: ✭ 5,285 (+432.76%)
Mutual labels:  customization, configuration
Ec 471g
黑苹果配置 hackintosh dsdt clover v3489 e1 471g ec 471g v3 471g acer mac osx efi ssdt aml config plist hd4000 gt630m inter
Stars: ✭ 30 (-96.98%)
Mutual labels:  configuration
Consul Conf
🐳 Consul.conf provides responsive web interface for managing configuration of your services in Consul key-value storage through beautiful and customizable dashboards, distributed as a lightweight Docker image.
Stars: ✭ 25 (-97.48%)
Mutual labels:  configuration
Configuron
Clojure(Script) config that reloads from project.clj when in dev mode
Stars: ✭ 24 (-97.58%)
Mutual labels:  configuration
Hzdtf.foundation.framework
基础框架系统,支持.NET和.NET Core平台,语言:C#,DB支持MySql和SqlServer,主要功能有抽象持久化、服务层,将业务基本的增删改查抽离复用;提供代码生成器从DB生成实体、持久化、服务以及MVC控制器,每层依赖接口,并需要在客户端将对应实现层用Autofac程序集依赖注入,用AOP提供日志跟踪、事务、模型验证等。对Autofac、Redis、RabbitMQ封装扩展;DB访问提供自动主从访问,Redis客户端分区。特别适合管理系统。
Stars: ✭ 22 (-97.78%)
Mutual labels:  configuration
Configuration
Library for setting values to structs' fields from env, flags, files or default tag
Stars: ✭ 37 (-96.27%)
Mutual labels:  configuration
Translucenttb
A lightweight utility that makes the Windows taskbar translucent/transparent.
Stars: ✭ 8,816 (+788.71%)
Mutual labels:  customization
Openlauncher
Customizable and Open Source Launcher for Android
Stars: ✭ 945 (-4.74%)
Mutual labels:  customization
Angular Builders
Angular build facade extensions (Jest and custom webpack configuration)
Stars: ✭ 843 (-15.02%)
Mutual labels:  customization
Cookbook
🎶 Cookbook for Nette Framework (@nette) & Contributte (@contributte). Read it while its HOT!
Stars: ✭ 30 (-96.98%)
Mutual labels:  configuration
Android Customtoast
Easy to use Custom Toast Library for Android
Stars: ✭ 24 (-97.58%)
Mutual labels:  customization
Lilconfig
Zero-dependency nodejs config seeker.
Stars: ✭ 35 (-96.47%)
Mutual labels:  configuration
Config Rs
⚙️ Layered configuration system for Rust applications (with strong support for 12-factor applications).
Stars: ✭ 915 (-7.76%)
Mutual labels:  configuration
Cas Configserver Overlay
Generic CAS Spring Cloud Configuration Server WAR overlay
Stars: ✭ 28 (-97.18%)
Mutual labels:  configuration
Coldnew Emacs
coldnew's emacs config
Stars: ✭ 32 (-96.77%)
Mutual labels:  configuration
Emacs.dz
Awesome emacs config files
Stars: ✭ 886 (-10.69%)
Mutual labels:  configuration
Yamlsettings
Yaml Settings Configuration Module
Stars: ✭ 12 (-98.79%)
Mutual labels:  configuration

banner


Take control of your create-react-app project configurations. No ejecting, no custom react-scripts fork, no limitations.

Highlights

  • 🎯 create the perfect config with minimal effort

  • 🎩 take advantage of cutting-edge software that hasn't made its way into CRA

  • 🥳 draw from a library of open-source "rescripts"

  • 👽 compatibility with "rewires" designed for react-app-rewired

Guide

Background

CRA (create-react-app) provides a first-class React developer experience. For building single-page web apps, it's not only the fastest bootstrap––it's also the most carefully-curated, well-supported, and feature-fledged. There is a downside, however: in an effort to create stability and simplicity for beginners, its creators excluded many configuration options and newer technologies (such as Babel transformations based on early-stage TC39 proposals). CRA comes with an "eject" script which––once irreversibly run––allows customization of the "start", "build", and "test" scripts, along with their corresponding configurations. While this does allow you some DX freedom, it isn't always preferable; ejection makes it impossible to upgrade to new versions of react-scripts, and it exposes a lot of tedious, knarly-lookin' code. Rescripts is for developers who don't want to eject or worry about configuration, but still want to use cutting-edge tools.

Tim Arney's react-app-rewired was the first project to successfully tackle this problem. It offered a solution that led to many "rewires" (community-made plugins for simpler setup). But––when CRA 2.0 came around––there were some breaking changes. Not to mention, the react-app-rewired DX was something to be further simplified.

Rescripts tackles this same probem for CRA 2.0+ with several key DX differences. First off, it was designed to be more of a focal point for all non-standard configuration. The underlaying loader can handle deeply nested "rescripts" (conceptually similar to babel plugins), all of which can modify any CRA process. The tools used to transform configuration are more robust and flexible than its predecessor's (@rescripts/utilities), and should weather most updates. The API also exposes a middleware entry, so that you can track your configurations as they are transformed. It should also be noted that Rescripts is compatible with many Webpack rewires built for react-app-rewired.

If you like this framework, please tweet at @gaearon requesting an "everything-i-did-not-include" rescript!

Installation

Install @rescripts/cli as a dev dependency:

npm i -D @rescripts/cli

[email protected]^2.1.2 requires you be using at least @rescripts/utilities^0.0.4 and @rescripts/cli^0.0.7

Install the rescript(s) you wish to use:

npm i -D @rescripts/rescript-env

@rescripts/rescript-env scans your package.json & project root for Babel, ESLint and TSLint configuration files. If present, these configurations will override the CRA defaults.

Basic Usage

1) Replace react-scripts calls with rescripts calls

package.json

{
  "name": "built-with-rescripts",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.6.1",
    "react-dom": "^16.6.1",
    "react-scripts": "2.1.1"
  }
  "devDependencies": {
    "@rescripts/cli": "^0.0.11",
    "@rescripts/rescript-env": "^0.0.10"
  }
  "scripts": {
-   "start": "react-scripts start",
+   "start": "rescripts start",
-   "build": "react-scripts build",
+   "build": "rescripts build",
-   "test": "react-scripts test",
+   "test": "rescripts test",
-   "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}

2) Define a 'rescripts' field and specify which to use

package.json

{
  "name": "built-with-rescripts",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.6.1",
    "react-dom": "^16.6.1",
    "react-scripts": "2.1.1"
  }
  "devDependencies": {
    "@rescripts/cli": "^0.1.0"
  }
  "scripts": {
    "start": "rescripts start",
    "build": "rescripts build",
    "test": "rescripts test"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ],
+ "rescripts": [
+   "env"
+ ]
}

You could also––instead of placing this in your package.json––specify your "root rescript" in a root-level .rescriptsrc file (with whatever convention you prefer: .js, .json, or no extension.)

3) Use the newly-enabled feature(s)

In the case of @rescripts/rescript-env, you will now be able to use custom Babel, ESLint and TSLint configurations. Use any of the following conventions:

Babel: place config inside of a root-level .babelrc, .babelrc.js, .babelrc.json, or babel.config.js file, or inside of the babel field of your package.json

ESLint: place config inside of a root-level.eslintrc, .eslintrc.js, .eslintrc.json, or eslint.config.js file, or inside of the eslintConfig field of your package.json

TSLint: place config inside of a root-leveltslint.js or tslint.json file

4) Good practice with the env rescript

@rescripts/rescript-env actually installs 3 rescripts:

For an incrementally faster boot time, use these independently and actually specify their configurations. Aka...

.rescriptsrc

module.exports = [
  ['use-babel-config', '.babel.json'],
  ['use-tslint-config', 'tslint.json'],
]

Advanced Usage

Your root rescript should be an array of other rescripts. Some rescripts take in options and/or other parameters. Some do not. Some contain functions that transform your webpack config. Some contain transformations for any combination of processes (webpack, devServer and jest). Consider the following:

In this example, the root rescript makes reference to @rescripts/rescript-env. This rescript takes in no arguments, which means that it has to scan your project at every run.

module.exports = ['env']

Alternatively, you could use @rescripts/rescript-use-babel-config and @rescripts/rescript-use-eslint-config (or @rescripts/rescript-use-tslint-config if you prefer TypeScript):

module.exports = [
  ['use-babel-config', '.babelrc'],
  ['use-eslint-config', '.eslintrc'],
]

This example illustrates how arguments can be passed to a rescript by wrapping its reference inside of another array and adding the arguments as subsequent elements.

The eventual goal of Rescripts is to provide a single, simple interface for deep customizations:

.rescriptsrc.js

module.exports = [
  [
    'use-babel-config',
    {
      presets: ['react-app'],
      plugins: [
        'react-require',
        [
          'module-resolver',
          {
            root: '.',
            alias: {
              '~': './src',
            },
          },
        ],
      ],
    },
  ],
  [
    'use-eslint-config',
    {
      extends: ['react-app'],
      plugins: ['ramda'],
      rules: {
        'ramda/compose-pipe-style': 'error',
        'react/react-in-jsx-scope': 0,
      },
    },
  ],
]

Rescript Structure

Rescripts transform the default configurations used by the three main processes of CRA (webpack, its developement server, and test-running via Jest). Rescripts can do much more though, such as writing logs, caching files, commiting changes and triggering other processes.

A rescript can be...

an array of other rescripts

child-rescript.js

// define child rescript
module.exports = ['rescript-a', 'rescript-b', 'rescript-c']

parent-rescript.js

// use child rescript
module.exports = [require.resolve('path/to/child-rescript')]
a function that takes in and returns a webpack config

child-rescript.js

// define child rescript
module.exports = config => {
  const newConfig = doSomethingToTheConfig(config)
  return newConfig
},

parent-rescript.js

// use child rescript
module.exports = [require.resolve('path/to/child-rescript')]
an object containing (any combination of) `webpack`, `devServer`, and `jest` functions, which take in and return their respective configs

child-rescript.js

// define child rescript
module.exports = {
  webpack: config => {
    const newConfig = transformWebpackConfig(config)
    return newConfig
  },
  devServer: config => {
    const newConfig = transformDevServerConfig(config)
    return newConfig
  },
  jest: config => {
    const newConfig = transformJestConfig(config)
    return newConfig
  },
}

parent-rescript.js

// use child rescript
module.exports = [require.resolve('path/to/child-rescript')]
a function that takes in arguments and outputs a new rescript

child-rescript.js

// webpack only:
module.exports = options => config => {
  const newConfig = someTransformation(config, options)
  return newConfig
}

// or with multiple processes:
module.exports = (webpackArg, devServerArg, jestArg) => ({
  webpack: config => {
    const newConfig = transformWebpackConfig(config, webpackArg)
    return newConfig
  },
  devServer: config => {
    const newConfig = transformDevServerConfig(config, devServerArg)
    return newConfig
  },
  jest: config => {
    const newConfig = transformJestConfig(config, jestArg)
    return newConfig
  },
})

parent-rescript.js

// use child rescript

// webpack only:
module.exports = [
  [require.resolve('path/to/child-rescript'), 'some important webpack arg'],
]

// multiple processes:
module.exports = [
  [
    require.resolve('path/to/child-rescript'),
    'webpackArg',
    'devServerArg',
    'jestArg',
  ],
]
a combination of formats

child-rescript.js

// define child rescript
module.exports = [
  'some-rescripts',
  [
    'rescript-that-takes-args',
    {
      docsQuality: 'helpful?',
    },
  ],
  config => {
    const newConfig = doSomethingToTheConfig(config)
    return newConfig
  },
  [
    someArg => config => {
      const newConfig = doSomethingToTheConfig(config, someArg)
      return newConfig
    },
    'some argument',
  ],
]

parent-rescript.js

// use child rescript
module.exports = [require.resolve('path/to/child-rescript')]

Rescript SDK

The @rescripts/utilities package makes it far easier to interact with configuration, while also reducing code size and the amount of conflict you'd otherwise see from composing numerous rescripts. You can use the tools in this package to identify and transform parts of any configuration without an exact path.

npm i -D @rescripts/utilities

@rescripts/utilities comes with @rescripts/cli (so there's no need to install if you're already working on a rescripted project)

Reference

For FP-lovers: all of @rescripts/utilities' methods are curried, so feel free to call them in stages. Use Ramda's R.__ placeholder to reorder how arguments are pieced together in the resulting functions.

getPaths(predicate, scanTarget)

Recursively traverses your config (or any object for that matter) and returns an array of paths to any nodes that match the predicate. This is useful for editing parts of a config that may change location at runtime (ostensibly because of another rescript in the transformation pipeline).

usage example
const {getPaths} = require('@rescripts/utilities')

const isBabelLoader = inQuestion =>
  inQuestion && inQuestion.loader && inQuestion.loader.includes('babel-loader')

module.exports = config => {
  const babelLoaderPaths = getPaths(isBabelLoader, config)
  console.log(babelLoaderPaths) // [['module', 'rules', 2, 'oneOf', 1]]
  return config
}

edit(transform, paths, config)

Takes in a transformation function and the paths at which that function should be applied, along with the object on which to apply it.

usage example
const {getPaths, edit} = require('@rescripts/utilities')

module.exports = config => {
  const paths = getPaths(somePredicate, config)
  return edit(
    matchedSection => {
      // change something about the subsection
      const updatedSection = someTransformation(matchedSection)
      return updatedSection
    },
    paths,
    config,
  )
}

replace(replacement, paths, config)

Works the same as edit, only it takes in a replacement for the specified path rather than a transformation function.

usage example
const {getPaths, replace} = require('@rescripts/utilities')

module.exports = config => {
  const paths = getPaths(somePredicate, config)
  return replace('some replacement', paths, config)
}

remove(paths, config)

Takes in the specified path and the object for the path-specified deletion.

usage example
const {getPaths, remove} = require('@rescripts/utilities')

module.exports = config => {
  const paths = getPaths(somePredicate, config)
  return remove(paths, config)
}

getWebpackPlugin(constructorName, config)

Retrieve a plugin instance from the webpack config with the plugin's constructor name.

usage example
const {getWebpackPlugin} = require('@rescripts/utilities')

module.exports = config => {
  getWebpackPlugin('ForkTsCheckerWebpackPlugin', config) &&
    console.log('TypeScript enabled')
  return config
}

prependWebpackPlugin(pluginInstance, config)

Add a plugin instance to the first slot of the Webpack configuration's plugins array.

usage example
const {prependWebpackPlugin} = require('@rescripts/utilities')
const WebpackBuildNotifierPlugin = require('webpack-build-notifier')

module.exports = config => {
  return prependWebpackPlugin(
    new WebpackBuildNotifierPlugin({
      title: 'Rescripted App',
      logo: require.resolve('./public/icon.png'),
      suppressSuccess: true,
    }),
    config,
  )
}

// or simplified...

module.exports = prependWebpackPlugin(
  new WebpackBuildNotifierPlugin({
    title: 'Rescripted App',
    logo: require.resolve('./public/icon.png'),
    suppressSuccess: true,
  }),
)

appendWebpackPlugin(pluginInstance, config)

Add a plugin instance to the last slot of the Webpack configuration's plugins array.

usage example
const {appendWebpackPlugin} = require('@rescripts/utilities')
const WebpackBuildNotifierPlugin = require('webpack-build-notifier')

module.exports = config => {
  return appendWebpackPlugin(
    new WebpackBuildNotifierPlugin({
      title: 'Rescripted App',
      logo: require.resolve('./public/icon.png'),
      suppressSuccess: true,
    }),
    config,
  )
}

// or simplified...

module.exports = appendWebpackPlugin(
  new WebpackBuildNotifierPlugin({
    title: 'Rescripted App',
    logo: require.resolve('./public/icon.png'),
    suppressSuccess: true,
  }),
)

editWebpackPlugin(transform, constructorName, config)

Applies the transform function to the Webpack plugin whose constructor name is a match.

usage example
const {editWebpackPlugin} = require('@rescripts/utilities')

module.exports = config => {
  const edited = editWebpackPlugin(
    p => {
      p.someOption = 'changed'
      return p
    },
    'DefinePlugin',
    config,
  )
  return edited
}

// or simplified...

module.exports = editWebpackPlugin(
  p => {
    p.someOption = 'changed some option'
    return p
  },
  'DefinePlugin',
  config,
)

replaceWebpackPlugin(replacement, constructorName, config)

Replaces the matched plugin with another.

usage example
const {replaceWebpackPlugin} = require('@rescripts/utilities')
const WebpackPWAManifestPlugin = require('webpack-pwa-manifest')

module.exports = config => {
  const replaced = replaceWebpackPlugin(
    new WebpackPWAManifestPlugin({
      name: 'Rescripted App',
      short_name: 'Example',
      description: 'An example app that uses Rescripts',
      background_color: '#fff',
      crossorigin: 'use-credentials',
      icons: [
        {
          src: require.resolve('./public/icon.png'),
          sizes: [96, 128, 192, 256, 384, 512],
        },
      ],
    }),
    'ManifestPlugin',
    config,
  )
  return replaced
}

removeWebpackPlugin(constructorName, config)

Remove the matched plugin from your config.

usage example
const {removeWebpackPlugin} = require('@rescripts/utilities')

module.exports = config => {
  const withoutIgnorePlugin = removeWebpackPlugin('IgnorePlugin', config)
  return withoutIgnorePlugin
}

// or simplified ...

const {removeWebpackPlugin} = require('@rescripts/utilities')

module.exports = removeWebpackPlugin('IgnorePlugin', config)

Middleware

The term "middleware" in Rescripts describes a kind of rescript that runs between all other rescripts.

Let's say your stack of rescripts looks like this:

const logConfig = config => {
  console.log(config)
  return config
}

logConfig.isMiddleware = true

module.exports = [
  ['use-babel-config', '.babelrc'],
  ['use-tslint-config', 'tslint.json'],
  logConfig,
]

The execution order will be as follows:

  1. logConfig
  2. use-babel-config
  3. logConfig
  4. use-tslint-config
  5. logConfig

Don't be afraid to track data in the outer scope:

const equal = require('deep-equal')
let lastConfig = null

const logConfig = config => {
  const unchanged = equal(config, lastConfig)
  console.log(unchanged ? 'config unchanged' : 'config changed')
  lastConfig = config
  return config
}

logConfig.isMiddleware = true

module.exports = [
  ['use-babel-config', '.babelrc'],
  ['use-tslint-config', 'tslint.json'],
  logConfig,
]
In simplified form
const equal = require('deep-equal')
let lastConfig = null

module.exports = [
  ['use-babel-config', '.babelrc'],
  ['use-tslint-config', 'tslint.json'],
  Object.assign(
    config => {
      const unchanged = equal(config, lastConfig)
      console.log(unchanged ? 'config unchanged' : 'config changed')
      lastConfig = config
      return config
    },
    {isMiddleware: true},
  ),
]

We prefer to keep and mutate a lastConfig reference incase other middleware is applied before logConfig; middleware isn't spread around other middleware (this would be chaos), and yet middleware can transform what's passed to subsequent rescripts (including other middleware). This can get messy if you're not deliberate about your middleware's behavior.

Rescript Library

Miscellaneous

Thank you for checking out (and maybe even building software with) Rescripts. If you have any bug reports or feature ideas, please go ahead and file an issue. If you have any other questions, comments, etc., please reach out to [email protected].

Acknowledgements

Big shout out to...

This library has been released under the MIT license

SO DO WHATEVER THE $%#@ YOU WANT WITH IT!!!

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].