Monday, June 12, 2017

How to manage configuration for Webpack/Electron app?

Leave a Comment

I am using Webpack 2 and Electron to build nodejs application on Mac.

In my project in the root I have directory 'data' where I store configuration in a json like data/configurations/files.json (in practices there are different files with dynamic names)

After webpackaing though when I call: fs.readdirSync(remote.app.getAppPath()); to get files in the root I get only these packed: [ "default_app.js", "icon.png", "index.html", "main.js", "package.json", "renderer.js" ]

path.join(remote.app.getAppPath(), 'data/tests/groups.json'); called with FS ReadSync leads to an issue Error: ENOENT, data/tests/groups.json not found in /Users/myuser/myproject/node_modules/electron/dist/Electron.‌​app/Contents/Resourc‌​es/default_app.asar. So it seems that the whole data folder is not picked up by webpacker.

Webpack config is using json-loader and I did not find any documentation mentioning anything special about including specific files or jsons. Or do I have to reference json files in my code differently as they might be packed under main.js.

What is the best practice for Electron/Webpack for managing JSON config files? Am I doing something wrong when webpacking the project?

My project is based of https://github.com/SimulatedGREG/electron-vue using webpack/electron/vue

3 Answers

Answers 1

The Webpack Misconception

One thing to understand upfront is that webpack does not bundle files required through fs or other modules that ask for a path to a file. These type of assets are commonly labeled as Static Assets, as they are not bundled in any way. webpack will only bundle files that are required or imported (ES6). Furthermore, depending on your webpack configuration, your project root may not always match what is output within your production builds.

Based on the electron-vue documentation's Project Structure/File Tree, you will find that only webpack bundles and the static/ directory are made available in production builds. electron-vue also has a handy __static global variable that can provide a path to that static/ folder within both development and production. You can use this variable similar to how one would with __dirname and path.join to access your JSON files, or really any files.

A Solution to Static Assets

It seems the current version of the electron-vue boilerplate already has this solved for you, but I'm going to describe how this is setup with webpack as it can apply to not only JSON files and how it can also apply for any webpack + electron setup. The following solution assumes your webpack build outputs to a separate folder, which we'll use dist/ in this case, assumes your webpack configuration is located in your project's root directory, and assumes process.env.NODE_ENV is set to development during development.

The static/ directory

During development we need a place to store our static assets, so let's place them in a directory called static/. Here we can put files, such as JSONs, that we know we will need to read with fs or some other module that requires a full path to the file.

Now we need to make that static/ assets directory available in production builds.

But webpack isn't handling this folder at all, what can we do?

Let's use the simple copy-webpack-plugin. Within our webpack configuration file we can add this plugin when building for production and configure it to copy the static/ folder into our dist/ folder.

new CopyWebpackPlugin([     {       from: path.join(__dirname, '/static'),       to: path.join(__dirname, '/dist/static'),       ignore: ['.*']     } ]) 

Okay so the assets are in production, but how do I get a path to this folder in both development and production?

Creating a global __static variable

What's the point of making this __static variable?

  1. Using __dirname is not reliable in webpack + electron setups. During development __dirname could be in reference to a directory that exists in your src/ files. In production, since webpack bundles our src/ files into one script, that path you formed to get to static/ doesn't exist anymore. Furthermore, those files you put inside src/ that were not required or imported never make it to your production build.

  2. When handling the project structure differences from development and production, trying to get a path to static/ will be highly annoying during development having to always check your process.env.NODE_ENV.

So let's simplify this by creating one source of truth.

Using the webpack.DefinePlugin we can set our __static variable only in development to yield a path that points to <projectRoot>/static/. Depending if you have multiple webpack configurations, you can apply this for both a main and renderer process configuration.

new webpack.DefinePlugin({     '__static': `"${path.join(__dirname, '/static').replace(/\\/g, '\\\\')}"` }) 

In production, we need to set the __static variable manually in our code. Here's what we can do...

index.html (renderer process)

<!-- Set `__static` path to static files in production --> <script>     if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\') </script> <!-- import webpack bundle --> 

main.js (main process)

