Browserify bug with multiple transforms and source maps - javascript

I am using Browserify with one transform method: reactify. Here is how I build my scripts:
gulp.task('scripts', function() {
var b = null, watcher = null;
function bundle() {
return b
.on('error', function(err) { console.error(err) })
.bundle()
.pipe(source('bundle.js'))
.pipe(gulp.dest('dest/scripts'));
}
b = browserify({
debug: true,
entries: ['app/index.jsx'],
transform: [ reactify ],
extensions: [ '.jsx' ],
cache: {}, packageCache: {}, fullPaths: true
});
if (config.watch) {
b = watchify(b);
b.on('update', bundle);
}
return bundle();
});
When running the app locally I the source maps are correct and in the dev tools I can see the original jsx file.
This problem starts when I add another transform. Then when running and looking in dev tools, I don't get the original file. Instead I get the jsx files AFTER compilation. I tried that with es6ify, uglifyfy and envify (reactify + es6ify, reactify + uglifyfy, reactify + envify) and I get the same incorrect behavior.
It has to be something wrong I do with the source maps configuration or a bug in browserify.
Any idea how to fix it?

Related

Terser does not give minified file

I am trying to minify an angularjs application using grunt and terser. I first used uglifiy-es but then read that it has some issues. So I tried terser. But the output does not give me minified files.
The gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
//grunt task configuration will go here
ngAnnotate: {
options: {
singleQuotes: true
},
app: {
files: {
'./public/min-safe/js/_config_min.js': ['./controllers/_config.js'],
'./public/min-safe/js/ctrl_accountingdashboard.js': ['./controllers/ctrl_accountingdashboard.js'],
}
}
},
concat: {
js: { //target
src: ['./public/min/app.js', './public/min-safe/js/*.js'],
dest: './public/min/app.js'
}
},
terser: {
options: {},
files: {
'./public/min/app.js': ['./public/min/app.js'],
},
}
});
//load grunt tasks
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-terser');
grunt.loadNpmTasks('grunt-ng-annotate');
//register grunt default task
grunt.registerTask('default', ['ngAnnotate', 'concat', 'terser']);
}
I had the same problem. According to the documentation, this should work but it didn't for me. Wrapping the "files" setting in a custom target works for me:
terser: {
options: {},
main: {
files: {
'./public/min/app.js': ['./public/min/app.js'],
}
}
}
To add to #Tim's great answer:
Here is an example that allows to run grunt-terser with path / file wildcard patterns (globbing) – which it does not support out of the box.
Please note the helper properties _src and _dest in the terser config which are not read by grunt-terser itself but by the task terser_all. This task expands the globbing pattern(s) in _src and builds the real config in the files property. When done it runs terser with that updated config.
module.exports = function (grunt) {
grunt.initConfig({
terser: {
dist: {
options: {
compress: {
drop_console: true // remove console.log, console.info, ...
}
},
files: {
// FILLED through terser_all task below!
// Examples config:
// "dist/example.js": [ "path/to/files/example.js" ]
// "dist/example_joined.js": [ "path/to/files/*.js" ]
},
// -----
// HELPER PROPERTIES to build the files prop (see above) in the terser_all task below.
_src: [
"path/to/files/*.js"
],
_dest: "dist/"
}
}
});
grunt.registerTask('terser_all', function () {
// work on this target in terser config
var terser_target_name = "dist";
// read the terser config
var terser_config = grunt.config.get('terser') || {};
var terser_target_config = terser_config[terser_target_name] || {};
// get the destination dir
var destDir = terser_target_config._dest;
// loop through all source files and create an entry in the terser config for each of it
var files = grunt.file.expand(terser_target_config._src);
for (const [i, file] of files.entries()) {
grunt.log.writeln(file);
// add this file to the terser config as: dest_file: src_file
terser_target_config.files[destDir + file] = file;
}
// show new config on CLI
grunt.log.writeflags(terser_target_config);
// write back config and run task
grunt.config.set('terser', terser_config);
grunt.task.run('terser');
});
grunt.loadNpmTasks('grunt-terser');
grunt.registerTask('build', ['terser_all']);
grunt.registerTask('default', ['build']);
};
Just a note:
If you try to "disable" some options by renaming this disables the whole process. At least this was my result with grunt-terser. I was left with the original js file.
{
mangleX: {
reserved: [/* ... */]
}
}

