ASP.NET Core BundleMinifier removes async modifier after minification - javascript

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

Related

Babel is trying to import a module that doesn't exist

My compiled Babel output tries to import a function/file that does not exist. Am I missing a configuration or step in my Mix/Babel/Webpack configuration that would output this file?
I am using Laravel Mix (5.0.4) with its default configurations.
I've recently used the Javascript await operator for the first time. It is causing an issue with Babel. When Babel processes await to make it backwards compatible, it adds import _regeneratorRuntime from "#babel/runtime/regenerator"; to the beginning of the Javascript file. However, babel/runtime/regenerator doesn't actually exist. This causes the Javascript to fail when the browser attempts to load it, producing the error Error resolving module specifier: #babel/runtime/regenerator.
I am beyond my understanding of how Mix, Babel, and Webpack work together. I do not know how to tell Mix/Babel/Webpack to produce file(s) that contain the necessary module(s), or if there's something else I need to be doing.
I've tried many solutions via googling, played with the configuration files, and hit my head against my desk a bunch of times. None of these worked. I'm not sure if I am even asking the right questions.
Debugging info:
webpack.mix.js looks like this:
const mix = require('laravel-mix');
// Use of mix.babel() is imperative as this is legacy code and cannot leverage mix.js()
mix.babel('public/js/helpers.js', 'public/js/processed/helpers.js')
.babel('public/js/main.js', 'public/js/processed/main.js')
.babel('public/js/stripe.js', 'public/js/processed/stripe.js');
The problematic Javascript looks like this:
function foo() {
const bar = document.getElementById('bar');
bar.addEventListener('click', async (event) => {
// ('async' is the part which causes the `import` to be added)
});
}
And when run through Babel, looks like this:
import _regeneratorRuntime from"#babel/runtime/regenerator";function asyncGeneratorStep(n,e,r,t,o,a,u){try{var c=n[a](u),i=c.value}catch(n){return void r(n)}c.done?e(i):Promise.resolve(i).then(t,o)}function _asyncToGenerator(n){return function(){var e=this,r=arguments;return new Promise(function(t,o){var a=n.apply(e,r);function u(n){asyncGeneratorStep(a,t,o,u,c,"next",n)}function c(n){asyncGeneratorStep(a,t,o,u,c,"throw",n)}u(void 0)})}}function foo(){document.getElementById("bar").addEventListener("click",function(){var n=_asyncToGenerator(_regeneratorRuntime.mark(function n(e){return _regeneratorRuntime.wrap(function(n){for(;;)switch(n.prev=n.next){case 0:case"end":return n.stop()}},n)}));return function(e){return n.apply(this,arguments)}}())}
When I dig into Mix's default Babel config, I think it's using this:
{
cacheDirectory: true,
presets: [
[
'#babel/preset-env',
{
modules: false,
forceAllTransforms: true
}
]
],
plugins: [
'#babel/plugin-syntax-dynamic-import',
'#babel/plugin-proposal-object-rest-spread',
[
'#babel/plugin-transform-runtime',
{
helpers: false
}
]
]
}
Use js mixin instead:
const mix = require('laravel-mix');
mix.js('public/js/helpers.js', 'public/js/processed/helpers.js')
.js('public/js/main.js', 'public/js/processed/main.js')
.js('public/js/stripe.js', 'public/js/processed/stripe.js');
I was not able to find a working solution in a reasonable time for the legacy project I was working on, so I just used a workaround and documented it. I tried various solutions, different libraries and compilers, upgrading existing libraries, better workarounds and so on. Because this was a legacy project, most changes/updates/tool swaps resulted in a cascade of changes needed (and it still didn't work in the end after making all of those changes).
Ultimately, all I did was take the offending bit of Javascript (it was only one small function) and moved it to its own Javascript that does not get processed by Babel.
// The code that gets processed fine is here:
mix.babel('public/js/stripe.js', 'public/js/processed/stripe.js')
// The code that doesn't get processed without error is here:
.copy('public/js/stripeUnminified.js', 'public/js/processed/stripeUnminified.js');
Considering the time I had invested, this workaround was an ok solution. Running the offending bit of Javascript through a compiler such as Babel just wasn't actually a super critical priority considering all of the headache it was causing and time it was taking. It was looking like I was going to have to rework and update a lot of the project just to fix this one little problem (and still a fix was not guaranteed).

Compile typescript without transpiling async functions

Is there a way to use the TypeScript compiler only to remove type annotations, but not transpiling async functions? Something like a { target: 'esInfinite' } option? The reason is: There are browsers that already support async functions, so I wish to have a build target where those functions are not affected.
example input:
async function foo(a : number) : Promise<void> {}
example output:
async function foo(a) {}
In your tsconfig.json, change your target to ES2017, then it will preserve the async/await.
{
"compilerOptions": {
.....
"target": "ES2017",
.....
}
}
DO make sure your run-time supports it natively!!!
PS: as of Apr 2018, AWS Lambda now supports Nodejs 8. You should be able to use the above config for it.
This feature was already requested here. Targeting es2016 and es2017 should be available in the Community milestone and in TypeScript 2.1.

Gulp workflow for validation and es 6

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);
}

