Exporting a class with Webpack and Babel not working - javascript

I have a very simple setup with Webpack and Babel for a small library.
Before, I had the following architecture to generate a ES5 version of the library:
module.exports.lib = (function () {
/* private part of library here */
return {
... /* public part of library here */
}
})();
Everything is working fine this way, and I even had some ES6 features such as arrow functions inside my library and everything worked. However, I decided to change my approach to a ES6 class, this way:
export default class Library {
}
And now, when I try to do:
var library = new Library();
I get that Library was not defined. Even just evaluating Library returns undefined.
So what I did was turn the file that uses the library into an ES6 file that does import Library from 'libraryfile.js' and it worked again.
However, I'd really like my output library to still be usable from regular ES5 with a <script> tag just like before. Is this possible?
Here's my webpack config file:
module.exports = {
entry: {
pentagine: "./lib/pentagine.js",
demos: ["./demos/helicopter_game/PlayState.js"]
},
output: {
path: __dirname,
filename: "./build/[name].js",
libraryTarget: 'umd'
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015']
}
}
]
}
};

Default exports are stored in the default property of the module. If you want to make your library accessible without users having to know that, you can change your webpack entry file to
module.exports = require('./libraryfile').default;
Also, make sure you have library: 'YourLibraryName' in your webpack config as per webpack.github.io/docs/configuration.html#output-library.

Webpack has changed a lot, now you can get the same results as the Felix Kling answer but with webpack config. You should add the libraryExport key in the output config and set it to "default". That would set your main class as the root content of your library. Here are the docs.
Your webpack config should be like this:
module.exports = {
entry: {
pentagine: "./lib/pentagine.js",
demos: ["./demos/helicopter_game/PlayState.js"]
},
output: {
path: __dirname,
filename: "./build/[name].js",
libraryTarget: 'umd',
libraryExport: 'default' //<-- New line
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015']
}
}
]
}
};

As Matias points out, webpack must be configured to export the default in order to avoid your client code doing const MyLibrary = require('MyLibrary').default
In 2021, using webpack 5, the correct config is:
module.exports = {
output: {
filename: '[name].bundle.js',
library: {
name: 'MyLibrary',
type: 'umd',
export: 'default' //<--- important
},
},
// specify entry and other configs per usual..
}
ref: https://webpack.js.org/configuration/output/#outputlibraryexport

Related

webpack can convert js to es6?

I using webpack to bundle my node application.
I see the result in the bundle that webpack convert from const to var. This is mean that webpack convert my files to es5.
How can I tell webpack to convert to es6? (leave the const as it is and/or use import keyword for example)
app.js
import {test} from './some';
const x = 1;
console.log('test', test);
console.log('this should be const in the bundle, not var. ', x);
And the bundle is:
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _some__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./some */ "./some.js");
var x = 1;
console.log('test', _some__WEBPACK_IMPORTED_MODULE_0__["test"]);
console.log('this should be const in the bundle, not var. ', x);
/***/ }),
my webpack config:
const path = require('path');
module.exports = () => [
{
mode: 'development',
entry: path.resolve(__dirname, './app.js'),
output: {
path: path.resolve(__dirname, './dist')
},
devtool: 'source-map',
target: 'node',
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
}
]
}
}
];
You are using the #babel/preset-env without any options. That will transform the code to ES5 and the documentation says it's not really recommended to use it this way. The whole point of the "env" preset is that you give a target platform and it will automatically apply the transformations, which are needed for that platform. Passing "targets.node"-option value true or "current" will transform the code for the currently used node-version. Using the preset with this option has the additional advantage, that upgrading node.js will not require any changes in Babel configuration and less code will be transformed, if the new node.js supports more of the ES features used.
Support for ECMAScript modules in node.js is still experimental, but you can disable module transformation by passing false to "modules"-option.
options: {
presets: [[
'#babel/preset-env',
{
targets: {
node: "current"
},
modules: false
}
]]
}

Webpack - 'velocity is not defined'

