How to apply babel plugin for certain dependency? - javascript

I have write a Babel plugin for transpiling some specific dependencies.
To reduce the compiling time of babel, I want to only use it for these dependencies, my babel.config.js is like
module.exports = {
plugins: [["plugin-a"], ["plugin-b"]]
// for `plugin-c` only apply to `./node_modules/certain_dependency`
I find only is a very useful config.
Is it possible to do that on Babel?

Related

How to split a library and its plugins among npm packages?

Im working on a JavaScript library and I want future users to be able to pick and choose the plugins they want to add to their project among with the main library. I'm having few issues with modules and webpack. I'm writing pseudo code to give an idea of how the code is organized.
My index.js for the main library looks like this:
import ClassA from "./classA";
import ClassB from "./classB";
export default class MyLib {
.....
}
export { ClassA, ClassB }
I can easily output the library with webpack:
output: {
path: ...
filename: 'mylib.min.js',
library: "MyLib",
libraryTarget: "umd"
}
To be able to choose which plugins to add, I'm creating different npm packages (one for each plugin), adding MyLib as an external dependency and then doing:
import {ClassA, ClassB} from "MyLib";
class PluginA extends ClassB {
constructor() {
this.test = new ClassA();
}
}
This works perfectly but, when "compiling" PluginA, webpack would include MyLib in the final js file for PluginA. If I want to include multiple plugins the code would end up with multiple copies of the main lib.
My final goal is to organize the code in such a way that can be easily installed with the following npm commands without having duplicated code everywhere:
npm install MyLib
npm install MyLib-PluginA
npm install MyLib-PluginB
Of course, one obvious solution would be to not use webpack for the plugins but I'd like to keep this option as the last resource in case nothing else works.
Thanks!
I wouldn't recommend using webpack to build your plugins/library. Rather, I'd let the consumer of the library decide on their own bundler. Your best step for the library should just be transpilation (if needed) of any intermediate code like babel-featured JS or TypeScript into something that can be safely require'd by node.
In addition, each plugin ought to have MyLib as a peerDependency instead of a regular dependency. That will make sure that MyLib doesn't get nested inside of the plugin's node_modules and will thus avoid duplicates being bundled. The plugins could in addition has MyLib as a devDependency for the sake of unit tests, but the important bit is that it's never a regular dependency.
Digging into webpack documentation, I've found a solution that uses webpack's externals.
From webpack documentation:
The externals configuration option provides a way of excluding dependencies from the output bundles.
I've just added the following lines to the webpack's configuration for the plugin:
module.exports = {
...,
externals: {
mylib: {
commonjs: 'MyLib',
commonjs2: 'MyLib',
amd: 'MyLib',
root: 'MyLib'
}
}
};
Webpack documentation: https://webpack.js.org/configuration/externals/
Hope this will help others.

Where should I insert '#babel/polyfill' into?: main.js or each modules [duplicate]

I just started to use Babel to compile my ES6 javascript code into ES5. When I start to use Promises it looks like it's not working. The Babel website states support for promises via polyfills.
Without any luck, I tried to add:
require("babel/polyfill");
or
import * as p from "babel/polyfill";
With that I'll get the following error on my app bootstrapping:
Cannot find module 'babel/polyfill'
I searched for the module but it seems I'm missing some fundamental thing here. I also tried to add the old and good bluebird NPM but it looks like it's not working.
How to use the polyfills from Babel?
This changed a bit in babel v6.
From the docs:
The polyfill will emulate a full ES6 environment. This polyfill is automatically loaded when using babel-node.
Installation:
$ npm install babel-polyfill
Usage in Node / Browserify / Webpack:
To include the polyfill you need to require it at the top of the entry point to your application.
require("babel-polyfill");
Usage in Browser:
Available from the dist/polyfill.js file within a babel-polyfill npm release. This needs to be included before all your compiled Babel code. You can either prepend it to your compiled code or include it in a <script> before it.
NOTE: Do not require this via browserify etc, use babel-polyfill.
The Babel docs describe this pretty concisely:
Babel includes a polyfill that includes a custom regenerator runtime
and core.js.
This will emulate a full ES6 environment. This polyfill is
automatically loaded when using babel-node and babel/register.
Make sure you require it at the entry-point to your application, before anything else is called. If you're using a tool like webpack, that becomes pretty simple (you can tell webpack to include it in the bundle).
If you're using a tool like gulp-babel or babel-loader, you need to also install the babel package itself to use the polyfill.
Also note that for modules that affect the global scope (polyfills and the like), you can use a terse import to avoid having unused variables in your module:
import 'babel/polyfill';
For Babel version 7, if your are using #babel/preset-env, to include polyfill all you have to do is add a flag 'useBuiltIns' with the value of 'usage' in your babel configuration. There is no need to require or import polyfill at the entry point of your App.
With this flag specified, babel#7 will optimize and only include the polyfills you needs.
To use this flag, after installation:
npm install --save-dev #babel/core #babel/cli #babel/preset-env
npm install --save #babel/polyfill
Simply add the flag:
useBuiltIns: "usage"
to your babel configuration file called "babel.config.js" (also new to Babel#7), under the "#babel/env" section:
// file: babel.config.js
module.exports = () => {
const presets = [
[
"#babel/env",
{
targets: { /* your targeted browser */ },
useBuiltIns: "usage" // <-----------------*** add this
}
]
];
return { presets };
};
Reference:
usage#polyfill
babel-polyfill#usage-in-node-browserify-webpack
babel-preset-env#usebuiltins
Update Aug 2019:
With the release of Babel 7.4.0 (March 19, 2019) #babel/polyfill is deprecated. Instead of installing #babe/polyfill, you will install core-js:
npm install --save core-js#3
A new entry corejs is added to your babel.config.js
// file: babel.config.js
module.exports = () => {
const presets = [
[
"#babel/env",
{
targets: { /* your targeted browser */ },
useBuiltIns: "usage",
corejs: 3 // <----- specify version of corejs used
}
]
];
return { presets };
};
see example: https://github.com/ApolloTang/stackoverflow-eg--babel-v7.4.0-polyfill-w-core-v3
Reference:
7.4.0 Released: core-js 3, static private methods and partial
application
core-js#3, babel and a look into the future
If your package.json looks something like the following:
...
"devDependencies": {
"babel": "^6.5.2",
"babel-eslint": "^6.0.4",
"babel-polyfill": "^6.8.0",
"babel-preset-es2015": "^6.6.0",
"babelify": "^7.3.0",
...
And you get the Cannot find module 'babel/polyfill' error message, then you probably just need to change your import statement FROM:
import "babel/polyfill";
TO:
import "babel-polyfill";
And make sure it comes before any other import statement (not necessarily at the entry point of your application).
Reference: https://babeljs.io/docs/usage/polyfill/
First off, the obvious answer that no one has provided, you need to install Babel into your application:
npm install babel --save
(or babel-core if you instead want to require('babel-core/polyfill')).
Aside from that, I have a grunt task to transpile my es6 and jsx as a build step (i.e. I don't want to use babel/register, which is why I am trying to use babel/polyfill directly in the first place), so I'd like to put more emphasis on this part of #ssube's answer:
Make sure you require it at the entry-point to your application,
before anything else is called
I ran into some weird issue where I was trying to require babel/polyfill from some shared environment startup file and I got the error the user referenced - I think it might have had something to do with how babel orders imports versus requires but I'm unable to reproduce now. Anyway, moving import 'babel/polyfill' as the first line in both my client and server startup scripts fixed the problem.
Note that if you instead want to use require('babel/polyfill') I would make sure all your other module loader statements are also requires and not use imports - avoid mixing the two. In other words, if you have any import statements in your startup script, make import babel/polyfill the first line in your script rather than require('babel/polyfill').
babel-polyfill allows you to use the full set of ES6 features beyond
syntax changes. This includes features such as new built-in objects
like Promises and WeakMap, as well as new static methods like
Array.from or Object.assign.
Without babel-polyfill, babel only allows you to use features like
arrow functions, destructuring, default arguments, and other
syntax-specific features introduced in ES6.
https://www.quora.com/What-does-babel-polyfill-do
https://hackernoon.com/polyfills-everything-you-ever-wanted-to-know-or-maybe-a-bit-less-7c8de164e423
Like Babel says in the docs, for Babel > 7.4.0 the module #babel/polyfill is deprecated, so it's recommended to use directly core-js and regenerator-runtime libraries that before were included in #babel/polyfill.
So this worked for me:
npm install --save core-js#3.6.5
npm install regenerator-runtime
then add to the very top of your initial js file:
import 'core-js/stable';
import 'regenerator-runtime/runtime';

How to publish a library of Vue.js components?

I am working on a project containing a Vuex module and an abstract components that users can extend from.
I would love to publish this on NPM to clean up my codebase and pull this away from my project as a solid well tested module. I have specified the main file in package.json to load an index which imports everything I want to expose:
https://github.com/stephan-v/vue-search-filters/
The index contains this at the moment:
import AbstractFilter from './src/components/filters/abstract/AbstractFilter.vue';
import Search from './src/store/modules/search';
module.exports = {
AbstractFilter,
Search
};
For this to work I need to transpile this since a babel compiler normally won't transpile files imported from node_modules(Correct me if I am wrong here). Besides that I would probably be a good idea to do this so it can be used by different systems.
How do I transpile only the files that I need though with Webpack? Do I have to create a separate config for this?
What does a config like that look like? I know the vue-cli has a build command for one single file component but this is a bit different.
Any tips or suggestions on how to transpile something like this are welcome.
Edit
This seems like a good start as well:
https://github.com/Akryum/vue-share-components
The most import thing for Webpack users to notice is that you need to transpile your files in UMD which can be set by:
libraryTarget: 'umd'
This will make sure your are transpiling for Universal Module Definition, meaning your code will work in different environments like AMD,CommonJS, as a simple script tag, etc.
Besides that it is import to provide the externals property in webpack:
externals: {}
Here you can define which libraries your project users but should not be built into your dist file. For example you don't want the Vue library to be compiled / transpiled into the source of your NPM package.
I will research a bit more, so far the best options looks like to create a custom project myself If I want flexibility and unit testing.
Webpack docs
This is also a useful page which goes in depth about how to publish something with Webpack:
https://webpack.js.org/guides/author-libraries/#add-librarytarget
The best way probably will be to build the module and set main in your package.json to my_dist/my_index.js. Otherwise every project that will use your module will have to add it to include which is tedious.
You will also want your webpack build to follow UMD (Universal Module Definition). For that you must set libraryTarget to umd:
...
output: {
filename: 'index.js',
library:'my_lib_name',
libraryTarget: 'umd'
},
...
Also a good thing will be to add Vue to externals so that you didn't pack extra 200kb of vue library.
externals: {
vue: 'vue'
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
And add it to peerDependencies in package.json:
...
"peerDependencies": {
"vue": "^2.0.0"
},
"devDependencies": {
"vue": "^2.0.0"
}
...
If you need an existing example of how to pack a vue.js component, you can take a look in one of the modules I maintain:
https://github.com/euvl/vue-js-popover
Particularly webpack.config.js and package.json will be interesting for you.
I was searching for a similar solution and found rollup https://github.com/thgh/rollup-plugin-vue2 (but was not able to make it work) and this component https://github.com/leftstick/vue-expand-ball where all the component code gets compiled to one reusable js-file.
I know that's not a proper solution but maybe it's sufficient for your needs.

Using Babel's `sourceRoot` Doesn't Affect Imports

Currently I can do:
require('./frontend/src/components/SomeComponent');
But if I set the following in my webpack.config.js:
resolve: {
root: path.resolve('frontend', 'src')
}
I can instead do:
require('components/SomeComponent');
The problem is, when I don't use Webpack (eg. in a test environment) all of my imports break. According to the Babel docs, the sourceRoot property sets the "root from which all sources are relative." This made me think I could add the following to my .babelrc to fix my imports:
"sourceRoot": "frontend/src"
... but no such luck. When I do require('components/SomeComponent'); in babel-node it fails. When I just use Babel to transpile the file, the require line is the same whether or not I set a sourceRoot.
So, my question is, is there any way (with or without sourceRoot) to simulate webpack's resolve.root in Babel?
P.S. I know there are several Babel plug-ins which address this problem, but all of the ones I've seen require you to add a ~ to the require path (which of course breaks imports in Webpack).
Many project have webpack + babel, and in many projects you sometimes bypass webpack (as in your case - for tests).
In such cases, all the resolve aliases should live in babel.
There are plugins out there to allow one reading the configuration of the other (and similar plugins for eslint etc.).

Browserify and Babel gulp tasks

I want to use both Browserify and Babel with my JavaScript. For this I created a gulp task
gulp.task('babel', function() {
return gulp.src('_babel/*.js')
.pipe(browserify({ insertGlobals : true }))
.pipe(babel({ presets: ['es2015'] }))
.pipe(gulp.dest('_dev/js'));
});
Unfortunately, when I want to use import within my code, I am getting an error:
ParseError: 'import' and 'export' may only appear at the top level
My main js file is very simple:
import 'directives/toggleClass';
I'm guessing that it is because of Babel (and it's use strict addition), but what can I do?
Babel maintains an official transform for Browserify called babelify and it should be used wherever possible if using babel and browserify.
Drop the use of babel directly and place babelify as a transform plugin for browserify. There are many ways to configure browserify but specifying config in your package.json would probably be easiest.
"browserify": {
"transform": [["babelify", { "presets": ["es2015"] }]]
}
Your gulp task will then be simplified
gulp.task('babel', function() {
return gulp.src('_babel/*.js')
.pipe(browserify({ insertGlobals : true }))
.pipe(gulp.dest('_dev/js'));
});
Browserify also exposes methods to do this programmatically so you will be able to configure the bundler from inside your gulp task (dropping the package config, although using the package is perfectly fine for this), check their documentation and experiment, I've done it before but its been a long time since I used gulp (using gulp here is just a complication you dont need, but I expect you are using it elsewhere in your build pipeline where it might be more helpful).

Categories