Vue.js dynamic component loading + hot reload - ecmascript-6

I have Laravel 5.3.21 application with Vue.js 1.0.28
I'm using hot-reload workflow with browserify-hmr plugin.
Here is the simple gulpfile.js used to achieve that:
var elixir = require('laravel-elixir');
var gutil = require('gulp-util');
require('laravel-elixir-browserify-official');
require('laravel-elixir-vueify');
// If 'gulp watch' is run
if (gutil.env._.indexOf('watch') > -1) {
// Enable watchify for faster builds
elixir.config.js.browserify.watchify.enabled = true;
// Add the browserify HMR plugin
elixir.config.js.browserify.plugins.push({
name: 'browserify-hmr',
options: {
url: 'http://1.2.3.4:2096',
hostname: '1.2.3.4',
port: 2096
}
})
}
elixir.config.js.browserify.watchify.options.poll = true;
elixir(function (mix) {
mix.copy('node_modules/datatables.net-bs/css/dataTables.bootstrap.css',
'resources/assets/css/vendor/dataTables.bootstrap.css');
mix.styles([
'vendor/dataTables.bootstrap.css'
]);
mix.sass('app.scss');
mix.browserify('app.js');
});
I need to load components dynamically from the resources/assets/js/views/ folder, so I could make my front-end code modular and based on current route name $Laravel->routeName = request()->route()->getName() in Laravel.
For example:
// Global Settings.
Route::get('admin/settings', 'Admin#settings')
->name('admin_global_settings');
Then in resources/assets/js/views/admin/admin_global_settings.js I have code to initialize Vue.js component and register it with Vue.js instance:
var FeaturedOpportunities = require( '../../components/Featured-Opportunities.vue' );
window.Vue.component('FeaturedOpportunities', FeaturedOpportunities);
That is all nice but here is the problem in resources/assets/js/app.js:
window.Vue = require('vue');
require('vue-resource');
Vue.http.interceptors.push((request, next) => {
request.headers.set('X-CSRF-TOKEN', Laravel.csrfToken);
next();
});
// Problem is here, have to keep track of all my routes and their corresponding modules:
var routes = {
'organization_invites_roles': function () {
require('./views/organization/organization_invites_roles');
},
'admin_global_settings': function () {
require('./views/admin/admin_global_settings');
},
};
// Is there a way to load it dynamically?
if (Laravel.routeName) {
if (routes[Laravel.routeName]) {
routes[Laravel.routeName]();
}
}
new Vue({
el: 'body',
components: {
},
ready() {
console.log('Vue and Vueify all set to go!');
}
});
I've found some way that could probably solve this issue: Compiling dynamically required modules with Browserify but not sure if this applicable for my case.

Related

Gulp 4 - How to generate dynamic tasks based on two arrays

