Aws-sdk-js: Doesn't work with Webpack 2

Created on 2 Feb 2017  ·  19Comments  ·  Source: aws/aws-sdk-js

After hours of trying to get it to work, I am now convinced that aws-sdk-js doesn't work with Webpack 2. Using the latest version from npm install aws-sdk.

Here's what I tried:

const S3 = require('aws-sdk/clients/s3');

const Config = {
    credentials: {
        accessKeyId: //...,
        secretAccessKey: //...,
    },
    apiVersion: '2006-03-01',
    region: //...
}

let _S3 = new S3(Config);
_S3.putObject(<data>, (err, data) => console.log('done'));

Expect to see a file uploaded to S3 and the callback invoked.

Instead this error happened:

Uncaught TypeError: regionConfig is not a function
    at features.constructor.initialize (service.js?9975******:53)
    at features.constructor.Service [as constructor] (service.js?9975******:39)
    at features.constructor (util.js?bbc0******:595)
    at new features.constructor (util.js?bbc0******:595)
    at features.constructor.Service [as constructor] (service.js?9975******:30)
    at new features.constructor (util.js?bbc0******:595)
    at Object.putFile (S3Helper.js?be3d******:53)
    at SendControlView.js?0a68**:38
    at HTMLImageElement.canvasImg.onload (DrawingBoardView.js?09cf**:477)
initialize @ service.js?9975******:53
Service @ service.js?9975******:39
features.constructor @ util.js?bbc0******:595
features.constructor @ util.js?bbc0******:595
Service @ service.js?9975******:30
features.constructor @ util.js?bbc0******:595
putFile @ S3Helper.js?be3d******:53
(anonymous) @ SendControlView.js?0a68**:38
canvasImg.onload @ DrawingBoardView.js?09cf**:477

The best I can debug it to, is that var regionConfig = require('./region_config') in lib/service.js returns Object {rules: Object, patterns: Object} instead of a function.

I'm 90% sure I'm not doing anything wrong.

I can get it to work using the "load dist version" workaround:

import 'aws-sdk/dist/aws-sdk';
const AWS = window.AWS;

But this is not ideal because the whole SDK is 1.2MB and I just need S3.

guidance third-party

Most helpful comment

The above example did not solve this issue. :(

All 19 comments

Hi friendly AWS folks!! If you have any particular questions, we (webpack) are happy to assist. Just drop a line. ❤️🔥❤️

@Swizec
Are you adding the json-loader to your webpack config?

{
  test: /\.json$/, 
  loaders: ['json-loader']
}

Actually, with @Swizec helpful info provided I think I've isolated to cause of breakage. It appears that same location of region_config.js, that there is a region_config.json. webpack v2 introduced a change in which allows users to no longer have to add json-loader to require json files (giving it closer feature parity to node's resolution process). I think this is probably the cause.

@chrisradek spot on. Yeah I'm not 100% sure if nodes resolution pattern would favor .json over .js but if it should not, maybe an issue in our side.

@TheLarkInn
Thanks for the info! It won't hurt anything on our end if we rename one of the region_config files, glad to know that should take care of the issue from our end.

No probs glad to help! 🙅‍♂️🙇

@TheLarkInn
Hmmm, I actually took my build that works with webpack 1 and rebuilt it using webpack 2, removing the json-loader config (and rerunning npm install without json-loader as well). The build runs and the webpage loads, and I'm able to access AWS services.

I tested using webpack version 2.2.1

@Swizec
Can you share what your webpack config looks like? Also, does adding json-loader fix the issue in your case?

I want to make sure that if we make a change, it actually fixes your issue.

Awesome glad to know we didn't (unknowingly) break something. When we get http://github.com/webpack-contrib/webpack-canary finished, we'll add the SDK to our list of covered packages. Ping me if anything else is 🐞 worthy

@chrisradek updated Webpack to 2.2.1, still having the same issue.

Adding the json-loader config didn't help either.

My node version is 6.9.1, npm is 3.10.8, if that's relevant. And I'm running this on a Mac in zsh. Sometimes that affects file list order.

Here's my entire Webpack config, if that helps.

const path = require('path'),
      webpack = require('webpack'),
      ExtractTextPlugin = require("extract-text-webpack-plugin"),
      _ = require('lodash');

