reactql / kit

ReactQL starter kit (use the CLI)
https://reactql.org
228 stars 38 forks source link

Webpack configuration customizations #34

Closed zachlendon closed 7 years ago

zachlendon commented 7 years ago

One of the greater challenges with the upgradeability of ReactQL versions in base projects is managing the customization of webpack configurations with the pulling in of new ReactQL versions. The different configurations are broken out so well (browser_dev, browser_prod, base, serverdev, etc.) that ultimately it seems much cleaner to make changes in those files then to have a separate override webpack config file (or files) where modifications are defined and merged with the kit's configurations. With a separate file, one just has another layer to look through in the code to see how the ultimate configuration is getting built (i.e., base, server, server + another config?) However, the alternative is every upgrade to the base 'kit' is ultimately a practice of pulling in the kit then manually altering the appropriate configurations. Maybe that's the "best balance that can be struck between functioning yet configurable base project", but I wonder if there's some changes that could be made.

Ultimately, while most of the default kit/webpack defaults/selections are great/fine, I've seen some challenges in just adopting them wholesale:

1) The desire to use Webpack's environment plugin (https://webpack.js.org/plugins/environment-plugin/) to manage environment variables between environments. This can be useful for example when one would have different GraphQL URLs per environment (and ultimately this makes using config/path.js without modifications difficult - should the current URL be a config value perhaps? Sorry, getting off-track). Should the environment plugin perhaps be defined in the various configs but empty (unless there are default values that make sense) and then overrideable outside the kit by ReactQL user's in a way that can be aligned with the different webpack env configs? In short, I'm just wondering if users can have better hooks for their own environment variables, like the GraphQL URL.

2) The current configuration is not Docker friendly on Linux (at least on Centos 7.2). Challenges include: a) compression-webpack-plugin – one of the options here is to compress the bundles using ‘zopfli’ which has to run a postInstall scripts that bombs on linux. The fallback is to use ‘gzip’ as the compression algorithm (this works - I wonder if this could/should be a more easily overrideable config?) image-webpack-loader – the file compression optimizations libraries that are used for optimizing image files in the bundles are really hard to get loaded on Centos; the primary library that pukes is pngquant. The fallback here, for now, is to use the standard ‘file-loader’ from webpack to bundle these files, and remove the loader from the project

Ultimately one can, with these changes, get the application building with Docker (using Node 8 and npm 5).

Changing these configurations in #2 seems reasonable for users to do - they are good defaults, but changing them shouldn't in my mind be destructive to the objectives of the ReactQL project - i.e., the webpack files are broken out and written in such a way to make them changeable/modifiable - but how should users best do this while still having upgradeable paths to pull in the latest and greatest ReactQL changes?

leebenson commented 7 years ago

Thanks for this analysis @zachlendon - I have some thoughts on solving your two points that I'll get over later today when I'm back in the office and not limited to my phone.

leebenson commented 7 years ago

Addressing the first point (i.e. custom Webpack), the files in kit/webpack export a webpack-config object, so they're all extendable out-the-box.

Let's say you want to extend browser_prod.js, you might do something like this:

kit/webpack/custom_browser.js

// Tooling to build on the existing Webpack config 
import WebpackConfig from 'webpack-config';

// Set the entry point of what we want to extend
export default new WebpackConfig().extend('[root]/browser_prod.js').merge({
  // Put here whatever we want to merge into the final config
  plugins: [
    new SomeCustomPlugin(),
  ]
});

Then, in your package.json, either change an existing script command or create a new one, by changing the WEBPACK_CONFIG variable to point to your new custom_browser.js file -- like so:

{
  "scripts": {
    "build-browser": "cross-env NODE_ENV=production WEBPACK_CONFIG=browser_prod webpack --colors",
  },
}

... becomes:

{
  "scripts": {
    "build-browser": "cross-env NODE_ENV=production WEBPACK_CONFIG=custom_browser webpack --colors",
  },
}

This way, if any of the underlying kit files change, you'll automatically inherit them by extending vs. overwriting.


Regarding the second point -- I'd recommend building your own Docker files from scratch rather than using the Node images. You can then add Python, gcc/compilers, etc which should be able to handle the image and compression binaries -- and generally result in a smaller image, to boot.

