0

So basically, I'm trying to make a hybrid structure for an MVC project. The frontend framework will be managed by Webpack and VueJS. However, after weeks of tinkering and picking up proper Webpack knowledge together with Vue, i've been unable to achieve what I want to do so.

This is the structure of how webpack works in the MVC project

So right above is the project structure, but specifically the webpack layer. The webpack folder will first be packed by Webpack into the wwwroot/dist folder, which would end up like this;

wwwroot structure or items

From here, we'll be able to import the bundles into the main layout of the MVC's view which we can then apply Vue inline to each and every view. The goal in doing this is such that we can first,

  1. Bundle styles and commonly used js libraries with Webpack
  2. Be able to utilize Vue and Vue's components while being able to create a chunk structure (0.js, 1, 2 ....)
  3. Because of 2, we'll be able to lean abit towards CSR (Client Side Rendering).

Here's my webpack.config.js for reference.

const path = require('path');
const webpack = require('webpack');
const MergeIntoSingleFilePlugin = require('webpack-merge-and-include-globally');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractCSS = new ExtractTextPlugin('bundle.css');

// Declaring multiple modules
// https://stackoverflow.com/questions/16631064/declare-multiple-module-exports-in-node-js
module.exports = function (env) {
    env = env || {};
    var isProd = env.NODE_ENV === 'production';

    // Setup base config for all environments
    var config = {
        entry: {
            main: './Webpack/js/main'
        },
        output: {
            // The format for the outputted files
            filename: '[name].js',
            // Put the files in "wwwroot/js/"
            path: path.resolve(__dirname, 'wwwroot/dist/')
        },
        devtool: 'eval-source-map',
        resolve: {
            alias: {
                'vue': 'vue/dist/vue.esm.js' // Use the full build
            },
            extensions: ['.js', '.jsx']
        },
        plugins: [
            extractCSS,
            new webpack.ProvidePlugin({ 
                $: "jquery",
                jQuery: "jquery",
                "window.jQuery": "jquery'",
                "window.$": "jquery",
                "dt": "datatables.net",
                Popper: ['popper.js', 'default']
            }),
            new MergeIntoSingleFilePlugin({
                files: {
                    // Table related libraries
                    "tbl.js": [
                        'node_modules/datatables.net/js/jquery.dataTables.js',
                        'node_modules/datatables.net-bs4/js/dataTables.bootstrap4.js'
                    ],
                    "tbl.css": [
                        'node_modules/datatables.net-bs4/css/dataTables.bootstrap4.css',
                        'node_modules/datatables.net-buttons-bs4/css/buttons.bootstrap4.min.css'
                    ],
                    "duo-web.js": [
                        'Webpack/js/securo/Duo-Web-v2.js'
                    ]
                }
            })
        ],
        module: {
          rules: [
            { test: /\.css$/, use: extractCSS.extract(['css-loader?minimize']) },
            { test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' },
            { test: /\.(png|woff|woff2|eot|ttf|svg)(\?|$)/, use: 'url-loader?limit=100000' },
            // Recognise VueJS
            {
                test: /\.vue$/,
                loader: 'vue-loader'
            },
            // Expose jQuery globally
            // https://stackoverflow.com/questions/47469228/jquery-is-not-defined-using-webpack
            {
                test: require.resolve('jquery'),
                use: [{
                    loader: 'expose-loader',
                    options: 'jQuery'
                },{
                    loader: 'expose-loader',
                    options: '$'
                }]
            },
            {
                test: require.resolve('bootbox'),
                use: [{
                    loader: 'expose-loader',
                    options: 'bootbox'
                }]
            },
            {
                test: require.resolve('clipboard'),
                use: [{
                    loader: 'expose-loader',
                    options: 'Clipboard'
                }]
            },
          ],
          loaders: [
            {
                test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
                loader: "url-loader"
            },
            {
                test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
                loader: "url-loader"
            },
            {
                test: /\.vue$/,
                loader: 'vue-loader'
            }
          ]
        }
      };

      // Alter config for prod environment
      if (isProd) {
        config.devtool = 'source-map';
        config.plugins = config.plugins.concat([
            new UglifyJsPlugin({
                sourceMap: true
            }),
            // https://v2.vuejs.org/v2/guide/deployment.html
            new webpack.DefinePlugin({
                'process.env': {
                    NODE_ENV: '"production"'
                }
            })
        ]);
      }

      return config;
};

function toObject(paths) {
    var ret = {};
    paths.forEach(function (path) {
        ret[path.split('/').slice(-1)[0]] = path;
    });
    return ret;
}

And here's the main.js

// Load the css first
import 'bootstrap/dist/css/bootstrap.css';
import '../css/counter-ui.css';
import '../css/site.css';
import 'font-awesome/css/font-awesome.css';
// Then the js
import Vue from 'vue'; // Compile included builds https://github.com/nuxt/nuxt.js/issues/1142
import 'jquery';
import 'jquery-validation';
import 'jquery-validation-unobtrusive';
import 'popper.js';
import 'bootstrap';
import 'bootstrap-datepicker';
import 'bootbox';
import 'clipboard';
import 'magicsuggest';
import 'nouislider';
import '../js/counter-ui.js';
import 'formcache';

// Vue Components
Vue.component(/* webpackChunkName: "base-select" */ 'base-select', () => import('./components/base-select.vue'));

// Expose Vue globally
// https://stackoverflow.com/questions/45388795/uncaught-referenceerror-vue-is-not-defined-when-put-vue-setting-in-index-html
window.Vue = Vue;

$(function() {
    $('.datepicker').datepicker();
    $('.dropdown-toggle').dropdown();
    $('[data-toggle="popover"]').popover({
        animation: true,
    });
});
 
// Array Dupes Filter
// https://stackoverflow.com/questions/6940103/how-do-i-make-an-array-with-unique-elements-i-e-remove-duplicates
function OmitArrDupes(a) {
    var temp = {};
    for (var i = 0; i < a.length; i++)
        temp[a[i]] = true;
    var r = [];
    for (var k in temp)
        r.push(k);
    return r;
}

As you can see, webpack is just solely used for bundling common libraries and styles while at the same time, it's used to store Vue and the components i'll be making, and help out with all the dirty work.

You will then eventually end up with:

enter image description here

And that is how you'll be able to get stuck like me, or as far as know, stupid as me. I'm turning into a degen after picking this up for production use.

Judging by the console errors on chrome, 0.js loaded but aborted because of the component side of things. So what exactly is going on? Am really curious to know how this is actually working. Have never been so deep into frontend before.

EDIT It could be due to the component's syntax. Might be wrong? hmm.. But if that's the case, Webpack would be out of the topic.

<template>
    <div :class="{ __disabled: disabled }" class="dropdown">
        <button @click="toggle" class="btn btn-primary dropdown-toggle">
            {{ currOption.name }}
        </button>
        <div v-if="opened" class="dropdown-menu">
            <div v-for="o in options" :value="getVal(value)" @click="change(o)" :class="{__active: getVal(o) == getVal(value)}" class="dropdown_item">{{ getLabel(o) }}</div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "base-select",
        data() {
            return {
                opened: false,
                currOption: {}
            }
        },
        methods: {
            getVal(opt) {
                return !this.valueKey ? opt : opt[this.valueKey];
            },
            getLabel(opt) {
                return !this.labelKey ? opt : opt[this.labelKey];
            },
            change(opt) {
                this.$emit('input', opt)
                this.opened = false;

                if (this.onChange !== undefined) {
                    this.onChange(this.value);
                }
            },
            toggle() {
                if (this.disabled) {
                    return
                }
                
                // flip
                this.opened = !this.opened;
            }
        },
        props: {
            value: {
                required: true
            },
            options: {
                type: Array,
                required: true
            },
            valueKey: {
                type: String,
                required: false
            },
            labelKey: {
                type: String,
                required: false
            },
            onChange: {
                type: Function,
                required: false
            },
            disabled: {
                type: Boolean,
                default: false
            }
        }
    }
