Requiring/running JavaScript outside of node-webkit application - javascript

Suppose I have the following app structure:
outer-folder/
├── my-app/
└── settings.js
where my-app/ is either the unbuilt directory that contains package.json or the packaged application my-app.exe or my-app.app.
I cannot package settings.js with the rest of my app, because I want this file to be editable by users, and loaded upon startup to configure my app. Moreover, I plan to allow settings.js to reside anywhere (for example, in the user's home directory) and be loadable from there.
This works fine when the app is unbuilt. I just have a relative path to the file and require() it like usual. But when the app is built (with grunt-node-webkit-builder if that makes a difference) this fails and I get the dreaded "Cannot find module" error.
I've searched the node-webkit wiki but can't find anything about this issue, am I missing something? What is the best way to load and run an external JavaScript file, like one would do with require(), from a packaged node-webkit app?

I suggest you to use the application data path.
See the documentation here
An exemple
var gui = require('nw.gui');
var path = require('path');
var yaml = require('js-yaml');
var fs = require('fs');
var confPath = path.join(gui.App.dataPath, 'conf', "dev-conf.yml");
try {
conf = yaml.load(fs.readFileSync(confPath, 'utf-8'));
} catch (err) {
throw new Error("Cannot read or parse configuration file '"+confPath+"': "+err);
}
It's a good pratice to separate code and configuration, and App.dataPath aims at the application specific folder in user's application data (different for each OS).
I generally use an installer to copy my configuration file into it.
Next tip: I prefer using YAML instead of JSON for my configuration or settings, because it allows you to insert comments inside the file.

Related

Add node module to chrome extension

I am currently tying to add this single node module to my chrome extension: https://www.npmjs.com/package/get-urls Except I always get the require is not defined error. I have googled this and all I have been able to find are chrome extension templates to use but those give them same require is not defined error as well (and they are confusing to set up). I have built a chrome extension before using the https://www.npmjs.com/package/fuse.js/v/3.4.3 module. I was able to get it to work by adding it into the manifest.json file like this:
"background": {
"scripts": ["node_modules/fuse.js/dist/fuse.js", "background.js"]
}
But I am unable to do this with the get-urls file as the index.js file has require() in it.
'use strict';
const {URL} = require('url');
const urlRegex = require('url-regex');
const normalizeUrl = require('normalize-url');
When I npm installed the get-urls It also installed the folders that all these things are requiring. My question is will I be able to add the node_modules path to each index.js file and remove the require at the top of the files? each index.js file is requiring each other so they are all connected.
Is there an easier way to do this that I am missing?

Conditional javascript source code

Background:
I have 3 different URLs, one per environment (dev, test, prod), and I don't want to expose all the URLs in the client (source code).
How can I expose in the client code, just the one corresponding to the environment in context?
Note: As I understand, I need to do something in the build process using environment variables (I'm using node.js). However, I don't want to touch anything related with webpack, as what I'm trying to do is a standalone package that can be imported in any application regardless of the framework they are using. Webpack plugins/configuration are not an option, but I can use any npm package if required.
During your build process, you can check the environment variable and then copy over a config file. For example, you could keep your URIs in /config/<env>.js, and then copy/rename it to /settings.js during the build. Your URL could be exported from that.
The following npm package fits my requirements completely https://www.npmjs.com/package/config , you can load conditional files based on the node environment variable NODE_ENV, so when NODE_ENV=development, the file /config/development.js is used to create the build. you can use different extensions for the config files, also you can customize the config folder path by changing the environment variable $NODE_CONFIG_DIR heres an example:
const config = require('config');
process.env.$NODE_CONFIG_DIR = './' // relative path ./config
const url = config.get('url');
//if NODE_ENV is development will load the file config/development.js
console.log(url);

How to manage configuration for Webpack/Electron app?

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.

Is there a way to import strings from a text file in javascript, meteor?

I have a programm where I need to have long multi line strings. It's a pain to store them in the .js document, because js doesn't have multi line strings and I end up having a twice as long as the screen width line looking as ugly as "This is an example.\n"
Is there a way to have a txt file, from where I can import strings with new lines (or at least just import strings)?
There is a Meteor Assets object that allows you to read files in the private directory of your app, in the following way for example for text files.
Assets.getText("foo.txt", function (err, res) { ... });
See full documentation: http://docs.meteor.com/#assets
Previous answer works only for public files. If you want to access file data that is visible only on the server you should probably use 'fs' npm module. It's described in details here: http://www.eventedmind.com/posts/meteor-file-uploader-part-2-server-side-save
The meteor-yaml package makes this easy - it automatically loads any .yaml files in your project, parses them into JavaScript objects, and makes them available in YAML.data.
In my application I have some code outside of the meteor app that needs the same settings, so I prefer to have the config file outside of the meteor project directory. Then I load the file like this:
var fs = Npm.require('fs');
fs.readFile('<path to file>.yaml', 'utf8', function(err, data) {
if(err) {
//Throw exception if the file is missing
throw new Error("Missing config file")
}
else {
//Read the file into a JavaScript object
config = YAML.parse(data);
}
});
Unfortunately, the meteor-yaml package is a little out of date with how the meteor team wants node packages to be loaded now, so if you're using a recent version of meteor the package won't work out of the box.
I filed a bug about this, but in the meantime to get around it I installed it as a private package, as opposed to installing it from atmosphere, and fixed the bug. To do this:
Clone the repo under your projects packages/ directory
Comment out the Npm.require lines.
Add a call to depends:
Npm.depends({yamljs: "0.1.4"});
Run meteor. Meteor will detect the meteor-yaml private package and install the dependencies.

node.js require cannot find custom module

Here is the project structure:
/
app.js
package.json
/node_modules
/app
config.json
/frontend
assets and html tpls
/modules
couch.js
raeume.js
users.js
I require config.json, raeume.js and users.js from app.js and it all works fine.
var config = require('./app/config');
var raeume = require('./app/modules/raeume');
var users = require('./app/modules/users');
Then I require config.json and couch.js from user.js the same way and it won't find anything.
var couch = require('./app/modules/couch');
var config = require('./app/config');
I guess it should find it. After some research I saw a diverse landscape of problems inclusive how node is compiled. Thus included: I work on osx 10.8 with node v0.10.7.
The path is relative to the directory in which you are requireing the files, so it should be something like:
var couch = require('./couch');
var config = require('../config');
A bit of clarification, if you write
var couch = require('./couch');
you are trying to require the couch module which resides in the current directory, if you write
var couch = require('couch');
you are trying to require the couch module installed via npm.
The current accepted answer is correct and properly answers the question.
Although not directly related to the original question, I'd like to add another point here for all those people who got the Cannot find module error for local files although the correct relative path was specified.
Assuming a file with the name couch.js exists in the current directory,
On case insensitive filesystems (like NTFS on Windows), both these lines will work -
var couch = require('./couch');
var couch = require('./Couch');
However, on a case-sensitive filesystem (like ext4 on Linux), require('./couch') will work but require('./Couch') will not.
There is a page in the NodeJS docs regarding this.
I hope this helps someone whose perfectly working code stopped working after moving from Windows/Mac to Linux. 😅
Here is how you do it :
var users = require('./../modules/users');
It must be:
var config = require(../../app/config)
var couch = require(./couch) (same directory)
Also double check the extension on the file being imported. I accidentally used .ts in a JavaScript project, and this prevented it from being found. Once I changed the extension to .js, require was able to find it fine.

Categories

Resources