I'll write a sample Dockerfile, and keep this issue open to track its release.

leebenson commented 7 years ago

On the point of environment vars --

There are definitely ways the API could be made 'cleaner' to make that stuff definable outside of editing scripts. Stuff like APOLLO, for example.

The one issue with that approach is that it assumes customisation is limited to scalar values. For many projects, customisation means physical changes to the logic of the app, which inevitably means changing stuff inside of kit or elsewhere in ReactQL proper.

The modus operandi of this kit is to become the baked-in foundation of a new project. It's not 'upgradable' in the traditional sense of there being a separate API alongside your own app code. The two are intertwined to a degree that will make it difficult to separate, the moment you start to move away from basic edits to src/app.js.

I'm open to ideas on how to play this better. But the 'official' advice when upgrading is just to spawn a new project, copy src over, and make sure any changes you've made still make sense in the new version.

zachlendon commented 7 years ago

Thank you very much @leebenson for the detailed responses.

The webpack configuration merging you suggest works for me - I mentioned that in my initial paragraph ('where modifications are defined and merged with the kit's configurations') and while I see some drawbacks to it (one more layer of configuration), it seems the best tradeoff I'd agree so I'm full steam ahead with that approach personally.

As to the docker images, I am not using the base Node images - Node 8 doesn't work as best I know and even 7.10 doesn't work with Full ICU, which is applicable in my use case...I have code similar to (after pulling in the base Centos Image) the following, before the application specific code:

WORKDIR /app

RUN yum -y install wget gcc-c++ tar make \ && wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo \ && wget https://raw.githubusercontent.com/srl295/btest402/master/btest402.js -O /app/btest402.js \ && curl --silent --location https://rpm.nodesource.com/setup_8.x | bash - \ && yum -y install yarn \ && yum -y install nodejs \ && yum clean all

Runs a test to check the intl setup

RUN node /app/btest402.js && rm /app/btest402.js

I'll be interested to see what you craft.

On the environment variable issue, I think the APOLLO variable is the unique issue as it's the one config option in 'config/' that I'd argue is quite likely to be replaced by users for any real-world project usage. I understand and agree with the ideas around use of 'src' and 'kit', I guess to me there was just 1 thing in config that was a hangup and I was hoping for a change for it. Ultimately the worst case is that to automate upgrading a bit I'll just script the search and replace of that GraphQL URL to something that is wired up in a webpack environment plugin. I kinda thought something like the environment variable configuration for the apollo uri might be useful for others, but I'm all for waiting to see if there's a clamoring down the road for it. Ultimately, I have a path forward, it was (just as these all are) simply discussion items around what I was experiencing as the friction (which overall is minor) between upgrading the project and what customizations felt reasonable to use it in non-example/demo use cases.

leebenson commented 7 years ago

The Dockerfile has been released in 1.7.0

Bump the ReactQL cli with npm i -g reactql to start a project with the latest version.

Then in your project folder, build the Docker image in the usual way:

docker build . -t your_project

You can then run with:

docker run -it -p 4000:4000 your_project

.. which should spawn the server on http://[docker_host]:4000.

The initial build time will take ~10-15 minutes (depending on your connection speed), due to building from scratch. The early build steps will only be repeated when changes are made to your local package.json -- after that, re-building the image should be only minimal overhead above the regular NPM build steps, since those initial steps will be cached.

I'll put together more comprehensive documentation in the docs shortly.


Re: APOLLO, I think there's a good case for overriding that variable specifically, with something as simple as:

export const APOLLO = {
  uri: process.env.APOLLO || 'https://api.graph.cool/simple/v1/cinomw2r1018601o42x5z69uc',
};

I'll have a think about it some more; there may be a better way than just 'shadowing' the demo GraphCool endpoint. My concern is that forgetting to set the environment variable will mean a bunch of React components expecting a certain GraphQL API, and getting something entirely. It may be that explicitly setting that variable elsewhere is a better way to go -- I'll have a think.

leebenson commented 7 years ago

I've just released ReactQL v2.0.0, which introduces userland configuration of GraphQL -- as well as the option to run a built-in GraphQL server. See the release tag for details.

This will generally be the approach for config settings going forward, so I'll go ahead and close this thread.