Webpack and AWS Lambda issue - handler missing on module - javascript

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

Related

MQTT.js and Webpack - "WS is not a constructor"

I am trying to bundle one of our microservices which is using MQTT.js and I am struggling with really strange issue.
It is working fine without bundling, so ws is available in node_modules.
Stuff which I think matters:
error:
TypeError: WS is not a constructor
at WebSocketStream (dist/index.js:159329:16)
at createWebSocket (dist/index.js:147450:10)
at Object.buildBuilderBrowser (dist/index.js:147476:10)
at MqttClient.wrapper [as streamBuilder] (dist/index.js:147937:36)
at MqttClient._setupStream (dist/index.js:146471:22)
at new MqttClient (dist/index.js:146452:8)
at Function.connect (dist/index.js:147940:10)
webpack config:
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const { NODE_ENV = 'production' } = process.env;
module.exports = {
entry: { index: './src/index.ts' },
mode: NODE_ENV,
target: 'node',
watch: NODE_ENV === 'development',
externals: [nodeExternals()],
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
},
resolve: {
extensions: ['.ts', '.js'],
},
node: {
__dirname: false,
},
module: {
rules: [
{
test: /\.ts$/,
use: [{ loader: 'ts-loader', options: { transpileOnly: true } }],
},
{
test: /(\.md|\.map)$/,
loader: 'null-loader',
},
],
},
};
Function where it happens:
createMqttClient(): MqttClient {
return mqtt.connect(this.mqttOptions.url, { ...this.mqttOptions.options });
}
The url is like: ssl://url-to-our-mqtt
Can anybody help please?
I also ran into same issue.
The problem for me was that I used
plugins: [
new webpack.NormalModuleReplacementPlugin(/^mqtt$/, "mqtt/dist/mqtt.js"),
],
in webpack.config.js order to fix the shebang error that comes with mqtt.js since it is a CLI tool.
Then instead I have used
{
test: [
/node_modules[/\\]mqtt[/\\]mqtt.js/,
/node_modules[/\\]mqtt[/\\]bin[/\\]sub.js/,
/node_modules[/\\]mqtt[/\\]bin[/\\]pub.js/,
],
loader: 'shebang-loader'
},
And my problem was fixed. Do you also use mqtt/dist/mqtt.js instead of mqtt in your imports or if you do something similar to mine, the shebang-loader rule I have posted above might solve your problem.
I experienced the same with Amazon aws-iot-device-sdk-js and Microsoft azure-iot-device-mqtt which both include mqtt.
The initial issue is the build error:
ERROR in ./node_modules/mqtt/mqtt.js Module parse failed: Unexpected character '#' (1:0)
This error is caused by the package mqtt. Three files (mqtt.js, pub.js and sub.js) contain a shebang line
#!/usr/bin/env node
The solution using module replacement suggested some places
plugins: [
new webpack.NormalModuleReplacementPlugin(/^mqtt$/, "mqtt/dist/mqtt.js"),
],
unfortunately changes the build error with the run time error
TypeError: WS is not a constructor
As mentioned in other answers, webpack can be configured (https://webpack.js.org/concepts/loaders/) to use the shebang loader (https://www.npmjs.com/package/shebang-loader)
TL;DR
Install shebang-loader
npm install shebang-loader --save
In webpack.config.js use the loader
module.exports = {
...
module: {
rules: [
{
test:
[
/.*mqtt\.js$/,
/.*sub\.js$/,
/.*pub\.js$/
],
use: 'shebang-loader'
}
]
}
}

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 with requirejs/AMD

I'm working on a new module for an existing project that still uses requireJS for module loading. I'm trying to use new technologies for my new module like webpack (which allows me to use es6 loaders using es6 imports). It seems like webpack can't reconcile with requireJS syntax. It will say things like: "Module not found: Error: Can't resolve in ".
Problem: Webpack won't bundle files with requireJS/AMD syntax in them.
Question: Is there any way to make webpack play nice with requireJS?
My final output must be in AMD format in order for the project to properly load it. Thanks.
I had the same question and I managed to achieve it. Below is the same webpack.config.js file.
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
let basePath = path.join(__dirname, '/');
let config = {
// Entry, file to be bundled
entry: {
'main': basePath + '/src/main.js',
},
devtool: 'source-map',
output: {
// Output directory
path: basePath + '/dist/',
library: '[name]',
// [hash:6] with add a SHA based on file changes if the env is build
filename: env === EnvEnum.BUILD ? '[name]-[hash:6].min.js' : '[name].min.js',
libraryTarget: 'amd',
umdNamedDefine: true
},
module: {
rules: [{
test: /(\.js)$/,
exclude: /(node_modules|bower_components)/,
use: {
// babel-loader to convert ES6 code to ES5 + amdCleaning requirejs code into simple JS code, taking care of modules to load as desired
loader: 'babel-loader',
options: {
presets: ['es2015'],
plugins: []
}
}
}, { test: /jQuery/, loader: 'expose-loader?$' },
{ test: /application/, loader: 'expose-loader?application' },
{ test: /base64/, loader: 'exports-loader?Base64' }
]
},
resolve: {
alias: {
'jQuery': 'bower_components/jquery/dist/jquery.min',
'application': 'main',
'base64': 'vendor/base64'
},
modules: [
// Files path which will be referenced while bundling
'src/**/*.js',
'src/bower_components',
path.resolve('./src')
],
extensions: ['.js'] // File types
},
plugins: [
]
};
module.exports = config;

Exporting a class with Webpack and Babel not working

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

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