// Set `__static` path to static files in production if (process.env.NODE_ENV !== 'development') {     global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\') }  // rest of application code below 

Now start using your __static variable

Let's say we have a simple JSON file we need to read with fs, here's what we can accomplish now...

static/someFile.json

{"foo":"bar"} 

someScript.js (renderer or main process)

import fs from 'fs' import path from 'path'  const someFile = fs.readFileSync(path.join(__static, '/someFile.json'), 'utf8')  console.log(JSON.parse(someFile)) // => { foo: bar } 

Conclusion

webpack was made to bundle assets together that are required or imported into one nice bundle. Assets referenced with fs or other modules that need a file path are considered Static Assets, and webpack does not directly handle these. Using copy-webpack-plugin and webpack.DefinePlugin we can setup a reliable __static variable that yields a path to our static/ assets directory in both development and production.

To end, I personally haven't seen any other webpack + electron boilerplates handle this situation as it isn't a very common situation, but I think we can all agree that having one source of truth to a static assets directory is a wonderful approach to alleviate developer fatigue.

Answers 2

I think the confusion, (if there is any), might come from the fact that webpack not only "packs", embeds, things, code, etc... but also process content with its plugins.

html plugin being a good example, as it simply generates an html file at build-time.

And how this relates to the config file issue?, well depending on how you are "requiring" the "config" file, what plug-in you are using to process that content.

You could be embedding it, or simply loading it as text, from file system or http, or else...

In the case of a config file, that I guess you want it to be parsed at runtime,
otherwise it's just fancy hardcoding values that perhaps you could be better simply typing it in your source code as simple objects.

And again in that case I think webpack adds little to nothing to the runtime needs, as there is nothing to pre-pack to read at later use,

so I would possibly instead or "require"it, i'll read it from the file system, with something like :

// read it parse it relative to appPath/cwd,  const config = JSON.parse(     fs.readfileSync(          path.join( app.getAppPath(), "config.json" ),         "utf-8"     )) //note: look fs-extra, it does all that minus the app.path plus async 

and electron will read it from the file system , or if using Electron.require will read it from asar|fileSystem (in that order if I remember correctly, I could be wrong),

Answers 3

Webpack design philosophy is focused around very simple yet powerful concept:

Transform and bundle everything that is actually used by your app.

To achieve that webpack introduces a powerful concept of dependency graph, which is able to manage virtually any kind of dependencies (not only *.js modules) by the means of so-called loaders.

The purpose of a loader is to transform your dependency in a way that makes statement import smth from 'your_dependency' meaningful. For instance, json-loader calls JSON.parse(...) during loading of *.json file and returns configuration object. Therefore, in order to take advantage of webpack dependency resolution system for managing JSONs, start from installing json-loader:

$ npm install --save-dev json-loader 

Then modify your webpack.config.js in the following way:

module.exports = {   ...   module: {     rules: [       {test: /\.json$/, use: 'json-loader'}     ]   }   ... }; 

At this point webpack should be able to resolve your JSON dependencies by their absolute paths, so the following should work (I assume here that you have a subdirectory config of your root context dir, containing file sample.json):

import sampleCfg from './config/sample.json'; 

But importing physical paths doesn't lead to elegant, robust and maintainable code (think of testability, for example), so it is considered a good practice to add aliases to your webpack.config.js for abstracting away your physical .config/ folder from your import statements

module.exports = {   ...   resolve: {     alias: {       cfg: './config'     }   }   ... } 

Then you'll be able to import your JSON config like that:

import sampleCfg from 'cfg/sample.json' 

Finally, if you use SimulatedGREG/electron-vue Electron project template (as you mentioned in your post), then you have three webpack configuration files:

  • .electron-vue/webpack.web.config.js - use this config file if you use this template just for ordinary web development (i.e. not for building native Electron projects);

  • .electron-vue/webpack.main.config.js - use this file to configure webpack module that will run inside Electron's main process;

  • .electron-vue/webpack.renderer.config.js - use this file for Electron's renderer process.

You can find more information on main and renderer processes in the official Electron documentation.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment