facebook / create-react-app

Set up a modern web app by running one command.
https://create-react-app.dev
MIT License
102.23k stars 26.69k forks source link

Provide a watching build mode that writes to the disk in development #1070

Open andreypopp opened 7 years ago

andreypopp commented 7 years ago

See https://github.com/facebookincubator/create-react-app/issues/1018#issuecomment-261792488 for a discussion which leads to the creation of this issue.

I propose adding a new command (working name build-dev) which watches for changes and rebuild the bundle (effectively, given the current CRA implementation, it should execute webpack --watch), then writes it onto filesystem.

The main motivation is an easier integration path with non-Node.js dev servers: such servers could just serve continuously updating bundle from the filesystem.

The only desired configuration options is an --output, which could probably be specified via a command line argument:

% npm run build-dev -- --output ../static/www

Alternative command name suggestions:

ioloie commented 5 years ago

Now that this has been released, are further changes required to support the option?

Nargonath commented 5 years ago

@ioloie AFAIU from @Friss the feature needs to be implemented. His PR only updated the package version but we need the behavior to be enabled in webpack-dev-middleware through the use of environment variables as he suggested.

suxin1 commented 5 years ago

What is the new way to do this?

ioloie commented 5 years ago

This pull request provides another environment variable called WRITE_TO_DISK. So adding WRITE_TO_DISK=true in the relevant place will enable the feature in the underlying webpack dev server

simon-iribarren commented 5 years ago

An easy solution I find, is to use the already provided 'configFactory' (from config/webpack.config.js) to generate a development configuration for webpack:

//scripts/devConfig.js
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';
const configFactory = require('../config/webpack.config');

// Generate configuration
module.exports = () => configFactory('development');

and then in the package.json create a watcher script:

  "scripts": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "devWatcher": "webpack --config scripts/devConfig.js --watch",
    "test": "node scripts/test.js"
  },