const envConfig  = {

    getStage: function(){
        return !process.env.NODE_ENV ? 'development' : process.env.NODE_ENV;
    },
    inDevelopment: function () {
        var stage = this.getStage();
        return stage == 'development';
    },

    inProduction: function () {
        return ['production', 'preproduction', 'qa-1'].indexOf(this.getStage()) >= 0;
    },

    getCDN: function() {
        var CDNs = {
            qa: 'd2jq5p9fb3us4h.cloudfront.net',
            staging: 'd17pwufp6j13bu.cloudfront.net',
            'staging-1':'d1ffm3l6o8qg4r.cloudfront.net',
            'staging-2': 'd2kkumcy9vcw22.cloudfront.net',
            'qa-2': 'd2avcxxv2hdx66.cloudfront.net',
            'qa-1': 'dazirtq81ffe5.cloudfront.net',
            'staging-swizec': 'd9wn1skltg0u7.cloudfront.net',
            production: 'd235oyylauiwpp.cloudfront.net',
            preproduction: 'dlb2dz5gp174o.cloudfront.net',
            scripts: 'd3ndbz27vmttup.cloudfront.net'
        };

        if (!this.inDevelopment()) {
            return "//"+CDNs[process.env.NODE_ENV.toLowerCase()];
        }
        return "";
    }
};

function isVendor(module, count) {
    const userRequest = module.userRequest;

    return userRequest && userRequest.indexOf('node_modules') >= 0;
}

const Apps = {
    test: './app/assets/javascripts/Apps/TestApp/',
    distribution: './app/assets/javascripts/Apps/DistributionApp/index.js',
    corp_transition: './app/assets/javascripts/Apps/CorpTransitionApp/index.js',
    yup_corp: './app/assets/javascripts/Apps/YupCorpApp/',
    mobile_webviews: './app/assets/javascripts/Apps/MobileWebviewsApp/',
    login: './app/assets/javascripts/Apps/LoginApp/',
    tutor_dashboard: './app/assets/javascripts/Apps/TutorDashboardApp/',
    chat_snippet: './app/assets/javascripts/Apps/ChatSnippetApp/',
    written_exam: './app/assets/javascripts/Apps/WrittenExamApp/',
    chat_bot_script: './app/assets/javascripts/Apps/ChatBotScriptApp/',
    whiteboard: './app/assets/javascripts/Apps/WhiteboardApp/',
    tutor_onboarding: './app/assets/javascripts/Apps/TutorOnboardingApp/',
    block_mobile: './app/assets/javascripts/Apps/BlockMobileApp/',
    contact_us_chat: './app/assets/javascripts/Apps/ContactUsChatApp/'
};

const GlobalModules = _.map({
    jquery: ['$', 'jQuery', 'window.jQuery'],
    lodash: ['_'],
    backbone: ['Backbone'],
    'backbone-validation': ['Backbone.Validation'],
    'raven-js': ['Raven'],
    moment: ['moment'],
    string: ['string', 'S'],
    async: ['async']
}, (vars, module) => new webpack.ProvidePlugin(
    _.fromPairs(vars.map(v => [v, module]))
));


let config = module.exports = {

    context: __dirname,

    entry: Object.assign(
        {},
        Apps,
        {
            babel_polyfill: 'babel-polyfill',
            'whatwg-fetch': 'whatwg-fetch'
        }
    ),

    output: {
        filename: envConfig.inDevelopment()
          ? '[name].js'
          : '[name].[chunkhash].js',
        path: 'public/assets/generated',
        publicPath: envConfig.getCDN()+'/assets/generated/'
    },

    module: {

        rules: [

            {
                test: /\.jssssss$/,
                include: [
                    path.resolve(__dirname, "app/assets/javascripts")
                ],
                exclude: [
                    path.resolve(__dirname, "node_modules/")
                ],
                loader: 'eslint-loader',
            },

            {
                test: /\.js$/,
                include: [
                    path.resolve(__dirname, "app/assets/javascripts")
                ],
                exclude: [
                    path.resolve(__dirname, "node_modules/")
                ],
                query: {
                    plugins: ['transform-decorators-legacy',
                              'transform-runtime',
                              'transform-object-rest-spread',
                              'transform-react-constant-elements',
                              'transform-class-properties'],
                    presets: [['es2015', {modules: false}], 'latest', 'react']
                },
                loader: 'babel-loader',
            },

            {
                test: /\.(less|css)$/,
                use: [
                    ExtractTextPlugin.extract({
                        fallbackLoader: "style/useable",
                        loader: "style-loader"
                    }),
                    {
                        loader: 'css-loader?sourceMap',
                        query: {
                            modules: true,
                            importLoaders: 2
                        }
                    },
                    'postcss-loader?sourceMap',
                    'less-loader?sourceMap'
                ]
            },

            {
                test: /\.handlebars$/,
                include: [
                    path.resolve(__dirname, "app/assets/javascripts")
                ],
                loader: "handlebars-loader?helperDirs[]="+__dirname+"/app/assets/javascripts/helpers/handlebars"
            },

            {
                test: /\.(jpg|png|gif|svg)$/,
                loader: "file-loader"
            },

            {
                test: /\.(eot|ttf|woff|woff2|otf)$/,
                loader: "file-loader"
            },

            {
                test: /\.json/,
                loader: "json-loader"
            }
        ]
    },
    plugins: [
        new webpack.NoEmitOnErrorsPlugin(),
        new ExtractTextPlugin( envConfig.inDevelopment() ? "[name]_style.css"
                                                         : "[name]_style.[chunkhash].css"
        ),
    ].concat(
        GlobalModules
    ).concat(
        Object.keys(Apps)
              .map(app => new webpack.optimize.CommonsChunkPlugin({
                  name: `${app}_vendor`,
                  chunks: [app],
                  minChunks: isVendor
              }))
    ).concat([
        new webpack.optimize.CommonsChunkPlugin({
            name: 'manifest',
            chunks: Object.keys(Apps).map(n => `${n}_vendor`),
            minChunks: (module, count) => {
                return count >= Object.keys(Apps).length && isVendor(module)
            }
        })
    ])
};