I'm using webpack along with gulp and this is my webpack config:
webpack.config.js
const path = require('path');
var HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
module.exports = {
output: {
publicPath: "./dist/",
path: path.join(__dirname, "/js/"),
filename: "bundle.js"
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
query: {
presets: ["env"]
}
},
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
resolve: {
alias: {
moment: 'moment/src/moment'
}
},
externals: {
jquery: 'jQuery',
$: 'jQuery',
moment: 'moment',
"velocity-animate": 'velocity'
},
plugins: [
new HardSourceWebpackPlugin()
]
};
scripts.js ( This is all that's in this file )
import velocity from 'velocity-animate';
And I get this error
Uncaught ReferenceError: velocity is not defined
Error on this line:
module.exports = velocity;
Am I doing something wrong with the externals configuration?
This works for both moment.js and jQuery, but not for velocity...
I've tried
"velocity-animate": 'velocity'
and
"velocity-animate": 'velocity-animate'
and
"velocity-animate": '"velocity-animate"'
And none of these work. If the first one isn't 'velocity-animate' ( the name of the package ) then Velocity.js gets included in the script anyway. The documentation on this doesn't really explain how to properly configure this
Is it really possible that this use case is so niche that nobody on earth can explain it?
Thanks!
Lead dev of Velocity V2 here.
Doh - we'd missed updating the export of Velocity - I'll get that in later today. We're also in the process of module-ifying it, so you'll be able to import it "normally" within a Webpack project (including tree shaking etc) - that should be done in the next week or so.
Until I push an updated build the name it's exporting as is "Velocity" - note the capital "V" - hopefully later today it'll move over (2.0.2#beta will have the corrected name of "velocity-animate").

How to import jquery in webpack

I have a problem with jquery when i using it on webpack.
My code:
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
entry: {
vendor: [
'./src/main/webapp/js/vendor/jquery-3.3.1.min.js',
// './src/main/webapp/js/vendor/fs.js',
'./src/main/webapp/js/vendor/google-adsense.js',
'./src/main/webapp/js/vendor/jquery.menu-aim.min.js',
'./src/main/webapp/js/vendor/jquery.touchSwipe.min.js',
],
app: './src/main/assets/js/desktop/app.js',
mobile: './src/main/assets/js/mobile/app.js',
touch: './src/main/assets/js/touch/app.js',
},
module: {
rules: [{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
},
{
test: require.resolve('jquery'),
loader: 'expose-loader?jQuery!expose-loader?$'
}
],
},
plugins: [
// new CleanWebpackPlugin(['src/main/webapp/assets']),
new webpack.optimize.CommonsChunkPlugin({
name: 'common' // Specify the common bundle's name.
}),
new UglifyJsPlugin({
test: /\.js$/,
sourceMap: process.env.NODE_ENV === "development"
}),
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
],
output: {
filename: '[name].js',
path: path.resolve(__dirname, './src/main/webapp/js')
}
};
When above code compiles , console throws this error
vendor.js:1 Uncaught ReferenceError: webpackJsonp is not defined
at vendor.js:1
And when i try to use this
externals: {
jquery: 'jQuery'
}
It throws
vendor.js:1 Uncaught ReferenceError: jQuery is not defined
at Object.231 (vendor.js:1)
at o (common.js:1)
at Object.228 (vendor.js:1)
at o (common.js:1)
at window.webpackJsonp (common.js:1)
at vendor.js:1
And i using jquery in my core js file import $ from 'jquery'.
What did i do wrong ? any help ? Thank you.
So there are few themes in your webpack.config.js, some of which conflict. Just going to list them so I can better understand what I think you are trying to achieve.
Theme 1
You have an entry called vendor that is clearly referencing a minified jQuery library you have presumably downloaded and placed in the directory specified.
Theme 2
You also have an expose-loader that is essential exposing the jquery library from its node_modules probably listed in the dependencies of your package.json.
This makes the jquery in the node_modules available as $ and jQuery in the global scope of the page where your bundle is included.
Theme 3
You also have included the ProvidePlugin with configuration for jQuery.
The ProvidePlugin is supposed to inject dependencies into the scope of your module code, meaning you do not need to have import $ from 'jquery' instead $ and jQuery will already be available in all of your modules.
Conclusion
From what I have gathered I think you're trying to bundle jQuery from the static file at ./src/main/webapp/js/vendor/jquery-3.3.1.min.js in a vendor bundle.
You are then trying to expose the libraries in the vendor bundle to the global scope (jQuery).
Then also have your application code able to import jQuery from what is made available by the vendor bundle in the global scope.
Answer
So if that is what you are doing you need to do the following things.
Firstly, check in your package.json files dependencies for jquery. If its there you want to remove it, there's no need for it if you're going to use your jquery-3.3.1.min.js file instead to provide jQuery to your application.
Secondly, change your test of the expose-loader to trigger when it sees your jquery-3.3.1.min.js file in your entries, not resolve it from the jquery dependency from your node_modules.
This regex pattern should do the trick.
{
test: /jquery.+\.js$/,
use: [{
loader: 'expose-loader',
options: 'jQuery'
},{
loader: 'expose-loader',
options: '$'
}]
}
Thirdly, remove the ProvidePlugin if you're going to import the library explicitly with import $ from 'jquery' you do not need it.
Lastly, you need to tell webpack when it sees an import for jquery it can resolve this from window.jQuery in the global scope. You can do this with the externals configuration you already referenced.
externals: {
jquery: 'jQuery'
}
With all these changes you should end up with a webpack.config.js file that looks like this.
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
entry: {
vendor: [
'./src/main/webapp/js/vendor/jquery-3.3.1.min.js',
// './src/main/webapp/js/vendor/fs.js',
'./src/main/webapp/js/vendor/google-adsense.js',
'./src/main/webapp/js/vendor/jquery.menu-aim.min.js',
'./src/main/webapp/js/vendor/jquery.touchSwipe.min.js',
],
app: './src/main/assets/js/desktop/app.js',
mobile: './src/main/assets/js/mobile/app.js',
touch: './src/main/assets/js/touch/app.js',
},
module: {
rules: [{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
},
{
test: /jquery.+\.js$/,
use: [{
loader: 'expose-loader',
options: 'jQuery'
},{
loader: 'expose-loader',
options: '$'
}]
}
],
},
plugins: [
// new CleanWebpackPlugin(['src/main/webapp/assets']),
new webpack.optimize.CommonsChunkPlugin({
name: 'common' // Specify the common bundle's name.
}),
new UglifyJsPlugin({
test: /\.js$/,
sourceMap: process.env.NODE_ENV === "development"
})
],
output: {
filename: '[name].js',
path: path.resolve(__dirname, './src/main/webapp/js')
},
externals: {
jquery: 'jQuery'
}
};
I hope that does not just give you the answer but enough explanation as to where you were going wrong.

