Load ES5 and ES6 npm modules with traceur - javascript

I'm using traceur to enable ES6 development under node.js via traceur.require.makeDefault. This works without issue.
I use Gemfury as a private npm repository, this also works. I'm creating my own npm packages using ES6. The challenge I ran into was how to support loading both ES5 and ES6 modules. The typical traceur.require.makeDefault example code block skips anything that has the node_modules directory in the path.
Here's how I solved this problem:
var fs = require('fs');
var path = require('path');
var traceur = require('traceur');
require('traceur-source-maps').install(traceur);
function find_parent_package(filename) {
var current = path.dirname(filename);
while (true ) {
var package_json = current + '/package.json';
if (fs.existsSync(package_json))
return require(package_json);
var last_slash = current.lastIndexOf('/');
if (last_slash === -1) return null;
current = current.substring(0, last_slash);
}
}
traceur.require.makeDefault(function(filename) {
if (filename.indexOf('package.json') > -1) return false;
if (filename.indexOf('node_modules') === -1) return true;
var p = find_parent_package(filename);
return p && p.es6;
});
And an example of a package.json for an ES6 module that works with the above code (some information has been removed for privacy):
{
"name": "ps-core",
"version": "0.2.0",
"private": true,
"es6": true,
"dependencies": {
"basic-auth": "^1.0.0",
"express": "^4.10.6",
"express-hal": "0.0.1",
"express-session": "^1.10.1",
"traverson": "^0.15.0"
}
}
Given a particular npm module path, this code walks up the directory tree until it finds a package.json file, reads it, and inspects the es6 key. If true, then it allows traceur to handle the file.
My question is this: Given the above implementation, is there a better way to do this?

Related

how to create simple nodejs script to wrap an binary/executable?

I want to create a nodejs file, that simple runs/wraps an executable binary file with all inputs and outputs.
For now at least on windows.
Why:
wanted to install an executable tool over npm install -g, to have it in console, without PATH changes. (npm global packages are included in PATH)
i used such solution:
const path = require("path");
const spawnSync = require('child_process').spawnSync;
const pathToMyExe = path.join(__dirname, 'bin', 'myfile.exe'); //just path to exe
const input = process.argv.slice(2); //minus "node" and "this js" arguments
spawnSync(pathToMyExe, input, {stdio: 'inherit'});
but for ".exe to PATH" problem, there is a simplier way (if you want windows only).
just set bin property in package.json to pathToExe.
https://docs.npmjs.com/files/package.json#bin
You can use the bin-wrapper npm package.
EDIT:
There's a better alternative called bin-manager.
Installation:
$ npm install --save bin-manager
Usage
const bmanager = require('bin-manager');
const base = 'https://github.com/imagemin/gifsicle-bin/raw/master/vendor';
const bin = bmanager('bin', 'gifsicle')
.src(base + '/macos/gifsicle', 'darwin')
.src(base + '/linux/x64/gifsicle', 'linux', 'x64')
.src(base + '/win/x64/gifsicle.exe', 'win32', 'x64')
.use(process.platform === 'win32' ? 'gifsicle.exe' : 'gifsicle');
bin.run(['--version'], (err, out) => {
if (err) {
console.log(error);
return;
}
console.log(out.stdout);
});

Work around to unsolved npm link symlink requires?

When developing an NPM package, it's common to use:
npm link
It allows to modify <myPackage> under development without the need of publishing and unpublishing all the time! The developer can make any changes locally and see it immediately.
It's installed into a project by using:
npm link <myPackage>
It's great, but there's a problem if the <myPackage> have a require(path).
It'll use the real location of <myPackage> as __dirname, for example, instead of the expected location of the symlink, that should be local to the project, like a regular node_module.
The solution I found so far, for my particular case works fine:
module.exports = {
loadImage: function (filename) {
var img
if (typeof window !== 'undefined' && ({}).toString.call(window) === '[object Window]') {
try {
img = require('../../src/images/' + filename)
} catch (e) {
// Development only
img = require('./template/src/images/' + filename)
}
} else {
img = '/assets/images/' + filename
}
return img
}
}
But as you can imagine, this cause Warning messages in the Browser.
While I'm aware of the reason why of this problem, ideally, I'd like to suppress the error.
I believe it won't be a very popular question, in that case, here's a nice option that won't cause any warning messages and that is quite specific to the NPM Package development stage.
The following modules.exports expose some code snippets you'd like to import into your application. You'll find the loadImage method, with a fallback for the require(path):
module.exports = {
loadImage: function (filename) {
if (typeof window !== 'undefined' && ({}).toString.call(window) === '[object Window]') {
return (process.env.NPM_PACKAGE_DEV && require('./template/src/images/' + filename) ||
require('./template/src/images/' + filename))
} else {
return '/assets/images/' + filename
}
},
isBrowser: function () {
return (typeof window !== 'undefined' && ({}).toString.call(window) === '[object Window]')
}
}
What's good about this, is that you can set the NPM_PACKAGE_DEV by running the command, than initialize the node server (osx terminal syntax):
export NPM_PACKAGE_DEV=1 && node server.js
If the NPM_PACKAGE_DEV is omitted, the require() fallback to the end use path, that is relative to the project node_modules directory.
Hope this helps someone else in the future!