browserify & react NODE_ENV for production

I want to build my js code with react and others and minify it to one file.
It works well but I get the development version of react
It looks like you're using a minified copy of the development build of React
Now I know I need to add NODE_ENV = production
but I tried in so many ways and still, the build stays the same...
I tried envify as you can see below, and hardcoding it like this:
process.env.NODE_ENV = 'production';
but still, not good.
When I try to add envify transform, with that:
.transform(envify({
'NODE_ENV': 'production'
}))
I get this error on the build:
TypeError Path must be a string.
any ideas?
function bundleJs() {
const _browserify = browserify({
entries: [config.entry],
debug : false,
cache: {},
packageCache: {},
fullPaths: true,
extensions: ['.js']
});
_browserify.plugin(resolutions, ['*'])
.transform('envify', {global: true, _: 'purge', NODE_ENV: 'production'})
.transform(hbsfy)
.transform(babelify, {
only: /(app)|(frontend-app)/,
presets: ['es2015-without-strict', 'react']
})
.on('update', () => {
bundle();
gutil.log('Rebundle...');
})
.on('log', gutil.log);
function bundle() {
return bundler.bundle()
.on('error', handleError)
.pipe(source('init.js'))
.pipe(rename('bundle.js'))
.pipe(buffer())
.pipe(gulpif(env === 'production', uglify()))
.pipe(gulpif(env !== 'production', sourcemaps.init({ loadMaps: true })))
.pipe(gulpif(env !== 'production', sourcemaps.write('.')))
.pipe(gulp.dest(config.dist))
.pipe(browserSync.reload({stream:true}));
}
// run it once the first time buildJs is called
return bundle();
}
OK, so after wasting 3 hours of my life on that code.
I noticed that the build of react is used from bower and not from npm.
inside composer.json we had identifieres under "browser", ie:
"react": "./bower_components/react/react.js",
"react-dom": "./bower_components/react/react-dom.js"
I assume this points directly to react dev build so that was the problem.
I simply installed with npm, and all worked well.

Gulp Bundle + Browserify on multiple files

So I have asimple gulp task function which currently converts my main.jsx to a main.js file:
gulp.task("bundle", function () {
return browserify({
entries: "./app/main.jsx",
debug: true
}).transform(reactify)
.bundle()
.pipe(source("main.js"))
.pipe(gulp.dest("app/dist"))
});
I was wondering if it would be possible to put multiple bundles in this gulp.task?
My ideal outcome would be being able to do:
main.jsx to main.js
otherPage.jsx to otherPage.js
otherPage2.jsx to otherPage2.js
All in one gulp task.
I have searched onliine but cannot seem to find anything relevant, any help or advice is appreciated, thank you in advance.
If you want to create a bundle for each file you need to loop over the respective files, create a stream for each file and then merge the streams afterwards (using merge-stream):
var merge = require('merge-stream');
gulp.task("bundle", function () {
var files = [ "main", "otherPage", "otherPage2" ];
return merge(files.map(function(file) {
return browserify({
entries: "./app/" + file + ".jsx",
debug: true
}).transform(reactify)
.bundle()
.pipe(source(file + ".js"))
.pipe(gulp.dest("app/dist"))
}));
});
The above requires that you maintain a list of files manually as an array. It's also possible to write a task that bundles all .jsx files in the app directory without having to maintain an explicit array of the files. You just need the glob package to determine the array of files for you:
var merge = require('merge-stream');
var glob = require('glob');
var path = require('path');
gulp.task("bundle", function () {
var files = glob.sync('./app/*.jsx');
return merge(files.map(function(file) {
return browserify({
entries: file,
debug: true
}).transform(reactify)
.bundle()
.pipe(source(path.basename(file, '.jsx') + ".js"))
.pipe(gulp.dest("app/dist"))
}));
});

