How to manage configuration for Webpack/Electron app? - javascript

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

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?
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.
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.

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),

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.

Related

Nest.js bundle size too large by default

Upon running nest build command, I was expecting that the build would pick up only the imported modules from the node_module folder.
However, the command did not pick up only the relevent files and minifiy them. Instead, it picked up the node_modules directly from the root folder.
As a result, the final build size of the application is extremely large.
How can I reduce the bundle size?
I'm not sure that firing you from your job would solve your company's problems.
However, did you try the nest build --webpack command instead of the nest build command?
Maybe we would need some more informations about the project.
Did you also try to check this page on the documentation that says:
The webpack options file can contain standard webpack configuration
options. For example, to tell webpack to bundle node_modules (which
are excluded by default), add the following to webpack.config.js:
module.exports = {
externals: [],
};
If you have a webpack.config.js on your project, try removing the externals key from it.

Next.js - best way to serve static JS from a node module's "dist" folder

I'm working with an application that uses Tesseract (OCR) to read text from images.
I would like to take some JS files from node_modules/tesseract.js/dist and make them downloadable in the browser.
I know I can just copy the files to ./public and next.js will serve it statically from there, but then if I update my version of Tesseract, I may need to update those files as well. So maintenance becomes a problem.
My 1st thought is to customize my webpack config to copy the files from node_modules/tesseract.js/dist to ./public/tesseract (or something like that). That would make sure the files get re-copied if I update Tesseract. But I'm not particularly savvy with webpack and haven't figured out how to do that yet.
My 2nd thought was to "proxy" the retrieval of the JS file's content and just make the content available as a "page" in next.js (but this seems super hacktastic).
I feel like this is something that shouldn't be so complicated ... but I've not been able to figure it out myself yet.
Thanks in advance!
Yup agreed, updating your server to serve a node_modules path sounds a bit dangerous.
I personally would just copy over these files with webpack like you mentioned.
Here are the docs on Next.js on how to set up a custom webpack config.
next.config.js
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
webpack: (config) => {
// append the CopyPlugin to copy the file to your public dir
config.plugins.push(
new CopyPlugin({
patterns: [
{ from: "node_modules/tesseract.js/dist", to: "public/" },
],
}),
)
// Important: return the modified config
return config
}
};
I purposefully didn't include the public/tesseract path, I'm not sure if the CopyPlugin will automatically generate missing directories if they don't exist at build time.

Is webpack a suitable tool to bundle a Node.js backend code? [duplicate]