you might also want to define a output folder in the webpack.config file, because I think that currently is only defined for prod

    output: {
      // The build folder.
      path: isEnvProduction ? paths.appBuild : undefined,

obviously I'm refering to a ejected project.

Nargonath commented 5 years ago

I also put on NPM a lib that helps you do it without ejecting: https://github.com/Nargonath/cra-build-watch while we wait for this PR to get merged.

dariusrosendahl commented 5 years ago

An easy solution I find, is to use the already provided 'configFactory' (from config/webpack.config.js) to generate a development configuration for webpack:

//scripts/devConfig.js
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';
const configFactory = require('../config/webpack.config');

// Generate configuration
module.exports = () => configFactory('development');

and then in the package.json create a watcher script:

  "scripts": {
    "start": "node scripts/start.js",
    "build": "node scripts/build.js",
    "devWatcher": "webpack --config scripts/devConfig.js --watch",
    "test": "node scripts/test.js"
  },

you might also want to define a output folder in the webpack.config file, because I think that currently is only defined for prod

    output: {
      // The build folder.
      path: isEnvProduction ? paths.appBuild : undefined,

obviously I'm refering to a ejected project.

@Simonig:

This works great! We're only using it to create react applications to integrate it into and e-commerce platform. What do I need to change to only watch multiple applications?

ifokeev commented 5 years ago

my similar to @dariusrosendahl solution:

you need install rewire first:

yarn add rewire

scripts/start-writeon-disk.js

#!/usr/bin/env node

const rewire = require('rewire')
const defaults = rewire('react-scripts/scripts/start.js')

const createDevServerConfig = defaults.__get__('createDevServerConfig')
defaults.__set__('createDevServerConfig', (...args) => ({
  ...createDevServerConfig(...args),
  writeToDisk: true,
}))

package.json

  "scripts": {
    "start": "./scripts/start-writeon-disk.js",
    ...
  }

NOTE: it doesn't drop CSS files this way

route2Dev commented 5 years ago

I found this to be a helpful implementation: https://gist.github.com/int128/e0cdec598c5b3db728ff35758abdbafd

If this could cleanup the temp files gererated and be incorporated into CRA, that would be awesome!

cowwoc commented 5 years ago

I'm interested in this PR minus the watch bits. I don't want my CI smoke tests to run optimized bits because it is extremely slow. My less-frequent integration tests might run it, but the main CI pipeline should not.

Should I open a separate PR for this or will this PR target this use-case as well?

cherukumilli commented 5 years ago

webpack-dev-middleware has an option writeToDisk and it can be enabled via webpack-dev-server (>=3.1.10 required)

With my PR above to get us to the required version of webpack-dev-server we could then add in some environment var(s) to enable the saving of files to disk. This could be 2 vars, 1 to enable and another to decide what directory to use. By default it uses dist. Or just one var to declare where to save the files and that enables the option to be set.

writeToDisk is no longer working with create-react-app v3. I don't see it create the /dist folder. It used to work with create-react-app v2. Trying to see if anyone else encountered this issue with cra v3? and if they have a solution to it?

Nargonath commented 5 years ago

How did you set it as this PR was never merged? Are you doing it with an ejected project?

cherukumilli commented 5 years ago

@Nargonath - I forked this project and updated the react-scripts/config/webpackDevServer.config.js file.

    quiet: true,
    // WebpackDevServer has an option to write every build to disk. This can be
    // useful for certain use cases such as developing browser extensions where one
    // would need to otherwise do full builds.
    writeToDisk: true,

The above setting with a few other changes like

  "homepage": ".",

got my local dev environment working in CRA2.x

After merging in CRA 3.x, this stopped working. I don't see the /dist folder anymore.

wondering if anyone else is facing this issue?

Nargonath commented 5 years ago

Oh alright, didn't think of that option. 😄

devloco commented 4 years ago

From my point-of-view, the biggest benefit to using create-react-app is that the maintainers make sure that all the included packages work well together. That kind of "quality assurance" is very very valuable. To me that's way more valuable than the dev server config.

Anyway, I'll throw this out there: it's actually not that difficult to maintain a fork of facebook/react-scripts. And by doing so, there's no need to eject and no need to worry about breaking changes by future versions of CRA. You can take your time merging any new features from create-react-app into your fork.

I've been doing that for about a year now for create-react-wptheme. That's just a wrapper for running create-react-app with the --scripts-version command line arg, pre-populated with the URL for the npm of my react-scripts fork.

The source for my react-scripts fork is here. When possible, I try to add files rather than modify the original react-scripts files (look inside the config and scripts subfolders there and note any files with wp in their name).

I did have to write my own little web socket server and client to get browser auto-refresh working. But I'm using the actual error overlay from CRA, which is another huge chunk of code that I don't have to worry about. I'd say I have 200-300 lines of custom code, and I rarely change them at all.

Then when there's a new CRA release, it takes me maybe an hour or two to merge the changes into my fork. Thus I'm benefiting from all the work that the CRA devs do to test package combinations and bug fixes/work-arounds. Tasks that would be painful to do by myself.

cowwoc commented 4 years ago

I don't see why this feature would have to be mutually exclusive to the existing behavior. Both features can live side-by-side and there is a strong benefit to shipping it standard (not as a fork).

Nargonath commented 4 years ago

Perhaps there are complications we are not foreseeing. 🤷‍♂

DevAlone commented 4 years ago

Would be awesome to have this feature. I'm developing a browser extension with react and didn't find anything better than using npm-watch(which is terribly slow cuz it rebuilds everything on every single change).

pupudu commented 4 years ago

If it helps anyone, this can be easily done using the npm patch-package module. (See here for how patch-package works: https://www.npmjs.com/package/patch-package)

Changes to be made

1) Add this entry to node_modules/react-scripts/config/webpackDevServer.config.js

writeToDisk: process.argv.includes('--writeToDisk'),

2) Define a constant in node_modules/react-scripts/config/webpack.config.js just below the isEnvProduction constant

const writeToDisk = process.argv.includes('--writeToDisk');

3) Change the following entries (note the additional || writeToDisk usage)

