Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compile an npm module into a single file, without dependencies

I'm trying to compile an the uncss npm module into a single .js file that is suitable for compilation by ExecJS. For example, the coffee script guy has this. The goal is to create a simple ruby wrapper for it, similar to ruby-coffee-script.

What I have tried to far:

  1. Came across an answer that suggested UglifyJS. Got nowhere with it.
  2. Used browserify, which should have done the trick, but it fails to compile lib/uncss.js with the following error message:

    Error: ENOENT, open 'tls' while resolving "tls" from file /home/prajjwal/code/uncss/node_modules/request/node_modules/forever-agent/index.js
    

I suppose this is because browserify does not have a proper shim for it? I'm also concerned about the shims that browserify replaces node modules with. Are they completely safe to use? I'm going to be embedding this into a ruby gem. Don't think browserify is what I should be using. Is there another way I can generate a stand alone .js from an npm module?

Any help appreciated.

like image 373
Prajjwal Avatar asked Mar 28 '14 10:03

Prajjwal


People also ask

Can you use npm packages without node?

Whether or not the library will work without Node will depend on the library. If it presents itself as a Node module, then you'll probably have to modify it (or find a compatible module loader for browser-side JS).

Does npm automatically install dependencies?

NPM installs devDependencies within the package. json file. The 'npm install' command should add all the dependencies and devDependencies automatically during installation. If you need to add specific devDependencies to your project, you can use this command- 'npm install --save-dev'.

Can you use npm without Webpack?

You don't need webpack nor babel to make an mpm module. Just put in any folder the files you want to distribute, specifying the main entry point and export elements on that file.


3 Answers

Browserify has a --standalone flag that can help here.

On the command line:

browserify -s moduleName --bare moduleName.js -o filename.js

In your node script you can import the concatenated module normally:

var moduleName = require('./filename');

However, you will may still need to ignore and/or stub out any tricky modules.

like image 183
xer0x Avatar answered Oct 11 '22 23:10

xer0x


Although it doesn't quite seem like it would be the right tool for the job, it appears that browserify is the closest thing to what you're looking for.

To be complete, here are the versions of the tools I used: Node v0.10.26 and browserify 3.38.0. I didn't test with other version, so they may have problems.

Here are the steps that I took after cloning uncss:

  1. npm install, which downloads and sets up the proper packages
  2. Due to some sort of versioning problem with NPM, I had to manually install the graceful-fs package (a dependency of one of uncss's dependencies) from Github (it wasn't available via npm)

    npm install https://github.com/isaacs/node-graceful-fs/tarball/v2.0.3
    
  3. At this point, I ran browserify. It turns out that browserify has a --bare flag, which does a couple of things:

    Alias for both --no-builtins, --no-commondir, and sets --insert-global-vars to just "__filename,__dirname". This is handy if you want to run bundles in node.

    With this flag, browserify doesn't inject its own shims for core modules. The full command I used was:

    browserify lib/uncss.js --bare > uncss.js
    

After doing the above, the file uncss.js contained uncss along with its bundled dependencies. Unfortunately, since browserify wraps everything inside its own require function, the now-bundled modules doesn't export anything initially.

$ node
> require('./uncss')
{}
>

To fix this, I had to change the initial line of the generated bundle from this:

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){

to this:

module.exports = (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require,ex;for(var o=0;o<r.length;o++)ex=s(r[o]);return ex})({1:[function(require,module,exports){

Note: it's not just the module.exports addition - there's some modification in the middle that was needed also*.

After that, the bundle seemed to work:

$ node
> require('./uncss')
[Function: init]
>

*: Essentially, browserify defines an inner function s(o, u) that acts like require. The surrounding code starts off by looping through what looks like a list of "main modules" (in this case, there's just one), requireing them, but not storing the result. It then returns s, the require-like function (why, I'm not sure) as the output of the entire anonymous function. All I had to do was add a variable to store the results, and then return that instead.

like image 29
voithos Avatar answered Oct 12 '22 00:10

voithos


While it is not impossible, it is a bit complicated, there is no tool that I know to do it automatically, but it coulb be done manually.

So, if you load a module in this way:

var async = require('async');

You can include the source of that module, first, declaring the module instance in your main script:

var global_async = null;

Then, include the module code inside an anonymous function and replace the "module.exports" with the global var you declared before:

module.exports = async

With

global_async = async;

Problem is that there are a lot of dependencies for "uncss", each one with some dependencies, so it is to much work to be done, but not impossible... but at the end, this module also requires some external binaries like "phantomjs".

If you want to build a gem that create a wrapper around "uncss" you can check if node and uncss are installed before anything, if not, install both, and then just call them, just like uncss does with phantomjs.

like image 40
Rodrigo Polo Avatar answered Oct 12 '22 00:10

Rodrigo Polo