I was just wondering, I started using Webpack for a new project and so far it's working fine. I almost would say I like it better than Grunt, which I used before. But now I'm quite confused how and or I should use it with my Express back-end?
See, I'm creating one app with a front-end (ReactJS) and a back-end (ExpressJS). The app will be published on Heroku. Now it seems like I should use Webpack with ExpressJS as well to get the app up and running with one single command (front-end and back-end).
But the guy who wrote this blogpost http://jlongster.com/Backend-Apps-with-Webpack--Part-I seems to use Webpack for bundling all back-end js files together, which is in my opinion really not necessary. Why should I bundle my back-end files? I think I just want to run the back-end, watch my back-end files for changes and use the rest of Webpack's power just for the front-end.
How do you guys bundle the front-end but at the same time run the back-end nodejs part? Or is there any good reason to bundle back-end files with Webpack?
Why to use webpack on node backend
If we are talking about react and node app you can build isomorphic react app. And if you are using import ES6 Modules in react app on client side it's ok - they are bundled by webpack on the client side.
But the problem is on server when you are using the same react modules since node doesn't support ES6 Modules. You can use require('babel/register'); in node server side but it transipile code in runtime - it's not effective. The most common way to solve this problem is pack backend by webpack (you don't need all code to be transpile by webpack - only problematic, like react stuff in this example).
The same goes with JSX.
Bundling frontend and backend at the same time
Your webpack config can have to configs in array: one for frontend and second for backend:
webpack.config.js
const common = {
module: {
loaders: [ /* common loaders */ ]
},
plugins: [ /* common plugins */ ],
resolve: {
extensions: ['', '.js', '.jsx'] // common extensions
}
// other plugins, postcss config etc. common for frontend and backend
};
const frontend = {
entry: [
'frontend.js'
],
output: {
filename: 'frontend-output.js'
}
// other loaders, plugins etc. specific for frontend
};
const backend = {
entry: [
'backend.js'
],
output: {
filename: 'backend-output.js'
},
target: 'node',
externals: // specify for example node_modules to be not bundled
// other loaders, plugins etc. specific for backend
};
module.exports = [
Object.assign({} , common, frontend),
Object.assign({} , common, backend)
];
If you start this config with webpack --watch it will in parallel build your two files. When you edit frontend specific code only frontend-output.js will be generated, the same is for backend-output.js. The best part is when you edit isomorphic react part - webpack will build both files at once.
You can find in this tutorial explanation when to use webpack for node (in chapter 4).
This is my second update to this answer, which is beyond outdated by now.
If you need full a stack web framework in 2023, I'd recommend nextjs (which is built on top of react). No need to go around setting up anything, it just works out of the box, and is full stack.
On the other hand, if you need to compile your nodejs project written in typescript (which you should use as much as you can for js projects), I would use tsup-node.
You don't need to be a genius to imagine that in 3-5 years I'll come back to this and say this is really outdated, welcome to javascript.
This answer is outdated by now since node now has better support for ES modules
There's only a couple of aspects I can redeem the need to use webpack for backend code.
ES modules (import)
import has only experimental support in node (at least since node 8 up to 15). But you don't need to use webpack for them work in node.
Just use esm which is very lightweight and has no build step.
Linting
Just use eslint, no need to use webpack.
Hot reloading/restart
You can use nodemon for this. It's not hot reloading but I think it's way easier to deal with.
I wished I could refer to a more lightweight project than nodemon, but it does do the trick.
The blog post you shared (which is dated by now) uses webpack for having hot reloading. I think that's an overkill, opens a can of worms because now you have to deal with webpack config issues and hot reloading can also lead to unexpected behaviour.
The benefits we get from using tools like webpack on the frontend don't really translate to backend.
The other reasons why we bundle files in frontend is so browsers can download them in an optimal way, in optimal chunks, with cache busting, minified. There's no need for any of these in the backend.
Old (and terrible, but maybe useful) answer
You can use webpack-node-externals, from the readme:
npm install webpack-node-externals --save-dev
In your webpack.config.js:
var nodeExternals = require('webpack-node-externals');
module.exports = {
...
target: 'node', // in order to ignore built-in modules like path, fs, etc.
externals: [nodeExternals()], // in order to ignore all modules in node_modules folder
...
};
to use Webpack for bundling all back-end js files together, which is in my opinion really not necessary.
I think you are absolutely right. It's not necessary at all. I've been researching on this topic for a while. I've asked lots of questions on this topic, and up to this day, I haven't found yet a single "real" reason for one to use webpack on a Node.js back-end.
I'm not saying you can't or shouldn't set up a webpack-dev-server to develop your back-end code locally. But you definitely don't need to bundle your backend code on your build process.
webpack bundles are meant for the browser. Take a look at its official docs: Why webpack?. Historically, browsers never had a built-in module system, that's the reason why you need webpack. It basically implements a module system on the browser. On the other hand, Node.js has a built-it module system out of the box.
And I do re-use all of my front-end code for SSR on my server. The very same front-end files are run on my server as-is without any bundling (they are just transpiled, but the folder structured and number of files is the same). Of course I bundle it to run on the browser, but that's all.
To run on your Node.js server, simply transpile it with babel, without webpack.
Just use ["#babel/preset-env", { targets: { node: "12" }}], on your babel config. Choose the Node version of your backend environment.
backend
dist_app // BABEL TRANSPILED CODE FROM frontend/src
dist_service // BABEL TRANSPILED CODE FROM backend/src
src
index.tsx
frontend
src
App.tsx
public // WEBPACK BUNDLED CODE FROM frontend/src
You can perfectly render your frontend code on the server, by doing:
backend/src/index.tsx
import { renderToString } from "react-dom/server";
import App from "../dist_app/App";
const html = renderToString(<App/>);
This would be considered isomorphic, I guess.
If you use path aliases on your code, you should use babel-plugin-module-resolver.

Building a Webpack 4 Config for SASS (Compilation, Minification in Prod), JS (Concat + Minification), and Multiple Vue Single Page Applications

Trying to setup a webpack setup for my entire resource generation workflow in our app. I don't need any hot reloading, browsersync, etc. Just needs to watch for changes, and rebuild depedent objects when changes are made. File structure looks like this:
(I apologize for the image I seriously looked up other methods to post structure, but nothing was working on the preview, I can post a fiddle-or-codepen with this output in a comment afterwards)
The only areas of note are:
1) Each folder underneath vue-spas each Single-Page Mini App (SPAs) generates it's own output file of the app.
2) All items in constructs underneath js are concat and minified into one output file (/dist/js/main.min.js) but every item underneath js/components/ just renders a minified and mangled version of that file
Lastly, I understand this is probably a difficult question to wrap around, but I have Gulp doing some of it now, and Webpack doing the Vue compilation, just trying to see if it's possible to consolidate into just webpack and let it do the work.
Thanks for any assistance.
There are a few approaches. The simplest would be to add a key for each app in the entry point configuration in your webpack.config.js file.
That would look like this in your webpack.config.js file:
module.exports = {
entry: {
app1: './vue-spa/vue-spa1/app.js',
app2: './vue-spa/vue-spa2/app.js'
// etc
}
};
This will output individual directories in your dist for each with a nicely bundled app file.
By default, if you pass in a production variable to your webpack build command it'll apply all the optimizations you're looking for. If it's lacking out-of-the-box, there's likely a loader that can handle whatever optimization you need.
For Sass, you can add sass-loader to your webpack file:
https://github.com/webpack-contrib/sass-loader
Your devServer can just watch your vue-spa directory recursively for changes.
Another approach is to use lerna -- though that would require additional configuration, and is only necessary in your situation if you're in an enterprise environment where each individual app is being shipped out to a private NPM registry, and require individual semver.