I'm building an e-mail generation pipeline (multiple templates) using nunjucks and json translation files. This means I need to loop over the multiple templates and the translation files, however I can't seem to get it working.
Tried adding another loop inside the templates.map(), but that doesn't seem to be working (or I'm doing it completely wrong ofcourse). It almost works, but it crashes at some point, generating only a few of the templates. The first template works, but it crashes at the second template:
The following tasks did not complete: <anonymous>
Did you forget to signal async completion?
source: https://cobwwweb.com/dynamic-tasks-gulp-4
var templates = [];
var languages = ["nl", "en"];
function generateTemplates(done) {
const tasks = templates.map((template) => {
return () => {
const langs = languages.map((lang) => {
return () =>
gulp.src(`source/templates/${template}`)
.pipe(data(function () {
return require(`./source/translations/${lang}/${template.split('.')[0] }.json`);
}))
.pipe(nunjucksRender({
path: ['source/partials']
}))
.pipe(gulp.dest('dist/' + lang));
});
return gulp.series(...langs, (seriesDone) => {
seriesDone();
})();
}
});
return gulp.series(...tasks, (seriesDone) => {
seriesDone();
done();
})();
}
I also tried generating tasks using 2 for-loops, but this only generates the last template of the array of the last language in the array (example: only en/template2 will be generated correctly). I do see in the console that the tasks are starting and finishing, but I don't see them anywhere. Maybe the loop is finished mush faster than the generation of tasks? :
var templates = fs.readdirSync('./source/templates');
var languages = ["nl", "en"];
for (var lang of languages) {
for (var template of templates) {
gulp.task(`${lang}-${template}`, function (done) {
return gulp.src(`source/templates/${template}`)
.pipe(data(function () {
return require(`./source/translations/${lang}/${template.split('.')[0]}.json`);
}))
.pipe(nunjucksRender({
path: ['source/partials']
}))
.pipe(gulp.dest(`dist/${lang}`));
});
tasks.push(`${lang}-${template}`);
}
}
gulp.task('genlang', gulp.series(tasks));
My folder structure:
/dist
/source
--/partials
--/templates
--/template1.html
--/template2.html
--/translations
--/en
--/template1.json
--/template2.json
--/nl
--/template1.json
--/template2.json
Fixed it myself, I needed to have some done cb's in the returns:
function generateTemplates(done) {
const tasks = templates.map((template) => {
return (doneTasks) => {
const langs = languages.map((lang) => {
return (doneLanguages) => {
gulp.src(`source/templates/${template}`)
.pipe(data(() => require(`./source/translations/${lang}/${template.split('.')[0]}.json`)))
.pipe(nunjucksRender({
path: ['source/partials']
}))
.pipe(gulp.dest('./dist/' + lang));
doneLanguages();
}
});
return gulp.parallel(...langs, (seriesDone) => {
seriesDone();
doneTasks();
})();
};
});

How do I load a WASM module in a Vue component without initializing the module every time?

I have created a Rust library of type cdylib using
cargo web build --target=wasm32-unknown-unknown
I use a modified version of the "rust-wasm-loader" NPM package to build and load the WASM file. rust-wasm-loader uses this as a way to use the Rust code:
const wasm = require('./main.rs')
wasm.initialize().then(module => {
// Use your module here
const doub = module.cwrap('doub', 'number', ['number'])
console.log(doub(21))
})
I do not want to initialize the module every time I want to use the code. How do I load the module and use it like a library?
Since the loading of WebAssembly is asynchronous and may actually take some time for large modules, you need to handle the state when the module is not loaded, and then let the rest of the application know when the WebAssembly module is loaded.
You do not say how you are handling state in your Vue application, but if you are e.g. using Vuex you can do something like this:
const doubPlugin = store => {
wasm.initialize().then(module => {
const doub = module.cwrap('doub', 'number', ['number'])
store.subscribe((mutation, state) => {
if (mutation.type === 'DOUB_REQUEST') {
store.commit('DOUB_RESULT', doub(mutation.payload))
}
})
store.commit('DOUB_READY')
})
}
const store = new Vuex.Store({
state,
mutations,
plugins: [doubPlugin]
})
I've done a similar thing in an Elm/WebAssembly application (relevant JavaScript), so if you want to see how this can be applied in practice you can check that out.
Making a wrapper JS module that performs initialization and re-exports the promise seems like the most straightforward approach.
// main.js
module.exports = require("./main.rs").initialize().then(module => {
return {
doub: module.cwrap('doub', 'number', ['number'])
};
});
Then anything can do
require("./main.js").then(api => {
console.log(api.doub(21));
});
and will always get the same module. Or alternatively you could invert the async part and do
// main.js
const api = require("./main.rs").initialize().then(module => {
return {
doub: module.cwrap('doub', 'number', ['number'])
};
});
exports.doub = async function (val) {
return (await api).doub(val);
};
Then users of your module could do
const api = require("./main.js");
api.doub(21).then(result => {
console.log(result);
});
I created a class to wrap the WebAssembly loading and created a cwrap for every function:
class mkLib {
ready = false
_mod = require("./main.rs").initialize().then(module => {
this._mod = module
this.doub = module.cwrap('doub', 'number', ['number'])
this.ready = true
})
}
export default mkLib
In the Vue component's data there is a variable for the new class and in watch I wait for a change in the ready property:
data () {
return {
mod: new mkLib,
ready: false
}
},
watch: {
'mod.ready': function () {
this.ready = true
// now this.mod.FUNC(PARAMS) can be used
console.log(this.mod.doub(20))
}
}

load data from chrome.storage into vue.js data

I'm building a chrome app and I use Vue.js for the options page.
So I want to load settings from the chrome storage and put it into the vue data.
My problem is, that i can not access the vue compontens from inside the chrome storage callback. Every time i call it inside the callback, all vue elements are undefined.
Is there a way, to let the chrome storage cb function return a value, or give it an extra callback.
Here is my code
name: 'app',
data: function () {
return {
data []
}
},
methods: {
init: function() {
chrome.storage.sync.get('series', function (storageData) {
this.data = storageData //this is not possible, because this.data is undefined
})
});
}
},
created: function () {
this.init();
}
}
If using ES6 and transpiling (preferred approach). Note: arrow functions don't create a new context.
init: function() {
chrome.storage.sync.get('series', storageData => {
this.data = storageData
});
}
ES5 workaround:
init: function() {
var self = this;
chrome.storage.sync.get('series', function (storageData) {
self.data = storageData
});
}

Read local file in AngularJS