const publicPath = isEnvProduction || writeToDisk
    ? paths.servedPath
    : isEnvDevelopment && '/';
  const publicUrl = isEnvProduction || writeToDisk
    ? publicPath.slice(0, -1)
    : isEnvDevelopment && '';
output: {
      // The build folder.
      path: isEnvProduction || writeToDisk ? paths.appBuild : undefined,

After making these changes, run the patch package command.

Now starting the app as yarn start --writeToDisk will start the app in dev mode, while writing the changes to the disk.

Nargonath commented 4 years ago

@pupudu You would need to eject your project for that solution to work, wouldn't you?

pupudu commented 4 years ago

@Nargonath not really. There is this https://www.npmjs.com/package/patch-package module, which can be used to do small patches to a node_module package. Doing a patch is simpler than making a fork, or ejecting CRA.

To give some more context; we tried both other approaches before (ie. ejecting and forking). But they soon become cumbersome if you plan to stay in sync with new releases of react-scripts. Using a patch is easier in the sense when you upgrade react-scripts, you can simply check if the watch script is working, and fix it on the spot if it is not. Since patches are committed and code reviewed, that's less decoupled than a fork.

Nargonath commented 4 years ago

I totally agree with you on this. I just thought that your solution implied to eject but I was wrong apparently. 😃

adamschoenemann commented 4 years ago

This would be great. Any reason why #7812 was ignored? Genuinely interested :)

pupudu commented 4 years ago

@adamschoenemann no idea. I guess they don't like having flags

Nargonath commented 4 years ago

Didn't see that the PR has been locked. That's a pity. I'd be curious to know what obstacles there are to the adoption of such feature, if any.

berpcor commented 4 years ago

Hello. I have checking of permissions by component name. But name is obfuscated after npm run build. How to fix that?

Nargonath commented 4 years ago

@berpcor I don't see what your question has to do with the current issue being discussed. I may have understood you wrong, but it seems you'd better open a new issue for that.

Nilegfx commented 4 years ago

is there any chance for this to be considered as a important feature? it is being requested since 2016 !!!

JoshMcCullough commented 4 years ago

My 2 cents ... would like to be able to run a "quicker" build before pushing, as a Git hook e.g. to do the transpiling, etc. to sanity check the developer's work. Currently we do this with react-scripts build but it does a lot of extra things we don't really care about for the purposes of the pre-push hook.

baurine commented 4 years ago

guys, any process for this issue?

akanshgulati commented 4 years ago

@gaearon May be we can add a flag like this to build and compile every time,

BUILD=true react-scripts start

koyadovic commented 4 years ago

A workaround, if using Bash, is to execute something like this:

yarn build && inotifywait -m -r -e modify -e attrib -e move -e create -e delete public src |
while read events; do
  pkill -9 sleep
  pkill -9 yarn
  sleep 2 && yarn build &
done

This waits for changes into public or src directories. If so, kills a possible previous execution of yarn and launches yarn build in the background again.

Friss commented 4 years ago

Now that you can specify the port and host for the dev tools via ENV vars I was able to get a pretty nice setup for developing a CRA app and Express backend.

I have this route handler that basically checks if a requested file can be served from the dev server. It uses fetch from node-fetch.

const fetch = require('node-fetch');
if (process.env.NODE_ENV !== 'production') {
  app.use(async (req, res, next) => {
    try {
      console.log('attempting', `http://localhost:3000${req.path}`);

      const response = await fetch(`http://localhost:3000${req.path}`);
      const headers = response.headers;
      const body = await response.arrayBuffer();

     // If no headers that means we didn't get a response worth sending back move on to next route.
      if (!headers) {
        return next();
      }

      if (headers.get('content-type')) {
        res.setHeader('content-type', headers.get('content-type'));
      }

      if (headers.get('content-length')) {
        res.setHeader('content-length', headers.get('content-length'));
      }

      res.send(new Buffer.from(body));
    } catch (e) {
      console.error(e);
      next();
    }
  });
}

And a basic template engine

app.engine('template', async (filePath, options, callback) => {
  let template = app.locals.appTemplate;

  if (isTest) {
    return callback(null, '');
  }

  if (!isProd) {
    try {
      template = await fetch('http://localhost:3000').then((res) => res.text());
    } catch (e) {}
  }

 // Handle how you want to serve the build HTML in production. I personally serve it from a DB. 
if (!template) {
 return BUILT_HTML_OUTPUT;
}

  return callback(null, template);
});
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'template');

