Require controller, factory, service and directive files with Browserify - javascript

In my gulp-browserify setup I'm writing a new AngularJS application. Because I know it's gonna be a very big application I want my base structure as clean and clear as possible.
In my main entry javascript file for browserify I setup my main angular module and define the routes with ui-router in my app.config event.
Now I don't want a single require() statement for every single controller/factory/directive in my application.
I was hoping I could require all the files in a directory like this:
require('./dashboard/*.js');
require('./users/*.js');
require('./settings/*.js')
But apperantly you can't.
What would be the cleanest way to require all my app files in my app's main entry point?

After some more research I found a module which did exactly what I wanted: Bulkify
1) Install Bulkify with NPM:
npm install bulkify
2) Require Bulkify in your Browserify task:
var bulkify = require('bulkify');
3) Just before your browserify.bundle() add the bulkify transform:
var bundle = function() {
return b.transform(bulkify)
.bundle()
};
4) In your javascript files use the bulk function:
require('bulk-require')(__dirname, ['./*/*.js']);
This will require every javascript file in any of the curren file's subfolders.

I would suggest to look at approach described in article
Main idea of it put to each folder like controller or service their own index.js.
In this case in your app.js should be some kind of require:
var angular = require('angular');
var app = angular.module('todoApp', []);
// one require statement per sub directory instead of one per file
require('./service');
require('./controller');
and index.js in controller folder looks like
var app = require('angular').module('todoApp');
app.controller('EditTodoCtrl', require('./edit_todo'));
app.controller('FooterCtrl', require('./footer'));
app.controller('TodoCtrl', require('./todo'));
app.controller('TodoListCtrl', require('./todo_list'));
app.controller('ImprintCtrl', require('./imprint'));
and example of controller declaration - todo.js
module.exports = function($scope, TodoService) {
// ...
};
For more details please look original article.

Related

Application modularity with Vue.js and local NPM packages

I'm trying to build a modular application in Vue via the vue-cli-service. The main app and the modules are separated projects living in different folders, the structure is something like this:
-- app/package.json
/src/**
-- module1/package.json
/src**
-- module2/package.json
/src**
The idea is to have the Vue app completely agnostic about the application modules that can be there at runtime, the modules themself are compiled with vue-cli-service build --target lib in a local moduleX/dist folder, pointed with the package.json "main" and "files" nodes.
My first idea (now just for development speed purposes) was to add the modules as local NPM packages to the app, building them with a watcher and serving the app with a watcher itself, so that any change to the depending modules would (I think) be distributed automatically to the main app.
So the package.json of the app contains dependencies like:
...
"module1": "file:../module1",
"module2": "file:../module2",
...
This dependencies are mean to be removed at any time, or in general be composed as we need, the app sould just be recompiled and everything should work.
I'm trying to understand now how to dynamically load and activate the modules in the application, as I cannot use the dynamic import like this:
import(/* webpackMode: "eager" */ `module1`).then(src => {
src.default.boot();
resolve();
});
Because basically I don't know the 'module1', 'module2', etc...
In an OOP world I would just use dependency injection retrieving classes implementing a specific interface, but in JS/TS I'm not sure it is viable.
There's a way to accomplish this?
Juggling with package.json doesn't sound like a good idea to me - doesn't scale. What I would do:
Keep all available "modules" in package.json
Create separate js file (or own prop inside package.json) with all available configurations (for different clients for example)
module.exports = {
'default': ['module1', 'module2', 'module3'],
'clientA': ['module1', 'module2', 'module4'],
'clientB': ['module2', 'module3', 'module4']
}
tap into VueCLI build process - best example I found is here and create js file which will run before each build (or "serve") and using simple template (for example lodash) generate new js file which will boot configured modules based on the value of some ENV variable. See following (pseudo)code (remember this runs inside node during build):
const fs = require('fs')
const _ = require('lodash')
const modulesConfig = require(`your module config js`)
const configurationName = process.env.MY_APP_CONFIGURATION ?? 'default'
const modules = modulesConfig[configurationName]
const template = fs.loadFileSync('name of template file')
const templateCompiled = _.template(template)
const generatedJS = templateCompiled({ `modules`: modules })
fs.writeFileSync('bootModules.js', generatedJS)
Write your template for bootModules.js. Simplest would be:
<% _.forEach(modules , function(module) { %>import '<%= module %>' as <%= module %><% }); %>;
import bootModules.js into your app
Use MY_APP_CONFIGURATION ENV variable to switch desired module configuration - works not just during development but you can also setup different CI processes targeting same repo with just different MY_APP_CONFIGURATION values
This way you have all configurations at one place, you don't need to change package.json before every build, you have simple mechanism to switch between different module configurations and every build (bundle) contains only the modules needed....
In an OOP world I would just use dependency injection retrieving classes implementing a specific interface, but in JS/TS I'm not sure it is viable.
Why not?
More than this, with JS/TS you are not restricted to use classes implementing a specific interface: you just need to define the interface (i.e. the module.exports) of your modules and respecting it in the libraries entries (vue build lib).
EDIT: reading comments probably I understood the request.
Each module should respect following interface (in the file which is the entry of the vue library)
export function isMyAppModule() {
return true;
}
export function myAppInit() {
return { /* what you need to export */ };
}
Than in your app:
require("./package.json").dependencies.forEach(name => {
const module = require(name);
if(! module.isMyAppModule || module.isMyAppModule() !== true) return;
const { /* the refs you need */ } = module.myAppInit();
// use your refs as you need
});

