Browserify --standalone with ES6 modules and multiple source files and exports - javascript

I am trying to use Gulp, Browserify, and Babelify to compile and transform multiple ES6 files into a single JavaScript library that can be shared with other developers.
I am trying to use multiple ES6 source files, each of which uses ES6 modules using export. I want them all to be wrapped up into a single class/function like a 'namespace'.
It seems like Browserify's --standalone option is designed to do this, but I can only get it to work when there is a single input file. When there are multiple source files with exports, I can't get them all to be included in the 'namespace' class, and I can't control which source file's exports ultimately gets picked to be in the 'namespace' class.
In this example, a.js and b.js are the source files, and I am expecting them to be bundled together in a 'namespace' class called TestModule.
a.js
export function fromA() {
console.log('Hello from a.js');
}
b.js
export function fromB() {
console.log('Hello from b.js');
}
gulpfile.js
const browserify = require('browserify');
const gulp = require('gulp');
const log = require('gulplog');
const plumber = require('gulp-plumber');
const source = require('vinyl-source-stream');
function minimalExample(done) {
return browserify({
entries: [
'./src/a.js',
'./src/b.js'
],
standalone: 'TestModule' // output as a library under this namespace using a umd wrapper
})
.transform('babelify')
.bundle()
.on('error', log.error)
.pipe(source('minimalExample.js'))
.pipe(plumber())
.pipe(gulp.dest('./dist'));
}
module.exports = {
minimalExample
};
What I want
I want minimalExample.js to have an object named TestModule that has functions fromA() and fromB(), so that I can call both methods. I should be able to run either of these commands from the console:
TestModule.fromA()
TestModule.fromB()
What is actually happening
When I load minimalExample.js in a browser, open the console, and inspect the TestModule object, it exists, but it is missing the function from a.js. It only has the function from b.js:
Am I missing a setting somewhere? Is there a way to get Browserify to include all the exports in the standalone 'namespace' class?
Update 1
Prompted by #Zydnar's discussion, I did the obvious thing and actually looked at the output file, minimalExample.js. I don't understand how the transforms are intended to work or what is going wrong yet; I'm still looking at that. But I do see both input files have been transformed and included in the output.
Here is the actual output, and the same thing but pretty-printed by Chrome.

Thanks to help on the browserify project on Github, I have an answer for this. Renée Kooi pointed me in the right direction. They said:
If you have multiple entry points, all of them are executed, but browserify doesn't merge modules, which could cause bad unexpected behaviour.
The solution is to have a single file that acts as an entry point for Browserify that exports everything you want exported. Use that single file as your input source file in the entries option. Browserify will walk your app's dependency tree and include the dependencies it requires. Anything exported by the entry point file will be included in the exported module as expected.
A complete example follows. main.js is the entry point file.
a.js
export function fromA() {
console.log('Hello from a.js');
}
b.js
export function fromB() {
console.log('Hello from b.js');
}
main.js (This one is new)
export * from './a.js';
export * from './b.js';
gulpfile.js
const browserify = require('browserify');
const gulp = require('gulp');
const log = require('gulplog');
const plumber = require('gulp-plumber');
const source = require('vinyl-source-stream');
function minimalExample(done) {
return browserify({
entries: [
'./src/main.js' // THIS LINE HAS CHANGED FROM THE QUESTION
],
standalone: 'TestModule'
})
.transform('babelify')
.bundle()
.on('error', log.error)
.pipe(source('minimalExample.js'))
.pipe(plumber())
.pipe(gulp.dest('./dist'));
}
module.exports = {
minimalExample
};
Now when you run the minimalExample task with gulp, the file generated will have both TestModule.fromA() and TestModule.fromB() functions.

Related

How to use js-cookie in a standalone *.js script?

