/ guide

Using nativescript-nodeify with Webpack

Issue #35 on the nativescript-nodeify project describes a bug that makes it incompatible with Webpack.

Unfortunately, many projects, including Antem require these two projects to work together. Here's how I hacked together a solution to make it work.

Step 0: Prerequisite

Run npm i --save nativescript-nodeify to install it. For more information, check out the project on GitHub

Step 1: Patch Webpack config

This step is adapted from this comment by @m-abs on GitHub. I have made my own modifications to this procedure for compatibility.

Add the following code to the top of webpack.config.js in the root of your NativeScript project.

// Add aliases
const shims = require('nativescript-nodeify/shims.json');
const aliases = {};
for (const key of Object.keys(shims)) {
        const value = shims[key];
        aliases[key + '$'] = value;
}
aliases['inherits$'] = 'inherits/inherits_browser';

// Remove hook, as this will only cause problems at this point.
// Checking and deleting within webpack ensures that it will be deleted during a cloud build.
let fs = require("fs");
let process = require("process");
if (fs.existsSync(__dirname + "/hooks/after-prepare/nativescript-nodeify.js")) {
	process.stdout.write("Found evil hook, deleting...\n");
	fs.unlinkSync(__dirname + "/hooks/after-prepare/nativescript-nodeify.js");
	process.stdout.write("Should be fixed now.\n");
} else process.stdout.write("Hooks seem clean, moving on.\n");

Next, find the config object that is exported by the config. That object contains a resolve object. Add aliasFields: ["browser"] to that object.

Finally, add ...aliases to the alias object inside that resolve object (see below).

The snippet below shows my resolve object after patching for clarity, however, it may be different depending on your template. Just pay attention to where ...aliases and aliasFields are located.

resolve: {
    aliasFields: ["browser"], // <-- ADDED LINE
    extensions: [".vue", ".js", ".scss", ".css"],
    // Resolve {N} system modules from tns-core-modules
    modules: [
        resolve(__dirname, "node_modules/tns-core-modules"),
        resolve(__dirname, "node_modules"),
        "node_modules/tns-core-modules",
        "node_modules",
    ],
    alias: {
        '~': appFullPath,
        '@': appFullPath,
        'vue': 'nativescript-vue', // <-- don't forget commas
        ...aliases // <-- ADDED LINE
    },
    // don't resolve symlinks to symlinked modules
    symlinks: false,
}

Step 2: Folders!

Unfortunately, you will have to manage two different project folders for this to work. For this guide, let's call your current project root ~/MyApp.

Create a new directory. We'll call it ~/MyApp-code, but you can call it whatever you want. It will house the code you will develop with. Building for devices or simulators will still be manged from ~/MyApp.

Step 2.5: Setting up MyApp-code

cd into ~/MyApp-code and run npm init. This will create a package.json file in MyApp-code.

Copy all of the dependencies from MyApp/package.json to MyApp-code/package.json. Delete any dependencies that are NativeScript-specific except nativescript-nodeify. Below is what mine looked like:

"dependencies": {
  "@trust/webcrypto": "^0.9.2",
  "ajv": "^6.5.4",
  "babel-polyfill": "^6.26.0",
  "base64-js": "^1.3.0",
  "crypto-browserify": "^3.12.0",
  "iconv-lite": "^0.4.24",
  "nativescript-nodeify": "^0.8.0"
}

Run npm i to install these dependencies.

Step 3: Browserify

Note that this step takes place in MyApp-code

Run this command to install browserify and aliasify.

npm i --save-dev browserify aliasify

You may also include other packages like tsify as per your individual requirements.

Add an aliasify field to your package.json as shown:

"aliasify": {
  "aliases": {
    "assert": "assert",
    // ...
  }
}

The contents of aliases should be the contents of nativescript-nodeify/shims.json.

Step 4: Poor man's derequire

For some reason, I couldn't get derequire to work at all, so I wrote my own script.

It replaces all instanes of require with _dereq_, and parses the output of browserify --ignore-missing, finding all modules that browserify asked for, but couldn't find. It reverts those _derequ_'s back to require's so Webpack can see them. It finally replaces all instances of global.nativeRequire with require, allowing you to selectively bypass browserify and use Webpack's require manually.

This doesn't parse any syntax, and so incidentally will transform strings and other variables. For my project, this was perfectly acceptible and hasn't caused any major issues.

The contents of this file can be found here. Do whatever you want with it.

For this guide, save the file as derequire.js in MyApp-code and make sure it's executable (on Linux/macOS, run chmod +x derequire.js).

I happen to use @trust/webcrypto in my project, so some more hacking was necessary, since this package is incompatible with browserify/webpack. I guess that's what I get for using something outside of its use case. Oops. Let me know by commenting on the issue if you want me to update this guide with instructions. Keep in mind that even this won't let you do everything that that package supports, as it's not pure JS.

Step 5: App code

Copy all of your app code from ~/MyApp/app to ~/MyApp-code. Change the name of your entry point as well. Since templates vary, we'll call the original entry point entrypoint.js and the new entry point index.js.

Step 6: Building

I use this command to bundle and transfer the app code to the app directory (after cd ~/MyApp-code):

npx browserify --ignore-missing index.js | ./derequire.js > entrypoint.js && cp -f entrypoint.js ../MyApp/app

Remember to change entrypoint.js and ../MyApp accordingly. Adapt the command if necessary according to your platform and needs. Tip: npx browserify is equivalent to the browserify command, but without having it installed globally (only in this folder).

The reason we save the file and copy it separately is to ensure that only one file change is sent to webpack if it's watching the file. I used to simply use (...) > ../MyApp/app/entrypoint.js but it would cause webpack to build the app twice at the same time, once with empty code, and once with the new code.

This command should be run before tns build (or other equivalent commands) and it can also be run while tns is watching the app directory to initiate a build.

Conclusion

While this solution works for my needs - even with cloud build! - it still is very hackish. You may have to play around with it to get everything working smoothly.

Questions? Comments? Talk to me via the original issue on GitHub.

Shameless plug: check out Antem, the project I've been building for the past year.