Then you can run your express app on a different port say 3001 and start CRA with:

"start": "WDS_SOCKET_HOST=localhost WDS_SOCKET_PORT=3000 react-scripts start",
josephr5000 commented 4 years ago

@Nargonath - I forked this project and updated the react-scripts/config/webpackDevServer.config.js file.

    quiet: true,
    // WebpackDevServer has an option to write every build to disk. This can be
    // useful for certain use cases such as developing browser extensions where one
    // would need to otherwise do full builds.
    writeToDisk: true,

The above setting with a few other changes like

  "homepage": ".",

got my local dev environment working in CRA2.x

After merging in CRA 3.x, this stopped working. I don't see the /dist folder anymore.

wondering if anyone else is facing this issue?

@Nargonath I'm one of the many who need this feature, and in scanning I found the writeToDisk: true solution and your comment saying it didn't work in CRA 3.x.

I'm using CRA 3.4.1 and just tried this today and found it works perfectly! I hope that provides you some encouragement to try it again.

For routing to work I also needed to set homepage in package.json, but didn't need to make any other changes. yarn start did a dev build and left the results in the dist directory. Very helpful!

Nargonath commented 4 years ago

@josephr5000 Alright, thanks for letting me know. I should try it out again. This would simplify much of my cra-build-watch.

josephr5000 commented 4 years ago

@josephr5000 Alright, thanks for letting me know. I should try it out again. This would simplify much of my cra-build-watch.

@Nargonath FYI I have noticed this setting is fragile. I think yarn add seems to feel free to "refresh" my node_modules, and finds this modification a nice thing to "refresh". I do occasionally have to re-apply this, so if yours stops working check out the config file to make sure your setting is still there. If I weren't so lazy I'd look for a more permanent fix, but this solves my immediate problem.

Nargonath commented 4 years ago

I developed a lib which monkey patches the node_modules for that exact purpose so I don't think it would have the same problem as you have but thanks for letting know. :wink:

ibraheemdev commented 3 years ago

Is there any update on this? Is there any other way to achieve a development hot reloading output to a folder? This is useful when you serve static files (/build) from a server and need hot reloading without rebuilding every time.

joaobibiano commented 3 years ago

I need this, because the same reason above... we have a serve that serves our CRA... in development we need the debug bundle, not just in memory.

ibraheemdev commented 3 years ago

For anyone else who has this issue, I ended up going with a combination of this gist and the cra-build-watch package. The package is probably the best and most configurable solution, but I just needed a simple script:

process.env.NODE_ENV = "development";

const fs = require("fs-extra");
const paths = require("react-scripts/config/paths");
const webpack = require("webpack");
const config = require("react-scripts/config/webpack.config")("development");

// the output directory of the development files
outputPath = paths.appPath + "/dist";
config.output.path = outputPath;

// update the webpack dev config in order to remove the use of webpack hotreload tools
config.entry = config.entry.filter((f) => !f.match(/webpackHotDevClient/));
config.plugins = config.plugins.filter((p) => !(p instanceof webpack.HotModuleReplacementPlugin));

(async () => {
  await fs.emptyDir(outputPath)
  webpack(config).watch({}, (err) => {
    if (err) {
      console.error(err);
    } else {
      // copy the remaining thing from the public folder to the output folder
      fs.copySync(paths.appPublic, outputPath, {
        dereference: true,
        filter: file => file !== paths.appHtml
      });
    }
  });
})();
bjankord commented 3 years ago