When I define the Angular controller in a separate file, it loads controller file before the Module file

I have done following things:
Created a module with the correct convention: var myApp = angular.module('myApp', []); As you can see I was mindful to put the [] as second param
I created some controllers inside the same file and it works absolutely fine with following convention: var myApp = angular.module('myApp').Controller(...)
Now, while cleaning up my code I decided to move these controllers to separate file and my module started failing with following error:
aught Error: [$injector:nomod] Module 'maintenance.portability.module'
is not available! You either misspelled the module name or forgot to
load it. If registering a module ensure that you specify the
dependencies as the second argument.(…)
It sounds like it is loading the separate controller file before the module file. How do I make sure that Module file is loaded before my browser tries to load the separate Controller file?
There might be a more correct way to do this with modules but I just use gulp to concatenate everything into the same file. Here is my gulpfile:
var gulp = require('gulp');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var babel = require('gulp-babel');
gulp.task('default', ['scripts', 'scripts:watch']);
gulp.task('scripts', function() {
return gulp.src('path/to/javascript/files/**/*.js')
.pipe(babel({
presets: ['es2015']
}))
.pipe(concat('app.min.js'))
.pipe(uglify({
mangle: false
}))
.pipe(gulp.dest('./public/js'));
});
gulp.task('scripts:watch', function () {
gulp.watch('path/to/javascript/files/**/*.js', ['scripts']);
});
This takes all of your JavaScript files, transpiles them to es5, concatenates them all together, minimizes them, then puts them in your public JavaScript folder for deployment. The watch task does this every time you save one of the javascript as long as you have gulp running. Make sure you have mangle set to false, angular is particular with variable names in my experience.

Webpack creating duplicate entries for dependencies

I am trying to switch from using browserify to webpack. One thing browserify handled nicely was dependency management inside dependencies. Let me give an example:
Main app project:
var util1 = require('shared-components/util1');
var util2 = require('shared-components/util2');
Inside shared-components/util1.js
var util2 = require('../util2');
Browserify would realize that the reference to util2 in both scenarios was the same but it appears that Webpack does not which creates duplicate entries for util2.
Is there a configuration setting or plugin I can use to resolve this?
Try new webpack.optimize.DedupePlugin(). See the docs for more info.

Using Gulp to create angular $templateCache per module/directory