I used to work with RequireJS and Backbone and used requirejs/text and requirejs-plugins to load local json files I normally use for configuration.
How does one achieve the same with AngularJS?
Everyone seems to suggest to use $http, but is this the only way?
Do I really need to make 20 calls if I have 20 configuration files?
Maybe something like ng-constant is the "preferred" way?
This is what I did. But it uses $http though so I'm hoping someone has a better solution.
app.js:
var myModule = angular.module('myApp', []);
myModule.config(function($routeProvider, $locationProvider) {
$routeProvider.when('/', {
templateUrl: 'html/home.html',
controller: 'MainCtrl as ctrl',
resolve: {
initializeData: function($q, $timeout, myService) {
return myService.promiseToHaveData();
}
}
});
});
myService.js:
var myModule = angular.module('myApp');
myModule.service('myService', function($http, $q) {
var _this = this;
this.promiseToHaveData = function() {
var defer = $q.defer();
$http.get('someFile.json')
.success(function(data) {
angular.extend(_this, data);
defer.resolve();
})
.error(function() {
defer.reject('could not find someFile.json');
});
return defer.promise;
}
});
Then I can inject myService anywhere and it will have all the fields from the json file.
I guess alternatively you could just make your .json files .js files, have them expose a global variable, and reference them in your index.html
Can you use jQuery's getJSON function?
E.g something like:
$.getJSON("config-1.json", function( data ) {
// do whatever you want
});
Here's an AngularJs service that uses the FileReader API:
http://odetocode.com/blogs/scott/archive/2013/07/03/building-a-filereader-service-for-angularjs-the-service.aspx

How to configure an AngularJS app at load time?

I want to do something like this (but obviously not this exactly, because this function doesn't work this way)
angular.bootstrap( $("#myelement"), ['myModule'], {foo: bar} );
I want to pass in a configuration object, since we may want to have more than one instance of the app on a page, with different settings, etc. All I can think of are ugly workarounds. I'm thinking the best thing would be to override an "Options" service of my own making, but I still can't figure out the proper way to do that (tersely).
Thanks in advance!
How about you try something like this:
angular.module('configFoo', []).run(function() {});
angular.module('configBar', []).run(function() {});
angular.bootstrap(myEl, ['myModule', 'configFoo']);
angular.bootstrap(myOtherEl, ['myModule', 'configBar']);
http://docs.angularjs.org/api/angular.Module for all available module methods (you're probably only interested in .run() and .config())
Here is a working code:
http://jsfiddle.net/x060aph7/
angular.module('myModule', [])
.controller('myController', function($scope,myConfig) {
$scope.name = 'inst '+myConfig.foo;
})
;
var aConfig = [{foo:1},{foo:2},{foo:3}];
aConfig.forEach(function(config){
angular.module('fooConfig',[]).value('myConfig', config);
angular.bootstrap(getDiv(), ['myModule','fooConfig']);
});
function getDiv(){
var mDiv = document.createElement('div');
mDiv.setAttribute('ng-controller','myController');
mDiv.innerHTML = '<span>{{name}}</span>';
document.body.appendChild(mDiv);
return mDiv;
}
The following example helped us out bootstrapping a widget to a page. First a div is made - with a bit of jQuery - for the widget to load a template with an ng-include, it is controlled by WidgetLogoController. Next a module WidgetConfig is created that holds the widget's configuration.
$('#pageWidget').html(`<ng-include src="'/dist/templates/widgetLogo.html'"></ng-include>`)
.attr('ng-controller','WidgetLogoController');
var widgetConfig = {
'widgetId': data.pageWidgetId,
'areaId': data.area,
'pageId': data.pageId
};
angular.module('WidgetConfig', []).value('WidgetConfig', widgetConfig);
angular.bootstrap(document.getElementById('pageWidget'), ['Widget', 'WidgetConfig']);
Widget module includes the WidgetConfig configuration but also has a spot for it own in CONFIG:
(function (window, angular) {
'use strict';
window.app = angular.module('Widget', ['ngFileUpload', 'WidgetConfig'])
.constant('CONFIG', {
BASE_URL: 'http://osage.brandportal.com/'
});
})(window, angular);
WidgetController can access CONFIG and WidgetConfig.
(function (app) {
'use strict';
app.controller('WidgetLogoController', ['CONFIG', 'WidgetConfig',
function(CONFIG, WidgetConfig){
console.log('---WidgetLogoController');
console.log('CONFIG', CONFIG);
console.log('WidgetConfig', WidgetConfig);
}]);
}(app));
What about:
Load config and than load angular:
angular.element(document).ready(() => {
$.get('config', // url to my configuration
{},
function (data) {
window.config = data;
angular.bootstrap(document, ['myApp']);
}
);
});
Access the config:
angular.module('myApp').run(myAppRun);
function myAppRun($window) {
$window.config; // here I have config
}