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/Resources/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 require
d or import
ed (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?
Using
__dirname
is not reliable inwebpack
+electron
setups. During development__dirname
could be in reference to a directory that exists in yoursrc/
files. In production, sincewebpack
bundles oursrc/
files into one script, that path you formed to get tostatic/
doesn't exist anymore. Furthermore, those files you put insidesrc/
that were notrequire
d orimport
ed never make it to your production build.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 yourprocess.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 require
d or import
ed 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.
0 comments:
Post a Comment