Babel does not convert ES6 to JavaScript that is understandable by browsers - javascript

I use Gulp as task manager and Babel for convert my ES6 program to a version that is understandable for browsers, not for Node!
const gulp = require('gulp');
const babel = require('gulp-babel');
gulp.task('default', () =>
gulp.src('src/app.js')
.pipe(babel({
presets: ['es2015']
}))
.pipe(gulp.dest('dist'))
);
and in src/app.js I have:
import { square, diag } from 'lib';
//Some code here....
But, when I run the gulp, it make a file in dist but it converts import which is in app.js file to require keyword that is not understandable for browsers... I thought bable will merge imported files in src/app.js to one file in dist
How can I convert my library code to be supported by browsers using Babel?

Babel's job is to transpile. Combining and minifying scripts is a separate task.
You need to add Browserify, Webpack, RequireJS, or similar, and then tell Babel that's what you're using so it emits code to use those. Some further information on the setup page.

Basicly Babeljs converts your ES6 to ES5, Imagine you have an ES6 code:
const secret = ({ msg = 'es 6 hey!'} = {}) => () => msg;
The above code is in ES6, and Babeljs will help you to convert it to ES5:
'use strict';
var secret = function secret() {
var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var _ref$msg = _ref.msg;
var msg = _ref$msg === undefined ? 'es 6 hey!' : _ref$msg;
return function () {
return msg;
};
};
And require is not a [current] JavaScript standard, you need a 3rd-party library that also have a function named require, libraries like requirejs, browserify, etc.
Even, if you have an ES6 program that includes any export or import keywords when you are working with Babili (online ES6 to ES5 converter) that will notify you that those converted keywords are not supported by browsers:
require is not supported in the browser, you need a commonjs
environment such as node.js/io.js, browserify/webpack etc
Online demo

Related

get gulp version in gulpfile

Is there a way to detect what version of Gulp is running (available to utilize in a gulpfile)?
I've got two separate gulpfile's I'm using amongst different environments, some that require v3 and some v4. For easier version control, I would prefer it if I could combine those files and not have to deal with different file names in different environments to eliminate confusion between multiple developers. Obviously to accomplish this I would need the script to differentiate between versions.
Alternatively to #alireza.salemian's solution, you could try to run the command line version command in javascript:
Depending on your JavaScript backend, your code may vary slightly, but inspired by this post you could run it as below:
const execSync = require('child_process').execSync;
// import { execSync } from 'child_process'; // replace ^ if using ES modules
const output = execSync('gulp -v', { encoding: 'utf-8' }); // the default is 'buffer'
const str_pos = output.search('Local version') + 14;
const gulp_version = output.substring( str_pos, str_pos + 5 );
console.log( 'Gulp version: ' + gulp_version );
You can read package.json and find gulp version
const pkgJson = fs.readFileSync('./package.json', { encoding: 'utf8' });
const pkg = JSON.parse(pkgJson);
const gulpVersion = pkg['devDependencies']['gulp'];
It may not be the best solution, but you can quickly determine the gulp version.

How to trick Node.js to load .js files as ES6 modules?