</script>

<style scoped>

</style>

UPDATE 2 Attempted to create a test.vue component, which has a template with a hi

Didn't work either. Same error.

tony19
  • 125,647
  • 18
  • 229
  • 307
Nicholas
  • 1,883
  • 21
  • 39

2 Answers2

1

I believe this may be caused by your async / dynamic import syntax which recently changed for vue-loader, which now uses "...ES modules internally to take advantage of webpack 3 scope hoisting." (see release notes below)

Try your import like this

Vue.component('base-select',  () => import('./components/base-select.vue').then(m => m.default));

https://github.com/vuejs/vue-loader/releases/tag/v13.0.0

Edit try changing the output chunk file name to a relative path like in the following.

  output: {
         // The format for the outputted files
        filename: '[name].js',

        // Put the files in "wwwroot/js/"
        path: path.resolve(__dirname, 'wwwroot/dist/')

        // Set chuck file name
         chunkFilename:'../../js/[name].bundle.js'
    },
skribe
  • 3,595
  • 4
  • 25
  • 36
  • Thanks mate for the prompt answer but.. sigh ended up with – Nicholas Mar 09 '18 at 12:23
  • Vue warn]: Failed to resolve async component: () => __webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 34)).then(m => m.default) Reason: Error: Loading chunk 0 failed. – Nicholas Mar 09 '18 at 12:23
  • Maybe try setting implicitly where the chunk files go? See my edit above. You will have to set the relative path to match your needs obviously. – skribe Mar 09 '18 at 12:36
  • looks like it doesn't matter, tried and worked. But the same error persists. Could it be my template that is errorneous? – Nicholas Mar 09 '18 at 13:08
  • okay. Added the component for reference in case its really bad – Nicholas Mar 09 '18 at 13:10
  • Okay emptied the template, nope. Even a clean template failed – Nicholas Mar 09 '18 at 13:26
1

Apart from what @skribe said,

Vue.component('base-select',  () => import('./components/base-select.vue').then(m => m.default));

That should expose all components globally. In order to support the routings called by .NET Core, you have to add an additional parameter called publicPath. publicPath allows you to globally expose files relatively to a root public path that you have declared, which in this case is publicPath.

module.exports = function (env) {
    env = env || {};
    var isProd = env.NODE_ENV === 'production';

    // Setup base config for all environments
    var config = {
        entry: {
            main: './Webpack/js/main'
        },
        output: {
            // The format for the outputted files
            filename: '[name].js',
            // Put the files in "wwwroot/js/"
            path: path.resolve(__dirname, 'wwwroot/dist/'),
            // ============ ADD THE LINE BELOW ============= //
            publicPath: '/dist/'
        },
Nicholas
  • 1,883
  • 21
  • 39