@iansu @ianschmitz @mrmckeb @Timer @gaearon Is there any interest with adding the writeToDisk flag into create-react-app proper as @pupudu described here? https://github.com/facebook/create-react-app/issues/1070#issuecomment-541260446

It looks like there was a PR to add this capability but it was closed purely out of the stale PR bot coming to close it. Would be nice to get that reopened and reviewed if maintainers think this would be a valuable addition to add into create-react-app.

From a consumer perspective, it would be helpful for being able to pipe the built files through express for faster developer feedback when working on an express/create-react-app.

The ability to write files to disk with a watch mode is also a feature that vue-cli has which I've found to be quite useful.

mrmckeb commented 3 years ago

We actually utilise something similar in my company, I agree there's value in this feature... but I'm not sure we can get it out for 4.0 though. I'll flag it for discussion.

pupudu commented 3 years ago

Great to see this being discussed. I am happy to update the PR and reopen when we are good to go 👍

lewisl9029 commented 3 years ago

@pupudu, was looking at your PR just now, looks like it changes the publicUrl as well as other unrelated behavior based on the flag (whether or not to open the browser). Just wanted to raise that there are many use cases for writing the dev bundle to disk outside of serving dev assets with a different server, that might require the publicUrl to remain unchanged:

For instance, building an unoptimized build for faster CI (https://github.com/facebook/create-react-app/issues/1960, https://github.com/facebook/create-react-app/issues/1070#issuecomment-488349515), developing browser extensions (https://github.com/facebook/create-react-app/issues/1070#issuecomment-357787414) or electron apps (https://github.com/facebook/create-react-app/issues/1070#issuecomment-386155494) using the CRA dev server for faster feedback loop with fast refresh and faster builds (no need to optimize), and for interacting with other developer tools that might make use of the unoptimized bundle (I'm actually working on something right now that processes dev bundles, and currently it requires patching CRA).

I think @ioloie's PR (https://github.com/facebook/create-react-app/pull/6144) that only adds a WRITE_TO_DISK env var is the more flexible option, since everything else your PR changes can already be controlled through existing env vars independently from WRITE_TO_DISK behavior (see BROWSER, PUBLIC_URL): https://create-react-app.dev/docs/advanced-configuration/

bjankord commented 3 years ago

I've been using https://www.npmjs.com/package/cra-build-watch as a work-around for this issue. I like the idea of a WRITE_TO_DISK flag. I think it would be awesome if when that flag is set, it applies the webpack config changes that are set in the cra-build-watch package.

efranca commented 3 years ago

@bjankord Thanks for the hint! It serves perfectly for my need as well. It would be very nice to have this feature out of the box. Anyway, https://www.npmjs.com/package/cra-build-watch works just great!!!

cseas commented 3 years ago

This feature would be extremely useful for anyone who's trying to inject some server data or trying to do some server-side templating instead of just serving the build as it is with a static server.

This blog explains the use case very well. Also #1703

The main problem with trying to use Express with create-react-app is that react-scripts start doesn't output anything so there's nothing to serve with Express in development mode with hot reload. This usually is not a problem if the server isn't doing anything in index.html (proxy works fine). But if we want to use any sort of templating using the server then it becomes a challenge for development because while developing we can't serve our react app with Express.

We already have cra-build-watch that does this. Isn't there a way to include that feature as part of create-react-app? It doesn't have to be the default behaviour but something the user can enable with a flag when they know what they want.

Luk-z commented 3 years ago

I'm working on CRA + SSR, this feature would be appreciated. Currently I accomplished it using nodemon and on every file changes -> yarn build && yarn ssr. But this is not performant and probably I will switch soon to webpack. Without this feature and working with SSR, eject + webpack is inevitable...

poelzi commented 3 years ago

I seriously can't understand this bug is open for 5 years already. If you want to give a prototype to testers, you want the version deoployed, but with full debug informations for recording the tracebacks etc. I don't understand why there is no debug build, seriously, I have not seen any compiler that forces you to build optimized builds only....

sancelot commented 3 years ago

Agreeing with @poelzi