Multiple gulp sources and destinations

I am trying to have following directory structure
-- src
|__ app
|__ x.ts
|__ test
|__ y.ts
-- build
|__ app
|__ js
|__ test
|__ js
I want that on "gulp compile", I have my generated js files inside build/app and build/test. i.e. I have multiple sources and multiple destination.
I dont want to create a new gulp target for test one. Following are two various methods which I am trying to accomplish the task
gulp.task('compile', function () {
//path to src/app typescript files
var app_js = gulp.src('./src/app/**/*.ts')
.pipe(tsc(tsProject))
//path to src/test typescript files
var test_js = gulp.src('./src/test/**/*.ts')
.pipe(tsc(tsProject));
return merge([
app_js.js.pipe(gulp.dest('./build/src/app/')),
test_js.js.pipe(gulp.dest('./build/src/test/'))
]);
});
gulp.task('bundle', function () {
var paths = [
{ src: './src/app/**/*.ts', dest: './build/src/app/' },
{ src: './src/test/**/*.ts', dest: './build/src/test/' }
];
var tasks = paths.map(function (path) {
return gulp.src(path.src).pipe(tsc(tsProject)).pipe(gulp.dest(path.dest));
})
return merge(tasks);
});
However, every time, I run "gulp compile" or "gulp bundle", I hit following issues
events.js:141
throw er; // Unhandled 'error' event
^Error: stream.push() after EOF
at readableAddChunk (_stream_readable.js:132:15)
Can somebody tell me that what am I doing wrong here?
NOTE: I tried using both merge-stream and merge2 packages.
Ower! Thanks for your prompt response.
I end up with following solution
gulp.task('compile', function () {
return gulp.src(['src/**/*.ts'] )
.pipe(sourcemaps.init())
.pipe(tsc(tsProject))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('build'));
});"
I have the same project structure in one of my projects and I use the following for the build task:
var tsProject = tsc.createProject({
removeComments : false,
noImplicitAny : false,
target : "ES5",
module : "commonjs",
declarationFiles : false
});
gulp.task("build-source", function() {
return gulp.src(__dirname + "/source/**/**.ts")
.pipe(tsc(tsProject))
.js.pipe(gulp.dest(__dirname + "/build/source/"));
});
var tsTestProject = tsc.createProject({
removeComments : false,
noImplicitAny : false,
target : "ES5",
module : "commonjs",
declarationFiles : false
});
gulp.task("build-test", function() {
return gulp.src(__dirname + "/test/*.test.ts")
.pipe(tsc(tsTestProject))
.js.pipe(gulp.dest(__dirname + "/build/test/"));
});
gulp.task("build", function(cb) {
runSequence("lint", "build-source", "build-test", cb);
});
I'm using two separated tasks because using one tsProject object with multiple sources and multiple destinations can lead to unexpected behavior.
You must create the project outside of the task. You can't use the same project in multiple tasks. Instead, create multiple projects or use a single task to compile your sources. - Source
I also use the following for the bundle task:
gulp.task("bundle-source", function () {
var b = browserify({
standalone : 'inversify',
entries: __dirname + "/build/source/inversify.js",
debug: true
});
return b.bundle()
.pipe(source("inversify.js"))
.pipe(buffer())
.pipe(gulp.dest(__dirname + "/bundled/source/"));
});
gulp.task("bundle-test", function () {
var b = browserify({
entries: __dirname + "/build/test/inversify.test.js",
debug: true
});
return b.bundle()
.pipe(source("inversify.test.js"))
.pipe(buffer())
.pipe(gulp.dest(__dirname + "/bundled/test/"));
});
gulp.task("bundle", function(cb) {
runSequence("build", "bundle-source", "bundle-test", "document", cb);
});
My entire build script is available here. Hope it helps!

Keep original typescript source maps after using browserify