config.resolve = {
    extensions: ['.json', '.js', '.less', '.css', '.handlebars'],
    modules: [
        'node_modules',
        'app/assets/javascripts',
        'app/assets/stylesheets'
    ],
    alias: {
        handlebars: 'handlebars/dist/handlebars.min.js'
    }
};

if ( envConfig.inProduction()) {
    config.plugins.push(
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: JSON.stringify('production')
            }
        }),
        new webpack.LoaderOptionsPlugin({
            minimize: true,
            debug: false
        }),
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false,
                screw_ie8: true,
                conditionals: true,
                unused: true,
                comparisons: true,
                sequences: true,
                dead_code: true,
                evaluate: true,
                join_vars: true,
                if_return: true
            },
            output: {
                comments: false
            }
        })
    );
    console.log('Webpack production build for Rails, Env: ' +  envConfig.getStage());
}
else {
    config.devtool = 'eval-source-map';
    console.log('****Webpack development build for Rails, Env: ' +  envConfig.getStage());
}

@chrisradek I submitted a PR that fixes this issue for me :)

@Swizec
Thanks, I found out the issue you're seeing happens due to this line in your config:

extensions: ['.json', '.js', '.less', '.css', '.handlebars'],

I'm not sure how this array is read in ( @TheLarkInn might be able to explain), but it seems order matters.
By default '.js' appears before '.json' in this list:
https://webpack.js.org/configuration/resolve/#resolve-extensions

If I take your example and switch '.json' and '.js', I don't get an error anymore.

My assumption is that when webpack sees region_config is required here, it resolves it using .json instead of .js, based on how you've configured your extensions array. I imagine it only resovles it using .json in this case because there is an actual region_config.json file imported as well.

Are you able to update your config to see if that fixes things? In any case, I think we can still make the update in your PR since now we know what's causing the issue and can add tests :)

@chrisradek yup that's exactly it.

enhanced-resolve uses a waterfall pattern for this process and applies extentions left to right via forEach https://github.com/webpack/enhanced-resolve/blob/master/lib/ResolverFactory.js#L170-L172

tl;dr order does matter.

Maybe this is an opportunity for a good suggestion or enhancement to warn a user if they are extending the extentions and change the resolution pattern that they may see adverse side effects.

@chrisradek that totally worked!

If it still makes sense to use my PR, how do I add tests for something like this? Does the test suite support using different webpack configs?

@Swizec
Awesome, glad to hear!
I'd like to write some new tests specifically for webpack, kind of like what we have for the browser builder today. That may be more work than you'd want to put in. I'm happy to do that, or you're welcome to take a crack at it as well. We would need to cover building the SDK with webpack, and then making an unauthenticated request (could use CognitoIdentity for that) to verify the SDK was pulled in correctly.

@chrisradek I'll try to take a crack at it over the weekend. Sounds like I'll learn useful stuff :)

@chrisradek you are welcome to pull down webpack-contrib/webpack-canary as a tool to smoke test with. It allows you to specify version of webpack and version of your library and then installs and runs that libraries tests. Can work in tandem with multiple versions and packages. :) we are.hoping to leverage it like nodejs's CITGM

1342 has been merged in. Thanks for your help on this, @TheLarkInn !

The above example did not solve this issue. :(

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs and link to relevant comments in this thread.

Was this page helpful?
0 / 5 - 0 ratings