I am using gulp to concatenate and minify a number of standalone *.js scripts used on my web site. Basically this is just a catchall folder where I place little utility scripts that run on page load. For example, one of them starts a carousel slider, another adds a class to the header that shrinks it on scroll, etc. Each of these "features" has its own standalone *.js file.
Now, I would like to use the popular js-cookie library in one of those scripts. Unfortunately, since my project is not set up as an ES6 module, I am not able to able to import the js-cookie library the way it's specified in the docs, like this:
import Cookies from 'js-cookie'
When I do this, I get the error message Uncaught SyntaxError: Cannot use import statement outside a module.
I tried changing it to this:
window.Cookies = require('js-cookie')
but that gave me this error:
Uncaught ReferenceError: require is not defined
Here is my gulpfile, followed by the feature.js script in which I'm trying to use the js-cookie library:
gulpfile.js
// Initialize modules
const { src, dest, watch, series, parallel } = require('gulp');
const sourcemaps = require('gulp-sourcemaps');
const sass = require('gulp-sass');
const concat = require('gulp-concat');
const uglify = require('gulp-uglify');
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
var replace = require('gulp-replace');
var merge = require('merge-stream');
// File paths (note that src paths are arrays)
const files = {
scssSrcPath: [
'scss/*.scss',
'scss/_pageContentModules/*.scss'
],
jsSrcPath: [
'js/*.js',
'node_modules/slick-carousel/slick/slick.js'
],
scssDstPath: '../web/css',
jsDstPath: '../web/js'
}
// Sass task: compiles SCSS files into style.css
function scssTask(){
return merge(files.scssSrcPath.map(function (file) {
return src(file)
}))
.pipe(sourcemaps.init()) // initialize sourcemaps first
.pipe(sass()) // compile SCSS to CSS
.pipe(postcss([ autoprefixer(), cssnano() ])) // PostCSS plugins
.pipe(sourcemaps.write('.'))
.pipe(dest(files.scssDstPath));
}
// JS task: concatenates and uglifies JS files to script.js
function jsTask(){
return merge(files.jsSrcPath.map(function (file) {
return src(file)
}))
.pipe(concat('app.js'))
.pipe(uglify())
.pipe(dest(files.jsDstPath));
}
// Watch task: watch SCSS and JS files for changes
// If any change, run scss and js tasks simultaneously
function watchTask(){
watch(files.scssSrcPath, scssTask);
watch(files.jsSrcPath, jsTask);
}
// Export the default Gulp task so it can be run
// Runs the scss and js tasks simultaneously
// then watch task
exports.default = series(
parallel(scssTask, jsTask),
watchTask
);
js/feature.js
import Cookies from 'js-cookie';
const rs = cookies.get('referral_source');
if (typeof rs !== 'undefined') {
console.log('referral_source = ' + rs);
}
How can I get this working? Is there a way to do it using my simple Gulp setup, or do I need to go beyond and set up a full-on Webpack setup (with all the complexity that adds)?
Unfortunately, as far as I know, Gulp does not support the ability to use ES6 modules. If you want to use them, you will need to use Webpack.
But js-cookie does have a jsDelivr CDN: <script src="https://cdn.jsdelivr.net/npm/js-cookie#3.0.1/dist/js.cookie.min.js"></script>. By including this before your JS script, like this:
<script src="https://cdn.jsdelivr.net/npm/jscookie#3.0.1/dist/js.cookie.min.js"></script>
<script src="./js/feature.js"></script>

Bundling with webpack from script