So, I'm moving from grunt to gulp (or trying to anyway), and I'm having trouble getting gulp to do what I'm doing in grunt. Specifically the $templateCache stuff.
My angular app is broken up into several components/modules. Each module contains everything it needs to run (controllers, directives, partials, scss, etc.).
Using Grunt, I've been able to boil each module down into 5 files:
module.min.css // all module scss files compiled and concatenated
module.min.js // all module controllers, directives, services, etc. concatenated
module.tpls.min.js // all partials in $templateCache for this module
module.mocks.min.js // all unit test mock objects for this module
module.specs.min.js // all unit test specs for this module
This has worked really well for 2 years now and been a cornerstone of my modular architecture. My only reasons to try out gulp was 1) Curiosity, 2) My grunt file is getting kinda hairy as we add in deployment and environment specific stuff and so far gulp has really slimmed that down.
For the most part, I've figured out how to do all my grunt tasks in gulp, but I'm having trouble figuring out how to generate a template cache file for each module. All the gulp-ng|angular-templates|templatecache plugins take all my partials and create one file. I'd like to take all my files under module/partials/*.html and create a single module.tpls.min.js; and do that for each module.
This was actually a problem with grunt too, but I figured it out with grunt.file.expand().forEach() like this:
grunt.registerTask('prepModules', '...', function(){
// loop through our modules directory and create subtasks
// for each module, modifying tasks that affect modules.
grunt.file.expand("src/js/modules/*").forEach(function (dir) {
// get the module name by looking at the directory we're in
var mName = dir.substr(dir.lastIndexOf('/') + 1);
// add ngtemplate subtasks for each module, turning
// all module partials into $templateCache objects
ngtemplates[mName] = {
module: mName,
src: dir + "/partials/**/*.html",
dest: 'dev/modules/' + mName + '/' + mName + '.tpls.min.js'
};
grunt.config.set('ngtemplates', ngtemplates);
});
});
My current gulp for this same task:
var compileTemplates = gulp.src('./src/js/modules/**/partials/*.html', {base:'.'})
.pipe(ngTemplates())
.pipe(gulp.dest('.'));
I've only really looked at the options, but none of them seemed to do what I wanted. They were all around changing the file name, or the final destination of the file, or a module name, or whatever else; nothing that said anything about doing it for only the directory it happens to be in.
I had thought about using gulp-rename because it worked well for me when doing the CSS compilation:
var compileScss = gulp.src('./src/js/modules/**/scss/*.scss', {base:'.'})
.pipe(sass({includePaths: ['./src/scss']}))
.pipe(rename(function(path){
path.dirname = path.dirname.replace(/scss/,'css');
}))
.pipe(gulp.dest('.'));
However, when I pipe rename() after doing ngTemplates() it only has the path of the final output file (one log entry). When you console.log() path after sass(), it has all the paths of all the files that it found (lots of log entries).
Any ideas? Thanks!
This SO post has the correct answer, but the wasn't coming up in my searches for this specific usage. I was going to vote to close my question, but since someone else might search using my own specific terms (since I did), it seems more appropriate to leave it alone and just redirect to the original question as well as show how I solved my own particular problem.
var fs = require('fs');
var ngTemplates = require('gulp-ng-templates');
var rename = require('gulp-rename');
var modulesDir = './src/js/modules/';
var getModules = function(dir){
return fs.readdirSync(dir)
.filter(function(file){
return fs.statSync(path.join(dir, file)).isDirectory();
});
};
gulp.task('default', function(){
var modules = getModules(modulesDir);
var moduleTasks = modules.map(function(folder){
// get all partials for this module
// parse into $templateCache file
// rename to be /dev/modules/_____/______.tpls.min.js
return gulp.src(modulesDir + folder + '/partials/*.html', {basedir:'.'})
.pipe(ngTemplates({module:folder}))
.pipe(rename(function(path){
path.dirname = './dev/apps/' + folder + '/';
path.basename = folder + '.tpls.min';
}))
.pipe(gulp.dest('.'));
});
});
It's essentially like the tasks per folder recipe but with a change to use gulp-ng-templates. I'll probably be using this same pattern for my SCSS and JS now that I'm more aware of it.
Seems like the gulp equivalent of grunt.file.expand().forEach().
Whenever I deal with scss/sass for gulp tasks, I will only put one scss file as the source parameter. This parameter file is composed of a list of imports. This way you don't need to rely on gulp to concat the scss file contents for you.
//in gulpfile
gulp.src('./src/js/modules/**/scss/main.scss', {base:'.'})
//in main.scss
#import 'a', 'b', 'c';
a, b, and c would represent your other scss files.

Using and injecting Angular $templateCache with RequireJS

I do a grunt serve:dist and within I build with grunt-contrib-requirejs an all.js file based on my RequireJS main.js file which have require.config and a require section.
I think all.js should be in my distribution that file which I have to include on startup in my index.html, because everything is in there. Is this right?
<script src="require.js" data-main="all.js"></script>
I also create based on all my template HTML files a template JavaScript file with ngTemplates and bootstrap it so the template file named templates.js looks like this:
define([
'angular'
], function(angular) {
angular.module('MyApp.templates', []).run(['$templateCache', function($templateCache) {
'use strict';
$templateCache.put('templates/MyTest.html',
"<h1>Title</h1>\r" +
// ...
// other put on $templateCache
}]);
});
So I have a $templateCache which I want to use. But I do not how this can be done. I think I have to load the templates.js because it is not included in all.js and therefore I should inject it in some way.
I had similar issue and I found a way to solve it.
Basically, I have templates.js to return just a function to inject to run block.
For example: templates.js
define([], function()){
return ['$templateCache", function($templateCache){
'use strict';
$templateCache.put('templates/MyTest.html',
"<h1>Title</h1>\r" +
// ...
// other put on $templateCache
}];
}
and then, in your app.js file, you can inject this function into run block
define(['templates'], function(templates){
angular.module('app')
.run(templates);
})
I hope this helped, please let me know if you are not clear with something.

Categories

Resources