0
votes

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://stackguides.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://stackguides.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://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://stackguides.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://stackguides.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.

2

2 Answers

1
votes

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'
    },
1
votes

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/'
        },