Node.JS 10 added experimental support for loading ES6 modules, which already work in browsers. That would mean that we could finally use exactly the same files for Node.JS and browsers without any transpiling or polyfills.
Except we can't. Node.js requires .mjs extension for files to be loaded as modules. I tried tricking node by using a symlink, but node got around it:
D:\web\lines>node --experimental-modules ES6test.mjs
(node:7464) ExperimentalWarning: The ESM module loader is experimental.
D:\web\lines\ES6test.js:6
import myLibrary from "./MyFile.mjs";
^^^^^^^^^^^^^^^
I can't think of any other workaround to make this work - which really renders the whole ES6 module support useless.
Can anybody else think of some trick to make Node.js ignore the extension?
You can now import .js file in node v12.x, in 2 steps:
Add the following line in your package.json file:
// package.json
{
"type": "module"
}
Add --experimental-modules flag before the script:
node --experimental-modules index.js
Reference: https://nodejs.org/api/esm.html
Node.js requires all ES modules should have .mjs extension. Since Node.js support of ES modules is experimental, this is subject to change. A proposal and open pull request are expected to address this problem with package.json esm flag and --mode option.
Currently this can be solved with custom ES module loader that hooks into default module resolver and changes module type for some modules:
custom-loader.mjs
import path from 'path';
const ESM_WITH_JS_EXT = './MyFile.js'; // relative to loader path
const ESM_WITH_JS_EXT_URL = new URL(path.dirname(import.meta.url) + `/${ESM_WITH_JS_EXT}`).href;
export function resolve(specifier, parentModuleURL, defaultResolver) {
const resolvedModule = defaultResolver(specifier, parentModuleURL);
if (resolvedModule.url === ESM_WITH_JS_EXT_URL)
resolvedModule.format = 'esm';
return resolvedModule;
}
It is used as:
node --experimental-modules --loader ./custom-loader.mjs ./index.mjs
Since there are fundamental differences in how ES and CommonJS modules are evaluated, the changes should be limited to modules that need them.
I solved exactly this problem with the fabulous esm package. You can enable dynamic (smart) esm module loading package wide, or per run with a flag like this:
node -r esm your/es6/module.js
It also has options to treat every file as a es6 module, or only those ending in '.mjs'. There are other packages out there, but this one just worked.
Import and export modules using ES6 that work with Node.js
Name files with .mjs extension instead of .js
Create files
touch main.mjs lib.mjs
main.js
import { add } from './lib.mjs';
console.log(add(40, 2));
lib.mjs
export let add = (x,y) => {
return x + y
}
Run
node --experimental-modules main.js
Here is a module that does what you need esmjs.mjs
import { readFileSync } from 'fs'
import { fileURLToPath, pathToFileURL } from 'url'
import { dirname, join } from 'path'
export const jsmodule = (test_url_or_path, module_path) => {
const __filename = test_url_or_path.toLowerCase().startsWith('file:')
? fileURLToPath(test_url_or_path)
: test_url_or_path
const __dirname = dirname(__filename)
const abs_path = join(__dirname, module_path)
const file_url = pathToFileURL(abs_path)
const file_buf = readFileSync(file_url)
const b64 = file_buf.toString('base64')
const moduleData = "data:text/javascript;base64," + b64
return import(moduleData)
}
Usage from .mjs module:
const { hey } = await jsmodule(import.meta.url, '../../test-data/mjs.js')
Usage, from .js file:
const { hey } = await jsmodule(__filename, '../../test-data/mjs.js')
Reference & tests on Github
You can do it in this way:
Create a file module2.mjs (the name is up to you)
'use strict';
export function foo() {
return 'foo';
}
Create index.mjs file:
'use strict';
import { foo } from './module2.mjs';
console.log(foo());
Using node 8.11.1 (or 10) you can run it as (command and output provided):
node --experimental-modules index.mjs
(node:60428) ExperimentalWarning: The ESM module loader is experimental.
foo

How to use uglify-es with gulp?