Calling a Browserify TypeScript bundle from asp.net MCV page using plain JavaScript

I have an asp.net where I have an MVC application where I want to add some client side processing using TypeScript, and call this from a.cshtml file (just using plain JavaScript from within this page). I am bundling using Gulp and Browserify
I have the following gulp file
/*
Use gulp --production to minimize and skip source maps
This skips the bundling jquery , so need to include this before the bundle
*/
// Pass this to build in production
var PRODUCTION_ARG = "production";
// Itellisense related defines
var INTELLISENSE_SRC_FOLDER = "UserControls/Intellisense/src";
var INTELLISENSE_DEST_FOLDER = "UserControls/Intellisense/build";
var INTELLISENSE_BUNDLE_FILENAME = "intellisense-bundle.js";
var gulp = require('gulp');
var del = require('del');
var ts = require("gulp-typescript");
var tsProject = ts.createProject("tsconfig.json");
var browserify = require("browserify");
var source = require('vinyl-source-stream');
var tsify = require("tsify");
var uglify = require('gulp-uglify');
var buffer = require('vinyl-buffer');
var argv = require('yargs').argv;
gulpif = require('gulp-if');
gulp.task('intellisense-clean', function () {
return del([INTELLISENSE_DEST_FOLDER + '/**/*']);
});
gulp.task("intellisense-copy-html", function () {
return gulp.src(INTELLISENSE_SRC_FOLDER + "/*.html")
.pipe(gulp.dest(INTELLISENSE_DEST_FOLDER));
});
gulp.task("intellisense-copy-css", function () {
return gulp.src(INTELLISENSE_SRC_FOLDER + "/*.css")
.pipe(gulp.dest(INTELLISENSE_DEST_FOLDER));
});
gulp.task("build-intellisense", ["intellisense-clean", "intellisense-copy-html", "intellisense-copy-css"], function () {
return browserify({
basedir: '.',
debug: true,
standalone: 'ABC',
entries: [INTELLISENSE_SRC_FOLDER + '/intellinode.ts',
INTELLISENSE_SRC_FOLDER + '/code-description-pair.ts',
INTELLISENSE_SRC_FOLDER + '/console-logger.ts',
INTELLISENSE_SRC_FOLDER + '/intellisense-control.ts'],
cache: {},
packageCache: {},
})
.ignore('jquery')
.plugin(tsify)
.bundle()
.pipe(source(INTELLISENSE_BUNDLE_FILENAME))
.pipe(buffer())
.pipe(gulpif(argv.production, uglify()))
.pipe(gulp.dest(INTELLISENSE_DEST_FOLDER));
});
gulp.task("default", ["build-intellisense"], function () {
});
My tsconfig.json is as follows..
{
"compilerOptions": {
"noImplicitAny": true,
"noEmitOnError": true,
"removeComments": false,
"sourceMap": true,
"target": "es3",
"module": "commonjs"
},
"target": "es3",
"files": [
"./UserControls/Intellisense/src/*.ts"
],
"exclude": [
"node_modules",
"wwwroot"
],
"compileOnSave": true
}
The first weird thing is I seem to need to include each ts file in the entries: list for the call to browserify, otherwise I only seem to get one or 2 of the classes included in the output bundle file.
So, including them all seem to work (though would like to know why need them all and not just the "top level" one.
The next problem is that I want to instantiate and call some of the methods from plain browser JavaScript. From other posts, I am told I can use the standalone: 'Intellisense' flag as above. This then adds a global object "ABC" (which I can see int eh debugger) but only seems to include one of the TypeScript classes (infact the last one in the entries list)
I have changed the tsconfig module to amd but got other errors (so changed back to commonjs).
I really do not know where to go from here. Seems to be very limited doco on marrying the TypeScript world back into plain browser JavaScript world.
Any help here would be greatly appreciated!
From what I could find out, nothing bundled by Browerify can be accessed outside of what is bundled within it, i.e. none of the exports are accessible by any external JavaScript (this was what I was actually asking in the question). Browerify just makes the stuff within the bundle work within the Browser.
So to expose my class(es) outside you can just add an object to the global space and just add anything to this...
E.g. in the TS file have
(function () {
let w: any = window;
w.TSModule = {};
w.TSModule.CreateClassToExport = function() {
return new ClassToExport();
}
})();
export class ClassToExport {
public doWork() : void{
...
}
and in the .cshtml JavaScript include the bundle and just get access to this object as below...
var myExport = TSModule.CreateClassToExport();
myExport.doWork();
Yes this adds something to the global space which I know is bad, but it is only one object.
Still be interested if there are any better solutions, but at least I can now use my TS code.

Node.js - Cannot read property 'toString' of null when running package on Windows

I am using the gulp and hercule package on node.js to transclude some plain text files. On Unix, everything seems to work fine. However, some coworkers are having issues running it on Windows. They are getting the following error message only when running on Windows:
[13:02:01] TypeError: Cannot read property 'toString' of null at Object.transcludeStringSync (D:\project\node_modules\hercule\lib\hercule.js:136:36)
I have tried the above with hercule#3.0.5 as well as hercule#2.0.5, and both packages give the above error. However, given that this occurs only on Windows and across many versions of the package, I suspect this issue has something to due with the Node.js installation or path.
The code that is using the hercule package:
var fs = require('fs');
var path = require('path');
var gulp = require('gulp');
var drakov = require('drakov');
var hercule = require('hercule');
gulp.task('mock', ['i18n','build_minify_no_tests'], function() {
var mockSpecificationTemplate= fs.readFileSync('test/mock/mock-template.apib','utf8');
var transcludedMockSpecification = hercule.transcludeStringSync(mockSpecificationTemplate, {
relativePath: path.resolve('../../../')
});
fs.writeFileSync('test/mock/mock.apib', transcludedMockSpecification, 'utf-8');
// Running mock server
var drakovArgv = {
sourceFiles: 'test/mock/mock.apib',
serverPort: 9000,
staticPaths: [
'../../'
],
discover: true,
watch: true
};
drakov.run(drakovArgv);
});
node and npm version information:
$ node -v
v6.3.0
$ npm -v
3.10.3
hercule.transcludeStringSync simply runs another hercule process and sends input to it:
const result = childProcess.spawnSync('../bin/hercule', syncArgs, syncOptions);
with the script ../bin/hercule:
#!/usr/bin/env node
"use strict";
require('../lib/main.js');
...obviously doesn't work on Windows
If that task must be synchronized, you may use the following function instead:
function transcludeStringSync(input, options) {
const {dirname, join} = require('path')
const hercule = join(dirname(require.resolve('hercule')), 'main')
const args = [hercule, '--reporter', 'json-err']
for (let name in options) {
args.push(`--${name}`, `--${options[name]}`)
}
const result = require('child_process').spawnSync('node', args, {input})
const err = result.stderr.toString()
if (err) throw new Error('Could not transclude input')
return result.stdout.toString()
}

How to perform a transform on npm module using browserify

By default, browserify does not perform transforms on modules included from node_modules, i.e. with no path.
I made a quick github repo that illustrates it here. The index.js file that gets browserified looks like this:
var fs = require('fs');
var testmodule = require('testmodule');
var trg1 = document.getElementById("target1");
var trg2 = document.getElementById("target2");
trg1.innerHTML = fs.readFileSync(__dirname+"/something.txt");
trg2.innerHTML = testmodule();
testmodule looks like this:
var fs = require('fs');
exports = module.exports = function() {
return fs.readFileSync(__dirname+'/data.txt');
}
Using the brfs transform module, I want to be able to inline both calls to fs.readFileSync, but when I run browserify index.js -t brfs -o bundle.js, only the call in my main project gets inlined. Here is the bundle.js result:
;(function(e,t,n){function r(n,i){if(!t[n]){if(!e[n]){var s=typeof require=="function"&&require;if(!i&&s)return s(n,!0);throw new Error("Cannot find module '"+n+"'")}var o=t[n]={exports:{}};e[n][0](function(t){var i=e[n][1][t];return r(i?i:t)},o,o.exports)}return t[n].exports}for(var i=0;i<n.length;i++)r(n[i]);return r})({1:[function(require,module,exports){
// nothing to see here... no file methods for the browser
},{}],2:[function(require,module,exports){
var fs = require('fs');
var testmodule = require('testmodule');
var trg1 = document.getElementById("target1");
var trg2 = document.getElementById("target2");
trg1.innerHTML = "This is data from a file in the main project folder"; // TRANSFORMED
trg2.innerHTML = testmodule();
},{"fs":1,"testmodule":3}],3:[function(require,module,exports){
(function(__dirname){var fs = require('fs');
exports = module.exports = function() {
return fs.readFileSync(__dirname+'/data.txt'); // NO TRANSFORM
}
})("/node_modules/testmodule")
},{"fs":1}]},{},[2])
;
Got some help from substack (author of browserify) on this one. To specify if a module outside of a project requires transformations, you need to specify a browserify.transform array in your package.json. So for the example I gave above, the package.json file in the testmodule directory looks like this:
{
"name":"testmodule",
"version":"0.0.0",
"browserify": {
"transform": ["brfs"]
},
"main": "index.js"
}
You can also use browserify -g brfs instead of browserify -t brfs. g is a global transform (which applies to dependencies)

Categories

Resources