I'm new to Gulp and the concept of task runners. I am wanting to write some javascript using es6 and have gulp run it through jscs, jshint and finally use babel to convert it to es5.
The part I'm confused about is the order I should have these tasks in my gulp pipeline. If I run jshint first I get warnings about how I can't use let and arrow functions. However, if I convert my code using babel first the babel output then fails validation as well.
What I'm looking for is a correct way of ordering my gulp task so it validates and converts my code to es5.
This is my current gulp task.
gulp.task('js-validation', function() {
$.util.log('**Starting js validation**');
return gulp
.src(config.alljs)
.pipe($.print())
.pipe($.jshint())
.pipe($.jscs())
.pipe($.babel())
.pipe($.jshint.reporter('jshint-stylish', {verbose: true}))
.pipe($.jshint.reporter('fail'))
.pipe(gulp.dest(config.temp));
});
This work for me:
.pipe(jshint({
esnext: true
}))
First, if possible, consider moving to ESLint; I'm not saying that because it's a subjective opinion, I'm saying that because it's modular and supports ES6, and even React+JSX if that's where you want to go with it.
You aren't going to have a lot of luck with JSHint, yet, if ES6 is where you're going.
If/when I'm wrong, please let me know, but I believe they have yet to replace their parser, to support all of ES6, and unless you're going to include the entirety of the browser polyfill+library in the pipeline (just for sake of having no missing methods, for validate to work), you may well be at a loss, here.
With ESLint in place, you could use the following config options (in .eslintrc, in the package.json, et cetera), to get ES6 support:
{
"env": {
"browser": true,
"node": true,
"es6": true
},
"ecmaFeatures": {
"modules": true,
"jsx": true
}
}
Of course, if you don't need node globals, JSX or ES6 modules, feel free to rip those out.
The one other caveat there is that ESLint has no support for ES7 (ES2016), yet (but will, when it's standardized).
So array/generator comprehensions, async/await, trailing commas in function argument lists, et cetera, are not supported and will cause explosions.
There is a babel-eslint version of eslint which will validate these, if that's your requirement.
You can put that in place by installing "babel-eslint" and then in your eslint config, setting { "parser": "babel-eslint" } to the root object, along with all of your other config preferences.
But typically, you would lint the code that you are putting into the system, pre-compile, using ESLint and Babel:
// ...
.pipe( eslint() )
.pipe( babel() )
// ...
To lint the source code (rather then the compiled code) you have to call the linter before babel, so the order is correct.
However, you have to use a linter that really understands ES6. With JSHint, you have to set the esnext option, but I'm not sure whether it supports all ES6 features. I recommend to have a look at eslint with babel-eslint instead.
Instead of JSHint, you can use ESLint, which will have support for numerous ES6 functions:
http://eslint.org/docs/user-guide/configuring
You are correct that you want your linting to occur prior to transpilation, also.
gulp.task('jshint', function () {
gulp.src('js/**/*.js')
.pipe(cache('jshint'))
.pipe(jshint({esnext:true}))
.pipe(jshint.reporter('default'));
});
.pipe(jshint({esnext:true}))
You have the correct order, but as suggested from other answers to use ESLint. You should also have a function to handle errors when linting. Here is my gulpfile.js (not a perfect example, but it's working for me):
const gulp = require("gulp"),
babel = require("gulp-babel"),
eslint = require("gulp-eslint");
gulp.task("babel", () => {
gulp.src("src/*.js")
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError())
.on("error", onError) // handle error for eslint
.pipe(babel())
.on("error", onError) // handle error for babel
.pipe(gulp.dest("dist"));
});
gulp.task("watch", () => {
process.chdir(process.env.INIT_CWD);
gulp.watch("src/*.js", ["babel"]);
});
// ignore if error is from babel, eslint error message is enough
if (err.plugin != "gulp-babel" && err.message) {
console.log("Message: ", err.message);
}
Related
I added bundleconfig.json to ASP.NET Core application. It has the following structure:
[
{
"outputFileName": "wwwroot/js/main.min.js",
"inputFiles": [
"wwwroot/js/scripts/first.js",
"wwwroot/js/scripts/second.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false
}
]
Both scripts has been minified and merged into main.min.js. But after minification all async modifiers has been removed from result script.
Function such as
async function foo() {
await /* some promise */;
}
have been turned into:
function foo() {await /*some promise*/;}
How do I avoid removing async modifier?
I'v reproduced the issue and tried to minify a simple js file that using ES6 specifications and later.
Test.js
async function foo() {
await bar();
}
async function bar() {
for (var i = 0; i < 10; i++) { // do some work
}
}
Then i tried to minify the file with Bundler and Minifier tool then this error thrown:
This means Bundler and Minifier doesn't support ES6 specifications and later.
For confirmation i started searching about this issue in the Github and i found these same behaviors
Crash on ES6 arrow functions in source files
minify es6 js file without turning them to es5
Where BundleMinifier currently is usefull (and where not)
I can surely claim that this is The Transpilers Issue
Transpilers, or source-to-source compilers, are tools that read source
code written in one programming language, and produce the equivalent
code in another language.
The most common and widely use one is TypeScript
TypeScript in some cases Transpiles ES6 and later to ES5
For example: if you set Target to ES6 and ES2015 it Transpiles to ES5. However, if You Target to ES2020 does NOT Transpile your code.
At The End
BundlerMinifier uses NUglify that perform javascript code
minification So There is NO way minifying ES6 and later codes by
using Bundler and Minifier. Unless, The Author decides to support it.
You are encountering The Transpile Issue (ex:ES6 to ES5).
Bundler & Minifier doesn't remove unknown keywords like async but thrown error
I'm using gulp-babel to transpile es6, below is the gulp task
gulp.task('uglify', [], function() {
var notMinified = config.scripts.src;
notMinified.push('!javascripts/*.min.js');
// console.log(notMinified);
return gulp.src(notMinified)
.pipe(babel({
"presets": ["es2015-without-strict"]
}))
.pipe(ngAnnotate())
.pipe(gp_uglify().on('error', function(e) {
console.log(e);
}))
.pipe(gulp.dest(config.scripts.dest));
});
I have tried presets es2015, es2015-without-strict etc.
While using es2015 preset, there was an error in moment.js which says 'cannot set moment of undefined'. After googling the error I found es2015 preset uses use strict at top level which converts this to undefined, hence the error in moment. So I used es2015-without-strict preset which says it transpiles without use strict but still no luck, having same error.
What could be possibly wrong here?
I'm getting below error running this command
gulp.task('minify', function () {
return gulp
.src('public/app/js/myapp.bundle.js')
.pipe(uglify())
.pipe(gulp.dest('public/app/js/myapp.bundle.min.js'));
});
GulpUglifyError: unable to minify JavaScript Caused by: SyntaxError: Unexpected token: name (MenuItem) (line: 1628, col: 18, pos: 53569)
Code on that location is this
setters: [],
execute: function () {
class MenuItem { // <-- line 1628
What's wrong?
UglifyJS does not currently support EcmaScript 6 structures like classes.
You'll probably need to run your JavaScript through a transpiler step first, or find a minifier that knows what to do with ES6 code.
Update 2017-06-17
The branch of UglifyJS that is designed to work with ES6 is now published as uglify-es on npm.
Update 2018-09-10
terser is the new uglify-es, uglify-es is no longer maintained.
If using gulp both npmjs gulp-uglify-es and npmjs gulp-terser packages support terser.
npm install gulp-terser --save-dev
const gulp = require('gulp');
const terser = require('gulp-terser');
function es(){
return gulp.src('./src/index.js')
.pipe(terser())
.pipe(gulp.dest('./build'))
}
gulp.task('default', es);
If you run into this problem and you have in fact a transpiler step like Babel, make sure that you include the proper Babel preset in you .babelrc file. Otherwise Babel will simply leave your code as is.
E.g.
{
"presets": ["es2015"]
}
It looks like my .eslintrc file is not found my gulp-eslint
I defined a lint task:
gulp.task('lint', function () {
gulp.src(['src/**/*.js', 'src/**/*.jsx'])
.pipe(eslint())
.pipe(eslint.format());
})
It runs but doesn't show any error.
My .eslintrc file is defined in src folder. I tried to move it to the root folder of my project but it didn't change anything.
It's a pretty simple file:
{
"parser": "babel-eslint",
"ecmaFeatures": {
"classes": true,
"jsx": true
},
"plugins": [
"react"
],
"extends": "eslint-config-airbnb"
}
When I run eslint src in the terminal, I get a bunch of eslint errors, which is fine.
Any idea what is not properly working?
According to the docs you need to fail on error in the pipe.
gulp.task('lint', function () {
// ESLint ignores files with "node_modules" paths.
// So, it's best to have gulp ignore the directory as well.
// Also, Be sure to return the stream from the task;
// Otherwise, the task may end before the stream has finished.
return gulp.src(['**/*.js','!node_modules/**'])
// eslint() attaches the lint output to the "eslint" property
// of the file object so it can be used by other modules.
.pipe(eslint())
// eslint.format() outputs the lint results to the console.
// Alternatively use eslint.formatEach() (see Docs).
.pipe(eslint.format())
// To have the process exit with an error code (1) on
// lint error, return the stream and pipe to failAfterError last.
.pipe(eslint.failAfterError());
});
Just a heads-up, the documentation is extremely useful and succinct on using configuration files, their precedence of usage and how they are located. You can also add the path to specify the location of your configuration file for a particular pipe:
gulp.task('lint', function () {
gulp.src(['src/**/*.js', 'src/**/*.jsx'])
.pipe(eslint({ configFile: '.eslintrc'}))
.pipe(eslint.format())
.pipe(eslint.failAfterError())
})
In the gulp-eslint documentation it should be noted that usage of the failOnError() and failAfterError() methods are advisable in that the task/stream is stopped and hence there is no invalid code written to the output.
If you use neither then the error is still caught but displayed only in the console output. So dependent on your task flow and design the destination file may still be written but you can conveniently correct the error immediately and carry on without having to start up your pipe processing/watch task again. An alternative is to look into gulp-plumber or some other means whereby you're not breaking out of a gulp watch task and yet also not writing a file containing code that doesn't pass linting validation.
I try to write these code
gulp.task('script', function() {
'use strict'
return gulp.src(['app.js', 'components/**/*.jsx'])
.pipe(babel())
.pipe(browserify())
.pipe(gulp.dest("dist"));
});
but it shows some error:
SyntaxError:
/Users/Zizy/Programming/learn-react-js/components/CommentBox.jsx:58
<div className="commentBox">
^
ParseError: Unexpected token
at wrapWithPluginError (/Users/Zizy/Programming/learn-react-js/node_modules/gulp-browserify/index.js:44:10)
It seems that before .pipe(browserify()) the gulp did't transform the jsx code. But if I just remove .pipe(browserify()) I find that did transform, just cannot let babel and browserify work together.
I know maybe I can use like babelify or browserify plugin for babel though, I just want figure out the reason.
gulp-browserify doesn't quite work like that. You don't give it a bunch of buffers to collect and bundle.
You give it one file—the entry file—which it passes into Browserify. Browserify checks to see what other files the entry file references, then loads those files directly from the file system, meaning that you can't modify them with gulp plugins beforehand.
So, really, if we pretend you don't want to use Babel on your source files, your gulpfile should look like this, only passing in the entry file:
gulp.task('script', function() {
'use strict'
return gulp.src('app.js')
.pipe(browserify())
.pipe(gulp.dest("dist"));
});
However, note that gulp-browserify is no longer maintained, and this is exactly why. gulp plugins aren't supposed to read directly from the file system. That's why you're supposed to use Browserify (or, in your case, Babelify) directly with vinyl-source-stream as recommended in the gulp recipes. It's more idiomatic and less confusing.
That wraps up my answer to your question, but I'd like to add: if you're using the ES2015 module syntax (and you probably should be), there's a better way to do this. Browserify wraps all your modules separately in a bunch of code to make the programmatic CommonJS API work properly, but ES2015 modules have a declarative syntax, which makes it much easier for tools to operate on them statically. There's a tool called Rollup that takes advantage of this, allowing it to produce bundles that are smaller, faster, and more minfication-friendly than Browserify's.
Here's how you might use it with gulp:
var gulp = require('gulp'),
rollup = require('rollup-stream'),
babel = require('gulp-babel'),
source = require('vinyl-source-stream'),
buffer = require('vinyl-buffer');
gulp.task('script', function() {
return rollup({entry: 'app.js'})
.pipe(source('app.js'))
.pipe(buffer())
.pipe(babel())
.pipe(gulp.dest('dist'));
});
Starting from Babel 6 you need to declare the presets manually, check this.
Basically, in the root of your project you need a .babelrc with the following content:
{
"presets": [ "es2015", "react" ]
}
And the corresponding npm modules in package.json:
// package.json
{
"devDependencies": {
...
"babel-preset-es2015": "^6.1.18",
"babel-preset-react": "^6.1.18",
...
}
}
Here is a sample repository with gulp, babel and browserify
Following is the code snippet
gulp.task("js", (done) => {
const bundler = browserify({ entries: paths.js.source }, { debug: true }).transform(babel);
bundler.bundle()
.on("error", function (err) { console.error(err); this.emit("end"); })
.pipe(source(paths.build.destMinJSFileName))
.pipe(buffer())
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(uglify())
.pipe(sourcemaps.write(paths.js.destMapFolder))
.pipe(gulp.dest(paths.build.destBuildFolder));
done();
});