I'm using gulp to uglify my files and it works fine for older Javascript.
There has already been a question about how to uglify an ES6-javascript file: how to uglify javascript classes?
That's because my code does not work (classes are ES5 or smth):
gulp.task('handleJs', () => {
gulp.src('src/frontend/xy/js/file.js')
.pipe(uglify());
}
The answer doesn't seem to be up-to-date anymore because uglify-js-harmony is deprecated (https://www.npmjs.com/package/uglify-js-harmony).
It says, I should use uglify-es (not -js) but I don't find a solution, how to use it with gulp? Which npm-packages do I really need for that and how does the code have to look like?
This is documented to a degree in the README for gulp-uglify. For clarity, I've slightly modified the example there to match your snippet.
You need to npm install the pump, gulp-uglify, and uglify-es packages first (along with any other packages your project needs. Then, you can setup your gulp task similar to as follows:
var uglifyjs = require('uglify-es');
var composer = require('gulp-uglify/composer');
var pump = require('pump');
var minify = composer(uglifyjs, console);
gulp.task('handleJs', function (cb) {
var options = {};
pump([
gulp.src('src/frontend/xy/js/file.js'),
minify(options),
gulp.dest('dist')
],
cb
);
});

How can I mock Webpack's require.context in Jest?

Suppose I have the following module:
var modulesReq = require.context('.', false, /\.js$/);
modulesReq.keys().forEach(function(module) {
modulesReq(module);
});
Jest complains because it doesn't know about require.context:
FAIL /foo/bar.spec.js (0s)
● Runtime Error
- TypeError: require.context is not a function
How can I mock it? I tried using setupTestFrameworkScriptFile Jest configuration but the tests can't see any changes that I've made in require.
I had the same problem, then I've made a 'solution'.
I'm pretty sure that this is not the best choice. I ended up stopping using it, by the points answered here:
https://github.com/facebookincubator/create-react-app/issues/517
https://github.com/facebook/jest/issues/2298
But if you really need it, you should include the polyfill below in every file that you call it (not on the tests file itself, because the require will be no global overridden in a Node environment).
// This condition actually should detect if it's an Node environment
if (typeof require.context === 'undefined') {
const fs = require('fs');
const path = require('path');
require.context = (base = '.', scanSubDirectories = false, regularExpression = /\.js$/) => {
const files = {};
function readDirectory(directory) {
fs.readdirSync(directory).forEach((file) => {
const fullPath = path.resolve(directory, file);
if (fs.statSync(fullPath).isDirectory()) {
if (scanSubDirectories) readDirectory(fullPath);
return;
}
if (!regularExpression.test(fullPath)) return;
files[fullPath] = true;
});
}
readDirectory(path.resolve(__dirname, base));
function Module(file) {
return require(file);
}
Module.keys = () => Object.keys(files);
return Module;
};
}
With this function, you don't need to change any require.context call, it will execute with the same behavior as it would (if it's on webpack it will just use the original implementation, and if it's inside Jest execution, with the polyfill function).
After spending some hours trying each of the answers above. I would like to contribute.
Adding babel-plugin-transform-require-context plugin to .babelrc for test env fixed all the issues.
Install - babel-plugin-transform-require-context here https://www.npmjs.com/package/babel-plugin-transform-require-context (available with yarn too)
Now add plugin to .babelrc
{
"env": {
"test": {
"plugins": ["transform-require-context"]
}
}
}
It will simply transform require-context for test env into dummy fn calls so that code can run safely.
If you are using Babel, look at babel-plugin-require-context-hook. Configuration instructions for Storybook are available at Storyshots | Configure Jest to work with Webpack's require.context(), but they are not Storyshots/Storybook specific.
To summarise:
Install the plugin.
yarn add babel-plugin-require-context-hook --dev
Create a file .jest/register-context.js with the following contents:
import registerRequireContextHook from 'babel-plugin-require-context-hook/register';
registerRequireContextHook();
Configure Jest (the file depends on where you are storing your Jest configuration, e.g. package.json):
setupFiles: ['<rootDir>/.jest/register-context.js']
Add the plugin to .babelrc
{
"presets": ["..."],
"plugins": ["..."],
"env": {
"test": {
"plugins": ["require-context-hook"]
}
}
}
Alternatively, add it to babel.config.js:
module.exports = function(api) {
api.cache(true)
const presets = [...]
const plugins = [...]
if (process.env.NODE_ENV === "test") {
plugins.push("require-context-hook")
}
return {
presets,
plugins
}
}
It may be worth noting that using babel.config.js rather than .babelrc may cause issues. For example, I found that when I defined the require-context-hook plugin in babel.config.js:
Jest 22 didn't pick it up;
Jest 23 picked it up; but
jest --coverage didn't pick it up (perhaps Istanbul isn't up to speed with Babel 7?).
In all cases, a .babelrc configuration was fine.
Remarks on Edmundo Rodrigues's answer
This babel-plugin-require-context-hook plugin uses code that is similar to Edmundo Rodrigues's answer here. Props to Edmundo! Because the plugin is implemented as a Babel plugin, it avoids static analysis issues. e.g. With Edmundo's solution, Webpack warns:
Critical dependency: require function is used in a way in which dependencies cannot be statically extracted
Despite the warnings, Edmundo's solution is the most robust because it doesn't depend on Babel.
Extract the call to a separate module:
// src/js/lib/bundle-loader.js
/* istanbul ignore next */
module.exports = require.context('bundle-loader?lazy!../components/', false, /.*\.vue$/)
Use the new module in the module where you extracted it from:
// src/js/lib/loader.js
const loadModule = require('lib/bundle-loader')
Create a mock for the newly created bundle-loader module:
// test/unit/specs/__mocks__/lib/bundle-loader.js
export default () => () => 'foobar'
Use the mock in your test:
// test/unit/specs/lib/loader.spec.js
jest.mock('lib/bundle-loader')
import Loader from 'lib/loader'
describe('lib/loader', () => {
describe('Loader', () => {
it('should load', () => {
const loader = new Loader('[data-module]')
expect(loader).toBeInstanceOf(Loader)
})
})
})
Alrighty! I had major issues with this and managed to come to a solution that worked for me by using a combination of other answers and the Docs. (Took me a good day though)
For anyone else who is struggling:
Create a file called bundle-loader.js and add something like:
module.exports = {
importFiles: () => {
const r = require.context(<your_path_to_your_files>)
<your_processing>
return <your_processed_files>
}
}
In your code import like:
import bundleLoader from '<your_relative_Path>/bundle-loader'
Use like
let <your_var_name> = bundleLoader.importFiles()
In your test file right underneath other imports:
jest.mock('../../utils/bundle-loader', () => ({
importFiles: () => {
return <this_will_be_what_you_recieve_in_the_test_from_import_files>
}
}))
Installing
babel-plugin-transform-require-context
package and adding the plugin in the .babelrc resolved the issue for me.
Refer to the documentation here:
https://www.npmjs.com/package/babel-plugin-transform-require-context
The easiest and fastest way to solve this problem will be to install require-context.macro
npm install --save-dev require-context.macro
then just replace:
var modulesReq = require.context('.', false, /\.js$/);
with:
var modulesReq = requireContext('.', false, /\.js$/);
Thats it, you should be good to go!
Cheers and good luck!
Implementation problems not mentioned:
Jest prevents out-of-scope variables in mock, like __dirname.
Create React App limits Babel and Jest customization. You need to use src/setupTests.js which is run before every test.
fs is not supported in the browser. You will need something like browserFS. Now your app has file system support, just for dev.
Potential race condition. Export after this import. One of your require.context imports includes that export. I'm sure require takes care of this, but now we are adding a lot of fs work on top of it.
Type checking.
Either #4 or #5 created undefined errors. Type out the imports, no more errors. No more concerns about what can or can't be imported and where.
Motivation for all this? Extensibility. Keeping future modifications limited to one new file. Publishing separate modules is a better approach.
If there's an easier way to import, node would do it. Also this smacks of premature optimization. You end up scrapping everything anyways because you're now using an industry leading platform or utility.
If you're using Jest with test-utils in Vue.
Install these packages:
#vue/cli-plugin-babel
and
babel-plugin-transform-require-context
Then define babel.config.js at the root of the project with this configuration:
module.exports = function(api) {
api.cache(true);
const presets = [
'#vue/cli-plugin-babel/preset'
];
const plugins = [];
if (process.env.NODE_ENV === 'test') {
plugins.push('transform-require-context');
}
return {
presets,
plugins
};
};
This will check if the current process is initiated by Jest and if so, it mocks all the require.context calls.
I faced the same issue with an ejected create-react-app project
and no one from the answers above helped me...
My solution were to copy to config/babelTransform.js the follwoing:
module.exports = babelJest.createTransformer({
presets: [
[
require.resolve('babel-preset-react-app'),
{
runtime: hasJsxRuntime ? 'automatic' : 'classic',
},
],
],
plugins:["transform-require-context"],
babelrc: false,
configFile: false,
});
Simpleset Solution for this
Just Do
var modulesReq = require.context && require.context('.', false, /\.js$/);
if(modulesReq) {
modulesReq.keys().forEach(function(module) {
modulesReq(module);
});
}
So Here I have added extra check if require.context is defined then only execute By Doing this jest will no longer complain

Import existing AMD module into ES6 module

I have an existing application where I have AMD modules defined using RequireJS. I use "text" and "i18n" plugins for requirejs extensively in my project.
I have been experimenting with ES6 modules lately and would like to use them while creating new modules in my application. However, I want to reuse the existing AMD modules and import them while defining my ES6 modules.
Is this even possible? I know Traceur and Babel can create AMD modules from ES6 modules, but that only works for new modules with no dependency on existing AMD modules, but I could not find an example of reusing the existing AMD modules.
Any help will be appreciated. This is a blocker for me right now to start using all ES6 goodies.
Thanks
Yes, it can be done. Create a new application with the following structure:
gulpfile.js
index.html
js/foo.js
js/main.es6
node_modules
Install gulp and gulp-babel. (I prefer to install gulp locally but you may want it globally: that's up to you.)
index.html:
<!DOCTYPE html>
<html>
<head>
<title>Something</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.20/require.js"></script>
<script>
require.config({
baseUrl: "js",
deps: ["main"]
});
</script>
</head>
<body>
</body>
</html>
gulpfile.js:
"use strict";
var gulp = require('gulp');
var babel = require('gulp-babel');
gulp.task("copy", function () {
return gulp.src(["./js/**/*.js", "./index.html"], { base: '.' })
.pipe(gulp.dest("build"));
});
gulp.task("compile-es6", function () {
return gulp.src("js/**/*.es6")
.pipe(babel({"modules": "amd"}))
.pipe(gulp.dest("build/js"));
});
gulp.task("default", ["copy", "compile-es6"]);
js/foo.js:
define(function () {
return {
"foo": "the value of the foo field on module foo."
};
});
js/main.es6:
import foo from "foo";
console.log("in main: ", foo.foo);
After you've run gulp to build the application, open the file build/index.html in your browser. You'll see on the console:
in main: the value of the foo field on module foo.
The ES6 module main was able to load the AMD module foo and use the exported value. It would also be possible to have a native-AMD module load an ES6 module that has been converted to AMD. Once Babel has done its work, they are all AMD modules as far as an AMD loader is concerned.
In addition to #Louis's answer, assuming you already have a bunch of third party libraries specified in require.js configuration, in your new ES6 modules, whenever you are importing a module, be it amd or es6, you'll be fine as long as you keep the imported module name consistent. For example:
Here is the gulpfile:
gulp.task("es6", function () {
return gulp.src("modules/newFolder//es6/*.js")
.pipe(babel({
"presets": ["es2015"],
"plugins": ["transform-es2015-modules-amd"]
// don't forget to install this plugin
}))
.pipe(gulp.dest("modules/newFolder/build"));
});
Here is the es6 file:
import d3 from 'd3';
import myFunc from 'modules/newFolder/es6module'
// ...
This will be compiled to sth like this:
define(['d3', 'modules/newFolder/es6module'], function (_d, _myFunc) {
'use strict';
// ...
});
as long as the module in define(['d3', 'modules/newFolder/es6module'], ... of the compiled file is fine in a original AMD file, it should work with under existing require.js setup, such as compress files etc.
In terms of #coderC's question about require.js loaders, I was using i18n!nls/lang in AMD modules, at first I thought it would be a really tricky thing to find an alternative of AMD plugin loaders in ES6 modules, and I switched to other localization tools such as i18next. But it turned out that it's okay to do this:
import lang from 'i18n!nls/lang';
// import other modules..
because it will be compiled by gulp task to sth like:
define(['d3', 'i18n!nls/lang'], function (_d, _lang) {
// ....
This way, we don't have to worry about the require.js loader.
In a nutshell, in ES6 modules, if you want to use existing AMD plugin/modules, you just need to ensure the compiled file is conformed with the existing setup. Additionally, you can also try the ES6 module bundler Rollup to bundle all the new ES6 files.
Hope this can be helpful for those who are trying to integrate ES6 syntax in project.
A few changes for the latest version of babel:
First, babel({"modules": "amd"}) doesn't work with the latest version of babel. Instead, use babel({"plugins": ["#babel/plugin-transform-modules-amd"]}). (You'll need to install that plugin as a separate module in npm, i.e. with npm install --save-dev #babel/plugin-transform-modules-amd.)
Second, the syntax for gulp.task no longer accepts arrays as its second argument. Instead, use gulp.parallel or gulp.series to create a compound task.
Your gulpfile will end up looking like this:
"use strict";
var gulp = require('gulp');
var babel = require('gulp-babel');
gulp.task("copy", function () {
return gulp.src(["./js/**/*.js", "./index.html"], { base: '.' })
.pipe(gulp.dest("build"));
});
gulp.task("compile-es6", function () {
return gulp.src("js/**/*.es6")
.pipe(babel({"plugins": ["#babel/plugin-transform-modules-amd"]}))
.pipe(gulp.dest("build/js"));
});
gulp.task("default", gulp.parallel("copy", "compile-es6"));

Categories