Webpack and AWS Lambda issue - handler missing on module

I'm using ES6, babel and Webpack 2 to bundle an AWS Lambda. I am then running/testing it using AWS SAM local. I get the following error when I hit the api -
Handler 'handler' missing on module 'dist/main'
Here is my webpack.config.js -
const path = require('path');
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js',
libraryTarget: 'commonjs'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
plugins: [require('babel-plugin-transform-flow-strip-types')],
presets: [
[
'env',
{
target: { node: 6.10 }, // Node version on AWS Lambda
useBuiltIns: false,
loose: false,
exclude: [],
debug: false
},
],
],
},
}
],
}
};
And here is a snippet of the compiled main.js -
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.handler = handler;
var _amazonCognitoIdentityJs = __webpack_require__(60);
var _aws_profile = __webpack_require__(290);
// A signin Lambda function
function handler(event, context, callback) {
switch (event.httpMethod) {
case "GET":
A little background.... this is a Lambda I initially wrote NOT in ES6 and not bundling using Webpack and it was working. I now need for it to be in ES6 and work with Webpack. N.B. this is Webpack 2
Much thanks...
To fix this issue I had to specify a library property and change the libraryTarget to commonjs2. The webpack.config.js file output now looks like this -
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js',
library: 'main',
libraryTarget: 'commonjs2'
},
I ran into this issue as well. However, I believe my situation was the inverse of what SamBrick shares. I was moving from transpiling ES6 with babel to run on lambda/node 6.10 to no transpiling and targeting lambda/node 8.10. Removing the library field and changing to the libraryTarget: 'commonjs' solved the problem for me.
Props to this guy: https://gist.github.com/nirnanaaa/d7f40deb38f1cf7f931dc7ef0c582bf0
I ended up here because I was being an idiot and was exporting the handler function as default instead of the named function.
export default handler;
... instead of ...
export { handler };

Make webpack's library output compatible with babel6

Babel's 6th version changes the functioning of export default and in particular its relation with commonjs require.
To summarise, while until babel5, require('module') where giving the default export of the module, it now always returns the module object containing all of the exports of the module.
If one only wants the default, he/she must use require('module').default.
As explained here, there is very good reasons behind this and the aim of this question is not to break or hack this behaviour.
However, if one is building a library, he/she usually does not want to distribute a module but the export value of his library (e.g. a function, whatever module system is used internally).
This is well dealt with by webpack and the output.library configuration when using commonjs or AMD. Because prior babel's versions allowed the default export to be required with commonjs, babel was also compatible with this mechanism. However it is not the case anymore: the library now always provides an es6 module object.
Here is an example.
src/main.js
export default "my lib content";
webpack.config.js
var path = require("path");
var webpack = require("webpack");
module.exports = {
entry: {
lib: [ path.resolve(__dirname, "src/main.js") ],
},
output: {
path: path.join(__dirname, "dist"),
filename: "mylib-build.js",
library: 'myLib'
},
module: {
loaders: [
{
test: /\.js$/,
loader: "babel",
include: path.join(__dirname, "src"),
query: { presets: ['es2015'] }
}
]
}
};
test.html
<html>
<head></head>
<body>
<script src="dist/mylib-build.js"></script>
<!-- `myLib` will be attached to `window` -->
<script>
console.log(JSON.stringify(myLib)); // { default: "my lib content" }
</script>
</body>
</html>
This is a very simple example but I obviously want the export of mylib to be the string "my lib content" instead of { default: "my lib content" }.
One solution could be to create an export source file in commonjs to perform the transformation:
module.exports = require('./main').default;
However I find this solution quite poor. One should be able to solve it at the compilation level, without changing the source code.
Any idea?
Was just going at this my self. Whether one like to call it a workaround or solution, there seem to be a Babel plugin that "solve it".
Using the plugin babel-plugin-add-module-exports as referenced in https://stackoverflow.com/a/34778391/1592572
Example config
var webpackOptions = {
entry: {
Lib1: './src/Lib1.js',
Lib2: './src/Lib2.js'
},
output: {
filename: "Master.[name].js",
library: ["Master","[name]"],
libraryTarget: "var"
},
module: {
loaders: [
{
loader: 'babel',
query: {
presets: ['es2015'],
plugins: ["add-module-exports"]
}
}
]
}
};
This yields Master.Lib1 to be lib1 instead of Master.Lib1.default.
Webpack 2 now supports es6 modules which partially solves this issue. Migrating from webpack 1 to webpack 2 is relatively painless. One just needs to remember to disable babel's es6 module to commonjs conversion to make this work:
.babelrc
{
"presets": [
["es2015", {"modules": false}]
]
}
However, unfortunately, it does not work properly with export default (but an issue is opened, hopefully a solution will be released eventually).
EDIT
Good news! Webpack 3 supports the output.libraryExport option that can be used to directly expose the default export:
var path = require("path");
var webpack = require("webpack");
module.exports = {
entry: {
lib: [ path.resolve(__dirname, "src/main.js") ],
},
output: {
path: path.resolve(__dirname, "dist"),
filename: "mylib-build.js",
library: "myLib",
// Expose the default export.
libraryExport: "default"
},
module: {
loaders: [
{
test: /\.js$/,
loader: "babel",
include: path.resolve(__dirname, "src")
}
]
}
};
You can use this solution (this is more like workaround, but it allow you to keep your sources from change):
There is a loader called callback-loader. It allow you to change your sources in a build time by calling a callback and put a result instead of it. In other words you can turn all you require('module') into a require('module').default automatically in a build time.
Here is your config for it:
var webpackConfig = {
module: {
loaders: [
{ test: /\.js$/, exclude: /node_modules/, loader: 'callback' },
...
]
},
...
callbackLoader: {
require: function() {
return 'require("' + Array.prototype.join.call(arguments, ',') + '").default';
}
}
};

Categories

Resources