Configure durandal for multiple SPA - javascript

I have a site, which technically consists of a number of separate SPAs.
App/Admin
App/Signup
App/MainApp
How should I organize the App folder and the gulp build to make this into 3-4 separate files / modules that are loaded when required.

Since I had to spend a whole day getting this to work, I will answer my own question.
First, prepare a loader site. This would be the main site that is actually loaded that is responsible from loading all the other sites.
App/main.js - Contains the requirejs setup
In this we would run app.setRoot('shell', 'entrance');
App/shell.js - contains the router config
return router.map([
{ route: 'Admin/*all', moduleId: "Admin/index", nav: true },
{ route: 'Signup/*all', moduleId: "Signup/index", nav: true },
{ route: '*all', moduleId: "MainApp/index", nav: true }
])
.buildNavigationModel()
.activate();
App/shell.html - contains the router usage and common header/footer/loading shield
<div style="height: 100%" data-bind="css: { loading: isLoading }">
<!-- ko compose: 'loading.html'--><!-- /ko -->
<div class="header">...</div>
<!-- ko router: { transition:'entrance' } --><!-- /ko -->
</div>
App/*.js - Files shared among all the SPAs
App/MainApp/index.js - Contains the sub router for that SPA
define(["require", "exports", "plugins/router"], function (require, exports, router) {
"use strict";
var childRouter = router.createChildRouter()
.makeRelative({
moduleId: 'MainApp',
fromParent: false //<!----- FALSE for MainApp, TRUE for other SPA
})
.map([
{ route: '', moduleId: "viewmodels/test", nav: true },
])
.mapUnknownRoutes("viewmodels/err", 'err')
.buildNavigationModel();
return (function () {
function class_1() {
this.router = childRouter;
}
return class_1;
}());
});
App/MainApp/index.html - Uses the router, and performs any rendering of submenues or whatever. Here the bare minimum:
<div data-bind="router: { }"></div>
App/Admin/index.js
Is identical to the MainApp.js, except for the fromParent set to true and moduleId set to the SPAs name.
This should work fine in debug mode now.
The gulp setup becomes:
gulpfile.js
var durandal = require('gulp-durandal');
gulp.task('build', [], function () {
return gulp.start(['build-admin', 'build-signup', 'build-mainapp', 'build-base']);
});
function buildModule(name) {
var path = name + '/';
return durandal({
baseDir: 'App',
output: name + '.js',
minify: true,
almond: false,
verbose: false,
moduleFilter: function (moduleName) {
return moduleName.indexOf(path) > -1;
},
rjsConfigAdapter: function (config) {
config.stubModules = config.exclude;
config.exclude = ["main"];
config.stubModules.push('text');
return config;
}
})
.pipe(gulp.dest('dist'));
}
gulp.task('build-admin', [], function () {
return buildModule('Admin');
});
gulp.task('build-signup', [], function () {
return buildModule('Signup');
});
gulp.task('build-mainapp', [], function () {
return buildModule('MainApp');
});
gulp.task('build-base', [], function () {
return durandal({
baseDir: 'App',
output: 'main.js',
minify: true,
verbose: false,
moduleFilter: function (moduleName) {
return moduleName.indexOf('Admin/') === -1 && moduleName.indexOf('MainApp/') === -1 && moduleName.indexOf('Signup/') === -1;
},
rjsConfigAdapter: function (config) {
config.stubModules = config.exclude;
config.exclude = [];
return config;
}
})
.pipe(gulp.dest('dist'));
});
This will now result in
dist/main.js
dist/Admin.js
dist/MainApp.js
dist/Signup.js
In your default.html (or whatever you call it), you need to tell it how to load these files:
<script>window.require = {
bundles: { 'MainApp': ['MainApp/index'],'Signup': ['Signup/index'], 'Admin': ['Admin/index'] }
};</script>
<script type="text/javascript" data-main="main" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.2.0/require.min.js"></script>
The reason for this being in the html files rather than the main.js, is that the bundles are only used for the compiled version.

Related

PWA - Frontend doesn't update even after a hard refresh

Below is the configuration of our nuxt and nuxt-pwa configuration.
Nuxt pwa is recognising a new version available and we prompt user to do a hard refresh/reload.
And on reload - new UI also starts to work.
However, if we open the site in a new tab. A spinner gets shown & the latest frontend fails to load. And again, a hard refresh is required.
our frontend redirects to /dashboard on visiting localhost:8080 by default and this is getting loaded from serviceworker with cached data.
Please help us resolve this since this has been a critical issue for us.
Spinner seen on new tab opening :
export default {
ssr: false,
target: 'static',
head: {
titleTemplate: '',
title: 'NocoDB',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: './favicon-32.png' }
]
},
plugins: [
// plugins
],
buildModules: [
'#nuxtjs/vuetify',
'#nuxtjs/pwa'
],
modules: [
// Doc: https://axios.nuxtjs.org/usage
'#nuxtjs/axios',
'vue-github-buttons/nuxt',
'#nuxtjs/toast'
],
axios: {
baseURL: process.env.NC_BACKEND_URL || (process.env.NODE_ENV === 'production' ? '..' : 'http://localhost:8080')
},
router: {
mode: 'hash',
base: process.env.NODE_ENV === 'production' ? './' : '',
middleware: ['auth']
},
vuetify: {
defaultAssets: {
icons: false
},
optionsPath: '#/config/vuetify.options.js',
treeShake: true,
customVariables: ['./config/variables.scss']
},
build: {
parallel: true,
plugins: [
new MonacoEditorWebpackPlugin({
languages: ['sql', 'json', 'javascript'],
features: ['!gotoSymbol']
})
],
extend(config, { isDev, isClient }) {
if (isDev) {
config.devtool = isClient ? 'source-map' : 'inline-source-map'
}
config.externals = config.externals || {}
config.externals['#microsoft/typescript-etw'] = 'FakeModule'
return config
}
},
pwa: {
workbox: {
assetsURLPattern: /\/_nuxt\//,
config: { debug: true }
},
icon: {
publicPath: './'
},
manifest: {
name: 'NocoDB',
start_url: '../?standalone=true',
theme_color: '#ffffff'
}
}
}
Lighthouse report :
Github issue reference : https://github.com/nuxt-community/pwa-module/issues/501
You need to set service worker which clears cache based on versions.
Setup service-worker.js as below. and update LATEST_VERSION on changes you deploy.
// service-worker.js
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
workbox.core.setCacheNameDetails({ prefix: 'pwa' })
//Change this value every time before you build to update cache
const LATEST_VERSION = 'v1.0.1'
self.addEventListener('activate', (event) => {
console.log(`%c ${LATEST_VERSION} `, 'background: #ddd; color: #0000ff')
if (caches) {
caches.keys().then((arr) => {
arr.forEach((key) => {
if (key.indexOf('pwa-precache') < -1) {
caches.delete(key).then(() => console.log(`%c Cleared ${key}`, 'background: #333; color: #ff0000'))
} else {
caches.open(key).then((cache) => {
cache.match('version').then((res) => {
if (!res) {
cache.put('version', new Response(LATEST_VERSION, { status: 200, statusText: LATEST_VERSION }))
} else if (res.statusText !== LATEST_VERSION) {
caches.delete(key).then(() => console.log(`%c Cleared Cache ${LATEST_VERSION}`, 'background: #333; color: #ff0000'))
} else console.log(`%c Great you have the latest version ${LATEST_VERSION}`, 'background: #333; color: #00ff00')
})
})
}
})
})
}
})
workbox.core.skipWaiting();
workbox.core.clientsClaim();
self.__precacheManifest = [].concat(self.__precacheManifest || [])
// workbox.precaching.suppressWarnings()
workbox.precaching.precacheAndRoute(self.__precacheManifest, {})
Don't know for nuxt, but for angular we need to bump the version element of ngsw-config.json on each deploy (1, 2, 3, ...) and also call SwUpdate.checkForUpdate() on startup. Only then the new version of the app will be retrieved from the server.

Add RTL condition in head with Nuxt Js i18n

I would like to add a condition as follows
if(app.i18n.locale == 'ar')
I use Arabic with English in this project. If the current language is Arabic, bootstrap-rtl.css is added in the head, and if the current language en is called bootstrap.css, I have tried more than one method and did not succeed.
Add a file in plugins folder for example direction-control.js
export default ({ app }, inject) => {
const dir = () =>
app.i18n.locales.find((x) => x.code === app.i18n.locale)?.dir;
inject('dir', dir);
};
While your nuxt.config.js will need similar code to this
plugins: [
'~/plugins/direction-control', // your plugins file name
// other plugins
],
modules: [
[
'nuxt-i18n',
{
locales: [
{
code: 'en',
file: 'en.json',
dir: 'ltr',
name: 'English',
},
{
code: 'ar',
file: 'ar.json',
dir: 'rtl', // that will be passed to your app
name: 'عربي',
},
],
langDir: 'translations/',
defaultLocale: 'ar',
},
],
],
Now it's time to get dir
export default {
mounted() {
console.log(this.$dir()); // logs your direction 'ltr' or 'rtl'
console.log(this.$i18n.locale);
if (this.$i18n.locale == "ar") {
// make some action
}
},
}
or inside template like this
<template>
<div :dir="$dir()">
<Nuxt />
</div>
</template>

vue.js Uncaught SyntaxError: Identifier has already been declared

I have two demos for same library under repo, with demo.
The main difference is that, one is for browser use and the other is for node use.
However browser one will have error
index.js:1 Uncaught SyntaxError: Identifier 'HeaderComp' has already been declared
What is the main cause?
Update:
Please keep in mind I do not declare a variable twice! I also tried to add console log at the top to ensure the script is executed once!
var HeaderComp = {
name: 'HeaderComp',
template: `
Back
{{ r.title }}
`,
mixins: [VueTopDown.VueTopDownItem],
computed: {
routes () {
return [
{ href: '/page', title: 'Page' },
{ href: '/hello-vue', title: 'HelloVue' }
]
}
}
}
var FooterComp = {
name: 'FooterComp',
template: `{{ vueFooter }}`,
mixins: [VueTopDown.VueTopDownItem],
data () {
return {
vueFooter: 'This is Vue Footer'
}
}
}
var ContentComp = {
name: 'ContentComp',
template: ``,
mixins: [VueTopDown.VueTopDownItem],
computed: {
innerHTML () {
var root = document.createElement('div')
root.innerHTML = this[VTDConstants.OUTER_HTML]
return root.querySelector('*').innerHTML
}
}
}
var HelloVue = {
template: `Hello Vue`,
props: ['clazz'],
inheritAttrs: false
}
var Page = {
template: ``,
props: ['clazz', 'innerHTML'],
inheritAttrs: false
}
var router = new VueRouter([
{ path: '/hello-vue', component: HelloVue },
{ path: '/page', component: Page },
{ path: '*', redirect: '/page' }
])
var inst = new Vue({
router,
mixins: [VueTopDown.VueTopDown],
components: {
HeaderComp: HeaderComp,
FooterComp,
ContentComp
},
data () {
return {
[VueTopDown.VTDConstants]: {
'header': HeaderComp,
'footer': FooterComp,
'.content': ContentComp
}
}
}
})
inst.$mount('#app')
Also keep in mind that similar code works fine in node environment but fails in browser!
Doesn't occur if commenting out inst.$mount('#app')
Expect
The expected behavior of browser should be same as that of node.

My routing React.js is not run

i'm followed by dud from tutorial, and now have some issue,
Routing is not work, and when i run gulp, i have and error from 'gulp-eslint' in terminal:
/home/im/Project/react/psadmin/src/main.js
7:13 error Unexpected token =
/home/im/Project/react/psadmin/src/components/homePage.js
6:13 error Unexpected token =
/home/im/Project/react/psadmin/src/components/about/aboutPage.js
6:13 error Unexpected token =
this is my files:
"use strict"
var gulp = require('gulp')
var connect = require('gulp-connect') // Runs a local dev server
var open = require('gulp-open') // Open a URL in a web browser
var browserify = require('browserify') // Bundle JS
var reactify = require('reactify') // Transform React JSX to JS
var source = require('vinyl-source-stream') // Use convertional text streams with Gulp
var concat = require('gulp-concat') // Concatenates files
var lint = require('gulp-eslin') // Lint JS files, including JSX
var config = {
port: 9005,
devBaseUrl: 'http://localhost',
paths: {
html: './src/*.html',
js: './src/**/*.js',
css: [
'node_modules/bootstrap/dist/css/bootstrap.min.css',
'node_modules/bootstrap/dist/css/bootstrap-theme.min.css'
],
dist: './dist',
mainJs: './src/main.js'
}
}
// Start a local development server
gulp.task('connect', () => {
connect.server({
root: ['dist'],
port: config.port,
base: config.devBaseUrl,
livereload: true
})
})
gulp.task('open', ['connect'], () => {
gulp.src('dist/index.html')
.pipe(open({ uri: config.devBaseUrl + ':' + config.port + '/'}))
})
gulp.task('html', () => {
gulp.src(config.paths.html)
.pipe(gulp.dest(config.paths.dist))
.pipe(connect.reload())
})
gulp.task('js', () => {
browserify(config.paths.mainJs)
.transform(reactify)
.bundle()
.on('error', console.error.bind(console))
.pipe(source('bundle.js'))
.pipe(gulp.dest(config.paths.dist + '/scripts'))
.pipe(connect.reload())
})
gulp.task('css', () => {
gulp.src(config.paths.css)
.pipe(concat('bundle.css'))
.pipe(gulp.dest(config.paths.dist + '/css'))
})
gulp.task('lint', () => {
return gulp.src(config.paths.js)
.pipe(lint({config: 'eslint.config.json'}))
.pipe(lint.format())
})
gulp.task('watch', () => {
gulp.watch(config.paths.html, ['html'])
gulp.watch(config.paths.js, ['js', 'lint'])
gulp.watch(config.paths.css, ['css'])
})
gulp.task('default', ['html', 'js', 'lint', 'css', 'open', 'watch'])
main.js
$ = jQuery = require('jquery')
var React = require('react')
var Home = require('./components/homePage')
var About = require('./components/about/aboutPage')
var App = React.createClass({
render: () => {
var Child
switch(this.props.route) {
case 'about':
Child = About
break
default:
Child = Home
}
return (
<div>
<Child />
</div>
)
}
})
function render() {
var route = window.location.hash.substr(1)
React.render(<App route={route} />, document.getElementById('app'))
}
window.addEventListener('hashchange', render)
render()
homePage.js
"use strict"
var React = require('react')
var Home = React.createClass({
render: () => {
return (
<div className="jumbotron">
<h1>Pluralsight Administration</h1>
<p>React, React Router, and Flux for ultra-responsive web apps.</p>
</div>
)
}
})
module.exports = Home
aboutPage.js
"use strict"
var React = require('react')
var About = React.createClass({
render: () => {
return (
<div>
<h1>About</h1>
<p>
This application uses the following technologies:
<ul>
<li>React</li>
<li>React Router</li>
<li>Flux</li>
<li>Node</li>
<li>Gulp</li>
<li>Browserify</li>
<li>Bootstrap</li>
</lu>
</div>
)
}
})
module.exports = About
eslint.conf.json
{
"root": true,
"ecmaFeatures": {
"jsx": true
},
"env": {
"browser": true,
"node": true,
"jquery": true
},
"rules": {
"quotes": 0,
"no-trailing-spaces": 0,
"eol-last": 0,
"no-unused-vars": 0,
"no-underscore-dangle": 0,
"no-alert": 0,
"no-lone-blocks": 0
},
"globals": {
"jQuery": true,
"$": true
}
}
Link to full application where i'm stoped is here on git
here
Link to current tutorial here
You are off a little on your implementation
With React Create class you need to do the following
var About = React.createClass({
render: function() {
return (
<div>
<h1>About</h1>
<p>
This application uses the following technologies:
<ul>
<li>React</li>
<li>React Router</li>
<li>Flux</li>
<li>Node</li>
<li>Gulp</li>
<li>Browserify</li>
<li>Bootstrap</li>
</lu>
</div>
)
}
})
Or you can use the ES6 syntax
class About extends React.Component {
render() {
return (
<div>
<h1>About</h1>
<p>
This application uses the following technologies:
<ul>
<li>React</li>
<li>React Router</li>
<li>Flux</li>
<li>Node</li>
<li>Gulp</li>
<li>Browserify</li>
<li>Bootstrap</li>
</lu>
</div>
)
}
}
Or you can use the ES6 arrow syntax
const About = () => (
<div>
<h1>About</h1>
<p>
This application uses the following technologies:
<ul>
<li>React</li>
<li>React Router</li>
<li>Flux</li>
<li>Node</li>
<li>Gulp</li>
<li>Browserify</li>
<li>Bootstrap</li>
</lu>
</div>
)
Your syntax is wrong.
replace render: () => {
by
render() { return (...) }
or if your project does not support ES6:
render: function(){return ...}
and tell me if that fixes the problem

Angularjs app crashes with "Error: too much recursion" when defining a route

I'm following this tutorial on using angularjs alongside laravel: http://angular-tips.com/blog/2014/11/working-with-laravel-plus-angular-part-2/. However, if I define a route in config.routes.js which points to a controller, any controller, my app crashes. In the console the error "Error: too much recursion" comes up, along with a useless stack trace.
All my files are exactly the same as in the tutorial, I only changed the name of the route and the controller, but that shouldn't make a difference.
I googled around a bit and it seems this error might be caused by using a wrong version of jquery. I use angularjs 1.3.0 and I have no idea which jquery version my app is using, but I used npm install angular, so it'd be weird if that installed a wrong version, right?
I'm completely lost on why this happens and also very frustrated, so any help would be greatly appreciated.
Thanks.
EDIT: Added code:
app/js/config.routes.js
angular.module('app').config(function($routeProvider, $locationProvider)
{
$locationProvider.html5Mode(true).hashPrefix('!');
$routeProvider.when('/transactions',
{
templateUrl: 'features/transactions/transactions.tpl.html',
controller: 'Transactions'
});
});
app/js/transactions/transactions.js:
angular.module('app').controller('Transactions', function($scope, $http)
{
$http.get('/api/transactions').then(function(result)
{
$scope.shows = result.data;
});
});
transactions.tpl.html is empty.
app.js:
angular.module('app', ['ngRoute']);
EDIT 2: added gulp.js
The only thing I changed here is, is that I added the 'webserver' task.
var gulp = require('gulp');
var fs = require('fs');
var plugins = require('gulp-load-plugins')();
var es = require('event-stream');
var del = require('del');
var publicFolderPath = '../public';
var paths = {
appJavascript: ['app/js/app.js', 'app/js/**/*.js'],
appTemplates: 'app/js/**/*.tpl.html',
appMainSass: 'app/scss/main.scss',
appStyles: 'app/scss/**/*.scss',
appImages: 'app/images/**/*',
indexHtml: 'app/index.html',
vendorJavascript: ['vendor/js/angular.js', 'vendor/js/**/*.js'],
vendorCss: ['vendor/css/**/*.css'],
finalAppJsPath: '/js/app.js',
finalAppCssPath: '/css/app.css',
specFolder: ['spec/**/*_spec.js'],
publicFolder: publicFolderPath,
publicJavascript: publicFolderPath + '/js',
publicAppJs: publicFolderPath + '/js/app.js',
publicCss: publicFolderPath + '/css',
publicImages: publicFolderPath + '/images',
publicIndex: publicFolderPath + '/angular.html',
publicJsManifest: publicFolderPath + '/js/rev-manifest.json',
publicCssManifest: publicFolderPath + '/css/rev-manifest.json'
};
gulp.task('scripts-dev', function() {
return gulp.src(paths.vendorJavascript.concat(paths.appJavascript, paths.appTemplates))
.pipe(plugins.if(/html$/, buildTemplates()))
.pipe(plugins.sourcemaps.init())
.pipe(plugins.concat('app.js'))
.pipe(plugins.sourcemaps.write('.'))
.pipe(gulp.dest(paths.publicJavascript));
});
gulp.task('scripts-prod', function() {
return gulp.src(paths.vendorJavascript.concat(paths.appJavascript, paths.appTemplates))
.pipe(plugins.if(/html$/, buildTemplates()))
.pipe(plugins.concat('app.js'))
.pipe(plugins.ngAnnotate())
.pipe(plugins.uglify())
.pipe(plugins.rev())
.pipe(gulp.dest(paths.publicJavascript))
.pipe(plugins.rev.manifest({path: 'rev-manifest.json'}))
.pipe(gulp.dest(paths.publicJavascript));
});
gulp.task('styles-dev', function() {
return gulp.src(paths.vendorCss.concat(paths.appMainSass))
.pipe(plugins.if(/scss$/, plugins.sass()))
.pipe(plugins.concat('app.css'))
.pipe(gulp.dest(paths.publicCss));
});
gulp.task('styles-prod', function() {
return gulp.src(paths.vendorCss.concat(paths.appMainSass))
.pipe(plugins.if(/scss$/, plugins.sass()))
.pipe(plugins.concat('app.css'))
.pipe(plugins.minifyCss())
.pipe(plugins.rev())
.pipe(gulp.dest(paths.publicCss))
.pipe(plugins.rev.manifest({path: 'rev-manifest.json'}))
.pipe(gulp.dest(paths.publicCss));
});
gulp.task('images', function() {
return gulp.src(paths.appImages)
.pipe(gulp.dest(paths.publicImages));
});
gulp.task('indexHtml-dev', ['scripts-dev', 'styles-dev'], function() {
var manifest = {
js: paths.finalAppJsPath,
css: paths.finalAppCssPath
};
return gulp.src(paths.indexHtml)
.pipe(plugins.template({css: manifest['css'], js: manifest['js']}))
.pipe(plugins.rename(paths.publicIndex))
.pipe(gulp.dest(paths.publicFolder));
});
gulp.task('indexHtml-prod', ['scripts-prod', 'styles-prod'], function() {
var jsManifest = JSON.parse(fs.readFileSync(paths.publicJsManifest, 'utf8'));
var cssManifest = JSON.parse(fs.readFileSync(paths.publicCssManifest, 'utf8'));
var manifest = {
js: '/js/' + jsManifest['app.js'],
css: '/css/' + cssManifest['app.css']
};
return gulp.src(paths.indexHtml)
.pipe(plugins.template({css: manifest['css'], js: manifest['js']}))
.pipe(plugins.rename(paths.publicIndex))
.pipe(gulp.dest(paths.publicFolder));
});
gulp.task('lint', function() {
return gulp.src(paths.appJavascript.concat(paths.specFolder))
.pipe(plugins.jshint())
.pipe(plugins.jshint.reporter('jshint-stylish'));
});
gulp.task('testem', function() {
return gulp.src(['']) // We don't need files, that is managed on testem.json
.pipe(plugins.testem({
configFile: 'testem.json'
}));
});
gulp.task('clean', function(cb) {
del([paths.publicJavascript, paths.publicImages, paths.publicCss, paths.publicIndex], {force: true}, cb);
});
gulp.task('watch', ['indexHtml-dev', 'images'], function() {
gulp.watch(paths.appJavascript, ['lint', 'scripts-dev']);
gulp.watch(paths.appTemplates, ['scripts-dev']);
gulp.watch(paths.vendorJavascript, ['scripts-dev']);
gulp.watch(paths.appImages, ['images-dev']);
gulp.watch(paths.specFolder, ['lint']);
gulp.watch(paths.indexHtml, ['indexHtml-dev']);
gulp.watch(paths.appStyles, ['styles-dev']);
gulp.watch(paths.vendorCss, ['styles-dev']);
});
gulp.task('webserver', ['indexHtml-dev', 'images-dev'], function() {
plugins.connect.server({
root: paths.tmpFolder,
port: 5000,
livereload: true,
middleware: function(connect, o) {
return [ (function() {
var url = require('url');
var proxy = require('proxy-middleware');
var options = url.parse('http://localhost:8000/api');
options.route = '/api';
return proxy(options);
})(), historyApiFallback ];
}
});
});
gulp.task('default', ['watch']);
gulp.task('production', ['scripts-prod', 'styles-prod', 'images', 'indexHtml-prod']);
function buildTemplates() {
return es.pipeline(
plugins.minifyHtml({
empty: true,
spare: true,
quotes: true
}),
plugins.angularTemplatecache({
module: 'app'
})
);
}

Categories

Resources