I am using webpack to bundle my Javascript files in my project:
webpack --config myconfig.webpack.config.
From commandline it is ok.
Building
However I would like to create a build task, I am using jake, so in order to create the bundle I need to invoke webpack from Javascript.
I could not find the API online, I basically need something like this:
// Jakefile.js
var webpack = require("webpack");
desc('This is the default build task which also bundles stuff.');
task('default', function (params) {
webpack.bundle("path-to-config"); // Something like this?
});
How do I achieve this?
Attempt 1
I have tried the following:
// Jakefile.js
var webpack = require("webpack");
var config = require("./webpack.config.js");
desc('This is the default build task which also bundles stuff.');
task('default', function (params) {
webpack(config);
});
webpack.config.js is my config for webpack. When I use from commandline and reference that file the bundle is correctly created. But when using the above code it does not work. When I execute it, no errors, but the bundle is not emitted.
In your Attempt 1, you seem to be consuming the webpack's Node.js API by passing the config to webpack method. If you take this approach, webpack method will return a compiler object and you need to handle it correctly.
For e.g.,
import webpack from 'webpack';
var config = {}; // Your webpack config
var wpInstanceCompiler = webpack(config);
wpInstanceCompiler.run(function(err, stats) {
if (stats.hasErrors()) {
console.log(stats.toJson("verbose");
}
});
This is how you execute a webpack config via the Node.js API. Unless you run the compiler instance, the output will not get generated.
This worked for me as well:
var webpack = require("webpack");
var lib = require(path.join(__dirname, "webpack.config.js"));
desc('Builds the projects and generates the library.');
task('default', function() {
webpack(lib, function() {
console.log("Bundle successfully created!");
});
});

Browserify does not work - why?

I have the following code that I browserify to bundle.js, that I include (before loading any other .js file) on my front-end. The file I browserify is simply this this:
var firebase = require('firebase')
I then call authorize() on this variable, in the next file that is included on the front-end, but I get an error saying firebase is not defined?
Browserify is a module bundler that enables you to use CommonJS (Node) modules in your browser. This implies that your project must follow CommonJS conventions to export (exports, module.exports) and import (require) modules. Here is a basic example:
Your module (module.js)
var foo = function () {
console.log('Foo');
};
var bar = function () {
console.log('Bar');
};
module.exports = {
foo: foo,
bar: bar
};
Your entry point (main.js)
var module = require('./module');
module.foo(); // Foo
module.bar(); // Bar
This code will work out-of-the-box with Node, but your browser cannot interpret it. This is where Browserify is useful...
When you run the command browserify main.js -o bundle.js on your entry point, Browserify traverses all your dependencies (here module.js) and loads them in a bundle. This bundle is usable in your browser, so you can now load it in a script tag:
<script src="bundle.js"></script>

Import and convert JS files using Gulp & Babel

Consider the following files:
//foo.js
(function(){
console.log('working');
})();
//bar.js
import 'foo.js';
Now I'm using gulp to compiled from ES6 to ES5. Here's the relevant task:
gulp.task('build-js', function() {
return gulp.src('bar.js')
.pipe(babel({
presets: ['es2015']
}))
.pipe(gulp.dest('./dist'));
});
My output file looks like this:
'use strict';
require('foo.js');
The isn't the outcome I expected. I want all code to import into the single output file using the ES5 conversion. This way, the single JS file can be loaded in a browser and run correctly. What do I need to do for the desired outcome?
Since bar.js only imports foo.js, the output file should look exactly like foo.js. Also, since foo.js contains only a self executing function, the output file should execute this immediately and log working to the console.
You should add a 'bundle' task if you want to create a single file for the browser. Take a look at browserify or webpack.
http://browserify.org/
https://webpack.github.io/
You usually need to specify an entry point, and the bundler resolves all the dependencies and creates a single js file.
EDIT:
An example task for gulp, using browserify:
var browserify = require('gulp-browserify');
gulp.task('bundle', function() {
gulp.src('./dist/bar.js') // entry point
.pipe(browserify())
.pipe(gulp.dest('./dist'))
});

Gulp with browserify, tsify, and reactify?

I'm using gulp with browserify and tsify in a TypeScript project. The following is an extract from my gulpfile.js:
var browserified = function (filename, debug) {
var b = browserify({
entries: filename,
debug: debug || false
});
b.plugin('tsify', {
noImplicitAny: true,
target: 'ES5'
});
b.transform('debowerify');
return b.bundle();
};
gulp.task('rebuild', ['lint', 'less'], function() {
var b = browserified ('./src/ts/main.ts', true);
return buildSourceMaps (b);
});
This works so far. I want to extend this so I can require React JSX files. First I tried (from one of my TypeScript files):
import Test = require ('../jsx/Test.jsx');
This doesn't work, though, because tsify would complain as it looks for a TypeScript file ../jsx/Test.jsx.ts. So I use the following hack:
declare var require: any;
var Test = require ('../jsx/Test.jsx');
If Test.jsx is plain vanilla JavaScript, this works. If Test.jsx contains TypeScript, it would fail, which is what I expect. So far, so clear.
Now I want to add reactify to my gulp tasks so I can use JSX in these files. Here I am stuck! I tried adding the following to the function browserified in my gulpfile.js:
b.plugin ('reactify', {
extension: 'jsx'
});
I still get the following error when I call gulp rebuild when Test.jsx contains actual JSX:
Unexpected token <
Obviously, gulp chokes on the first JSX-specific term. I think gulp is trying to pass the JSX through the TypeScript compiler. Which isn't a surprise, since I can't think of a way how to tell tsify to ignore my .jsx files. I'm new to gulp, so I am a bit at a loss. Any ideas how to set up gulp to allow for TypeScript with all .ts files and JSX with all .jsx files?
This is the gulp task I use for development. It uses watchify along with browserify and reactify to build your code, provide source mapping, and rebundle any changes you make on the fly. The path.ENTRY_POINT variable is the main component for your react app (often app.js or main.js).
gulp.task('watch', function() {
gulp.watch(path.HTML, ['copy']);
var watcher = watchify(browserify({
entries: [path.ENTRY_POINT],
transform: [reactify],
debug: true,
cache: {}, packageCache: {}, fullPaths: true
}));
return watcher.on('update', function () {
watcher.bundle()
.pipe(source(path.OUT))
.pipe(gulp.dest(path.DEST_SRC))
console.log('Updated');
})
.bundle()
.pipe(source(path.OUT))
.pipe(gulp.dest(path.DEST_SRC));
});
I used this tutorial to set up my gulpfile.js and it provides a good explanation for every gulp task:
http://tylermcginnis.com/reactjs-tutorial-pt-2-building-react-applications-with-gulp-and-browserify/

Categories

Resources