webpack not copying images

I trying to import local package, that already bundled in webpack. When I build an app everything looks good, except webpack not copying images, fonts, etc. from that internal package. Any ideas what's going wrong?
When I try to reference my custom library, that is already bundled:
- dist
-- asdfjlsjdflasdjflsdaf.ttf
-- sdjflsjdfasjdflsjdfsd.eot
-- mylibrary.js
only mylibrary.js is copied. So parent app looks like:
- dist
-- mylibrary.js
-- parentapp.js
- packages
-- mylibrary
--- src...
--- dist...
Problem could be solved:
by increasing the loader limit and everything that converted to
base64 and included in mylibrary.js is available in my parent app
copy plugin, which copy fonts from mylibrary, but this solution looks ugly, since everyone who using mylibrary should care about copying fonts, images, etc.
A work around for copying images and fonts using webpack would be the use of GlobCopyWebpackPlugin.
How it works (I am not quite sure how your current webpack configuration looks like) but let`s go with a blank slate and see if this might help you. So, you can keep separate webpack files for different environments - say for now a prod and dev. Once you have created webpack.config.dev.js, inside the "plugins" property you can use GlobCopyWebpackPlugin. It would look something like below:
new GlobCopyWebpackPlugin({
"patterns": [
"Assets"
],
"globOptions": {
"cwd": path.join(process.cwd(), "src"),
"dot": true,
"ignore": "**/.gitkeep"
}
}),
"Assets" is the folder where you have kept fonts, icons, multimedia etc.
And then you can configure your tasks inside package.json
"scripts": {
"serve": "npm run -s generatelocalefiles && node --max_old_space_size=4096 ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --config webpack.config.dev.js --port=4300",
}
And the folder structure looks like this (just in case you are wondering)
-src
-app
-assets
-package.json
-webpack.config.dev.js
-webpack.config.prod.js
Hope this helps!
The best way to understand is to see how webpack takes care of images, fonts etc.
Here is a module that uses bootstrap and has the required concepts in action.
The index.html uses bootstrap styles and one glyph as well. The glyph (and bootstrap in all) uses one image from bootstrap binaries.
After you build the module, you can compare the paths of the image in original bootstrap binary (i.e. node_modules/bootstrap/dist/css/bootstrap.css) and the bundled output file (i.e. dist/bundle.js). You can search for glyphicons-halflings-regular.svg in those files.
The webpack simply
Copies images/fonts to your configured (in loader config) output folders
Updates those resource paths in the bundled output files (i.e. js, css etc)
(1) above need to be configured by you via a loader for ex. (2) will be automatically taken care by webpack provided the module.rules in webpack config are working properly for the images/fonts.
Hope that makes things a bit clear.

Categories

Resources