Background: I am compiling 2 dependent TypeScript files to js, which produces also source maps (one source map per file) using tsc 1.0
I'm using -m commonjs and then use browserify to generate a single bundle.js
However I noticed that I get the original source map references twice in the bundle, which doesn't seem to work.
Passing --debug doesn't seem to do the trick either.
I had a feeling this issue: https://github.com/substack/node-browserify/issues/325 is somewhat related, but I couldn't figure out how the issue was resolved.
Also https://github.com/substack/browser-pack was suggested, but again I don't fully understand how to use it, is it a replacement to browserify?
Bottom line, I would like to merge the 2 js files but "merge" the js to ts source maps using browserify. Is that possible?
tsify is a browserify plugin that is better and replaces e.g. typescriptifier.
npm install tsify browserify watchify
You use tsify like this:
browserify src/index.ts -p tsify --debug -o build/index.js
Notice that this supports browserify --debug switch, no extra tricks required. So you can also use it with watchify like this:
watchify src/index.ts -p tsify --debug -o build/index.js
Using the minifyify browserify plugin I believe you can use TypeScript with Browserify and retain the source maps. After compiling the TypeScript files you should be able to pass the "entry" file (the one that imports the other one via commonjs syntax) through browserify with the minifyify plugin.
var browserify = require('browserify'),
bundler = new browserify();
bundler.add('entry.js');
bundler.plugin('minifyify', {map: 'bundle.js.map'});
bundler.bundle({debug: true}, function (err, src, map) {
if (err) console.log(err);
fs.writeFileSync('bundle.js', src);
fs.writeFileSync('bundle.js.map', map);
});
Here is my working solution:
var settings = {
projectName : "test"
};
gulp.task("bundle", function() {
var mainTsFilePath = "src/main.ts";
var outputFolder = "bundle/src/";
var outputFileName = settings.projectName + ".min.js";
var pkg = require("./package.json");
var banner = [
"/**",
" * <%= pkg.name %> v.<%= pkg.version %> - <%= pkg.description %>",
" * Copyright (c) 2015 <%= pkg.author %>",
" * <%= pkg.license %>",
" */", ""
].join("\n");
var bundler = browserify({
debug: true,
standalone : settings.projectName
});
// TS compiler options are in tsconfig.json file
return bundler.add(mainTsFilePath)
.plugin(tsify)
.bundle()
.pipe(source(outputFileName))
.pipe(buffer())
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(uglify())
.pipe(header(banner, { pkg : pkg } ))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(outputFolder));
});
I created example project.
You can run it with $(npm bin)/gulp build --env=dev for development environment and source maps will be generated.
There is gulpfile.js:
'use strict';
var path = require('path'),
gulp = require('gulp'),
del = require('del'),
typescript = require('gulp-typescript'),
sourcemaps = require('gulp-sourcemaps'),
browserify = require('browserify'),
source = require('vinyl-source-stream'),
buffer = require('vinyl-buffer'),
uglify = require('gulp-uglify'),
gutil = require('gulp-util'),
inject = require('gulp-inject'),
babel = require('gulp-babel'),
argv = require('yargs').argv;
var devEnvironment = 'dev',
prodEnvironment = 'prod',
environment = argv.env || prodEnvironment,
isDevelopment = environment === devEnvironment;
var projectPath = __dirname,
srcDir = 'src',
srcPath = path.join(projectPath, srcDir),
buildDir = path.join('build', environment),
buildPath = path.join(projectPath, buildDir),
distDir = 'dist',
distRelativePath = path.join(buildDir, distDir),
distPath = path.join(buildPath, distDir);
var tsSrcPath = path.join(srcPath, 'typescript'),
tsGlob = path.join(tsSrcPath, '**', '*.ts'),
tsBuildPath = path.join(buildPath, 'tsc');
var indexHtmlName = 'index.html',
indexJsName = 'index.js';
var distIndexJsPath = path.join(distPath, 'index.js'),
distIndexHtmlPath = path.join(distPath, indexHtmlName);
var tsProject = typescript.createProject('tsconfig.json');
console.log('Environment: ' + environment);
gulp.task('clean', function () {
return del([buildPath]);
});
gulp.task('tsc', ['clean'], function () {
var stream = gulp.src([tsGlob]);
if (isDevelopment) {
stream = stream
.pipe(sourcemaps.init());
}
stream = stream
.pipe(typescript(tsProject))
.pipe(babel({
presets: ['es2015']
}));
if (isDevelopment) {
stream = stream.pipe(sourcemaps.write({sourceRoot: tsSrcPath}));
}
return stream.pipe(gulp.dest(tsBuildPath));
});
gulp.task('bundle', ['tsc'], function () {
var b = browserify({
entries: path.join(tsBuildPath, indexJsName),
debug: isDevelopment
});
var stream = b.bundle()
.pipe(source(indexJsName))
.pipe(buffer());
if (!isDevelopment) {
stream = stream.pipe(uglify());
}
return stream
.on('error', gutil.log)
.pipe(gulp.dest(distPath));
});
gulp.task('build', ['bundle'], function() {
return gulp.src(path.join(srcPath, indexHtmlName))
.pipe(inject(gulp.src([distIndexJsPath], {read: false}), {ignorePath: distRelativePath, addRootSlash: true}))
.pipe(gulp.dest(distPath));
});
You should pay attention to lines:
stream = stream.pipe(sourcemaps.write('', {sourceRoot: tsSrcPath})); - write inline source maps with sourceRoot pointing to your typescript sources path. Inline maps are written directly to .js files generated by tsc to build/dev/tsc.
debug: isDevelopment - in development environment make browserify generate his own source maps for resulting bundle build/dev/dist/index.js file so it will have source maps referencing .js files from build/dev/tsc which in turn have source maps referencing .ts files from src/typescript.
With this setup you will be able to see and debug .ts files in browser:
I faced similar issue when trying to debug my Angular2 app running in Chrome in Visual Studio Code (Using Debugger for Chrome extension)
I use gulp as my task runner and my setup is as follows:
Typescript files -> tsc -> intermediate es5 js -> browserify (plus uglify in production build) -> compiled bundle
My directory structure is as follows:
|- src
|- my .ts files here
|- main.ts - my entry file
|- dist
|- intermediate files go here
|- web
|- app.js - final bundle
|- app.js.map - final bundle map
|- gulpfile.js
gulpfile.js:
var gulp = require('gulp'),
tsc = require('gulp-typescript'),
browserify = require('browserify'),
uglify = require('gulp-uglify'),
sourcemaps = require('gulp-sourcemaps'),
source = require('vinyl-source-stream'),
buffer = require('vinyl-buffer');
gulp.task('tsc', [], () => {
return gulp.src(['src/**/*.ts'])
.pipe(sourcemaps.init())
.pipe(tsc({
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [ "es2015", "dom" ],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}))
.pipe(sourcemaps.write(null, {
"sourceRoot": function(file) {
let parts = file.relative.split('\\');
let root = Array(parts.length + 1).join('../') + 'src';
return root;
}
}))
.pipe(gulp.dest('dist/'));
});
gulp.task('bundle', ['tsc'], () => {
let b = browserify({
entries: 'dist/main.js',
debug: true,
});
return b.bundle()
.pipe(source('app.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(sourcemaps.write('./', {
"sourceRoot": "../",
}))
.pipe(gulp.dest('web/'));
})
gulp.task('default', ['bundle']);
Explanation/reasoning:
For some reason browserify doesn't read and parse .js.map files linked in .js file (via special comment at the end) but it does when the source map is embedded in js file. So, by passing null instead of path to sourcemaps it will embed it at the end of generated .js file.
Next issue I noticed was that sourcemaps doesn't automatically follow directory structure (add '../' to sourceRoot when it goes to next directory level), so I made a quick function to complement this. Keep in mind that it only works on Windows - on Linux you'd have to change split character.
function(file) {
let parts = file.relative.split('\\'); // put '/' here on Linux
let root = Array(parts.length + 1).join('../') + 'src';
return root;
}
Certainly there is a way to detect correct path separator, I'm debugging only on Windows thus it's not important for my purposes.
I hope it helps someone, cause I've spent whole Sunday morning tracking down this problem.

Categories