Transpile Async Await proposal with Babel.js?

There is a proposal for introducing C# style async-await. I know Babel.js transpiles ES6 to ES5, but is there any way to make it transpile async-await to ES5?
Babel v6
As of Babel v6, Babel doesn't contain any transformers itself anymore. You have to explicitly specify any feature you want to transform.
Presets - non ES2015 environment
The quickest way to get this working is to use presets which already contain the set of plugins needed to transform ES2015 and newer proposals. For async, you will need the es2015 and es2017 presets and the runtime plugin (don't forget to install babel-runtime as described in the documentation):
{
"presets": [
"es2015",
"es2017"
],
"plugins": [
"transform-runtime"
]
}
Presets - ES2015 environment
If you run the code in an environment that supports ES2015 (more specifically, generators and Promises), then all you need is the es2017 preset:
{
"presets": [
"es2017"
]
}
Custom
To only transform the async functions, you will need the following plugins.
syntax-async-functions is needed in any every case to be able to parse async functions
In order to run the async function, you either need to use
transform-async-to-generator: Converts the async function into a generator. This will use Babel's own "co-routine" implementation.
transform-async-to-module-method: Also converts the async function to a generator, but passes it to the module and method specified in the configuration instead of Babel's own method. This allows you to use external libraries such as bluebird.
If your code runs in an environment that supports generators, then there is nothing left to do. However, if the target environment does not support generators, you will also have to transform the generator. This is done via the transform-regenerator transform. This transform depends on runtime functions, so you will also need Babel's transform-runtime transform (+ the babel-runtime package).
Examples:
Async to generator
{
"plugins": [
"syntax-async-functions",
"transform-async-to-generator"
]
}
Async to module method
{
"plugins": [
"syntax-async-functions",
["transform-async-to-module-method", {
"module": "bluebird",
"method": "coroutine"
}]
]
}
Async to generator + regenerator
{
"plugins": [
"syntax-async-functions",
"transform-async-to-generator",
"transform-regenerator",
"transform-runtime"
]
}
Babel v4 and older
Yes, you have to enable the experimental transformers. Babel uses regenerator.
Usage
$ babel --experimental
babel.transform("code", { experimental: true });
This solution may have changed since (Feb 25 Felix Kling) or perhaps there is still more than one way to use async await.
What worked for us was to run Babel like so
$ npm install babel-runtime
$ babel inputES7.js -o outputES5.js --optional runtime
I got this working as-of today by doing an additional npm install babel-preset-stage-0 and using it like
var babel = require("babel-core");
var transpiled = babel.transform(code, { "presets": ["stage-0"] });
See
http://babeljs.io/docs/usage/options/#options
http://babeljs.io/docs/plugins/preset-stage-0/
Perhaps even more up-to-date now; just put the babel stuff in a separate file:
'use strict';
require('babel/register'); // Imports babel - auto transpiles the other stuff
require('./app'); // this is es6 - gets transpiled
See my code at how-can-i-use-es2016-es7-async-await-in-my-acceptance-tests-for-a-koa-js-app for some more details.
The approved answer seems to be outdated now. The experimental flag has been deprecated in favor of stage.
http://babeljs.io/blog/2015/03/31/5.0.0/#experimental-option
Usage
$ babel --stage 0
babel.transform("code", { stage: 0 });
Stage 0
es7.classProperties
es7.comprehensions
Stage 1
es7.asyncFunctions
es7.decorators
es7.exportExtensions
es7.objectRestSpread
Stage 2 (Stage 2 and above are enabled by default)
es7.exponentiationOperator

How do I concatenate ES6 modules?

How can I concatenate ES6 modules?
var foo = 2; // This would normally be scoped to the module.
export function Bar() {}
// ...concatenate...
import { Bar } from 'javascripts/bar' //This file no longer exists in the concatenated scenario.
export function Bam() {}
Update 2020-09-02: Esperanto was replaced by Rollup some time ago, and is a great choice for this problem. Depending on your needs, Webpack may also be a good choice.
If what you want to do is create a single JavaScript file that does not internally use ES6 modules, so that you can use it with browsers/node today, then I recommend using Esperanto (full disclosure, I'm a maintainer of the project). It allows you to create a bundle that concatenates all of the files together without the use of a loader like you'd get using something like browserify or webpack. This typically results in smaller code (no loader), better dead code elimination (when using a minifier like Google Closure Compiler or UglifyJS), and better performance as the JS interpreter is better able to optimize the result.
Here's an example usage, but note that there are plenty of tools to integrate Esperanto into your workflow:
var fs = require( 'fs' );
var esperanto = require( 'esperanto' );
esperanto.bundle({
base: 'src', // optional, defaults to current dir
entry: 'mean.js' // the '.js' is optional
}).then( function ( bundle ) {
var cjs = bundle.toCjs();
fs.writeFile( 'dist/mean.js', cjs.code );
});
This example is taken from the wiki page on bundling ES6 modules.
I would suggest that you take a look at http://webpack.github.io and then combine it with babel.
alternatively you can do it with babel alone:
https://babeljs.io/docs/usage/cli/

Categories

Resources