Angular2 bundling and minification - javascript

ASP.NET (or gulp) will take care of bundling and minification. However, the problem i came across is rather different. Per Angular2's tutorials, the view HTML is embedded within the component itself. There is a way, using TypeScript, to separate that into a .ts and .html files. Here's how:
...
/// <reference path="./view-declaration.d.ts" />
...
import {html} from '/app.html!text';
...
#Component({
...
template: html
})
...
And faking .html as a module in view-declaration.d.ts file:
declare module '/app.html!text' {
var html:string;
return default html;
}
This is using SystemJS with its text plugin. This will not generate System.register for .html files which means HTMLs can't be bundled along the transpiled .js files.
So questions is, how do you separate the HTML from JavaScript, but also be able to bundle them properly?
Also to note, this approach is just the same as setting the templateUrl on your component. Both of which defeat the purpose of bundling and reduction of server hits per component. The offered solution from Angular2 is to use string and set template on a component. This is pretty far from reality of junior developers and code reviews (yea not gonna get the whole code base in order to run and see if browser complains about a non-closed tag!).

This is gulp plugin which I think can solve your issue.
look at:
https://github.com/ludohenin/gulp-inline-ng2-template
the advantage you keep having a clean html file using templateUrl during development. And this task can be part of your production\staging -minified - gulp build task.
e.g (Part of my build tasks!)
var inlineNg2Template = require('gulp-inline-ng2-template');
gulp.task('build-prod', ['build.lib'], function () {
var tsProject = typescript.createProject('./tsconfig.json', { typescript: require('typescript') });
var tsSrcInlined = gulp.src([webroot + '**/*.ts'], { base: webroot })
.pipe(inlineNg2Template({ base: webroot }));
return eventStream.merge(tsSrcInlined, gulp.src('Typings/**/*.ts'))
.pipe(sourcemaps.init())
.pipe(typescript(tsProject))
.pipe(sourcemaps.write())
.pipe(gulp.dest(webroot));
});

As it turns out, you'll need to use templateUrl for development, but replace that with template during the bundling and minification. This is the gulp task for the job:
var gulp = require('gulp'); //install 'gulp' via npm
var uglify = require('gulp-uglify'); //install 'gulp-uglify' via npm
var concat = require('gulp-concat'); //install 'gulp-concat' via npm
var replace = require('gulp-replace'); //install 'gulp-replace' via npm
var fs = require("fs"); //already available in Visual Studio and of course NodeJS
gulp.task('bundle:js', function () {
return gulp
.src([
"file1.js",
"file2.js"
])
.pipe(replace(/templateUrl.*\'/g, function (matched) {
var fileName = matched.match(/\/.*html/g).toString();
var fileContent = fs.readFileSync(fileName, "utf8");
return 'template:\'' + fileContent.replace(/\r\n/g, '') + '\'';
}))
.pipe(concat('file.min.js'))
.pipe(gulp.dest('my bundle folder'))
.pipe(uglify())
.pipe(gulp.dest('my bundle folder'));
});
This is to match .html files and the template URL, but it can be tweaked if needed.

Your template file app.html.ts can export the html template as a string.
export const htmlTemplate = `
<p>My app</p>
`;
Then your component (app.component.ts) can import the template inline.
import { Component } from '#angular/core';
import { htmlTemplate } from './app.html';
#Component({
selector: 'my-app',
template: htmlTemplate,
})
...
This approach:
allows for in-lining of templates at compilation, avoiding the additional network request that using "templateUrl" adds and allowing templates to be minified and bundled with other js
allows templates to live in external files for code clarity and scaling
can be easily migrated to a plain HTML file once Typescript "import file as string" functionality is in place
allows Webstorm html syntax highlighting to still work correctly
See blog from Angular University

Related

How can I insert content into a file during a gulp build?

I managed to accomplish my task using a gulp plugin called gulp-insert like this:
gulp.task('compile-js', function () {
// Minify and bundle client scripts.
var scripts = gulp.src([
srcDir + '/routes/**/*.js',
srcDir + '/shared/js/**/*.js'
])
// Sort angular files so the module definition appears
// first in the bundle.
.pipe(gulpAngularFilesort())
// Add angular dependency injection annotations before
// minifying the bundle.
.pipe(gulpNgAnnotate())
// Begin building source maps for easy debugging of the
// bundled code.
.pipe(gulpSourcemaps.init())
.pipe(gulpConcat('bundle.js'))
// Buffer the bundle.js file and replace the appConfig
// placeholder string with a stringified config object.
.pipe(gulpInsert.transform(function (contents) {
return contents.replace("'{{{appConfigObj}}}'", JSON.stringify(config));
}))
.pipe(gulpUglify())
// Finish off sourcemap tracking and write the map to the
// bottom of the bundle file.
.pipe(gulpSourcemaps.write())
.pipe(gulp.dest(buildDir + '/shared/js'));
return scripts.pipe(gulpLivereload());
});
What I'm doing is reading our app's configuration file which is managed by the config module on npm. Getting our config file from server-side code is a snap using var config = require('config');, but we're a single-page app and frequently need access to the configuration settings on the client-side. To do that I stuff the config object into an Angular service.
Here's the Angular service before gulp build.
angular.module('app')
.factory('appConfig', function () {
return '{{{appConfigObj}}}';
});
The placeholder is in a string so that it's valid JavaScript for some of the other gulp plugins that process the file first. The gulpInsert utility lets me insert the config like this.
.pipe(gulpInsert.transform(function (contents) {
return contents.replace("'{{{appConfigObj}}}'", JSON.stringify(config));
}))
This works but feels a little hacky. Not to mention that it has to buffer the whole bundled file just so I can perform the operation. Is there a more elegant way to accomplish the same thing? Preferably one that allows the stream to keep flowing smoothly without buffering the whole bundle at the end? Thanks!
Have you checked gulp-replace-task?
Something like
[...]
.pipe(gulpSourcemaps.init())
.pipe(replace({
patterns: [{
match: '{{{appConfigObj}}}',
replacement: config
}],
usePrefix: false
})
.pipe(gulpUglify())
[...]
Admittedly, this feels a bit hacky, too, but maybe slightly better... I'm using envify and gulp-env in a React project. You could do something like this.
gulpfile.js:
var config = require('config');
var envify = require('envify');
gulp.task('env', function () {
env({
vars: {
APP_CONFIG: JSON.stringify(config)
}
});
});
gulp.task('compile-js', ['env'], function () {
// ... replace `gulp-insert` with `envify`
});
factory:
angular.module('app')
.factory('appConfig', function () {
return process.env.APP_CONFIG;
});

Browserify dynamic separate bundles

My app loads an object of messages in a given language into the application. My structure is like so:
/lang
/en.js (100 kb file)
/ru.js (100 kb file)
/... many more
app.js (this is `MyApp` as below)
The language files are very big so I would like to create separate bundles and you then only include the files you need <script src="lang/en.js"></script>. The language can also be 'switched' within the application at any time.
How would I tell browserify to build the main app and separate bundles for all the language files, and still allow MyApp to require those language files?
function MyApp(lang) {
this.messages = {};
this.switchLang(lang);
};
MyApp.prototype.loadLang = function(lang) {
this.messages = require('./lang/' + lang + '.js');
};
MyApp.prototype.switchLang = function(lang) {
this.lang = lang;
this.loadLang(lang);
};
MyApp.prototype.sayHello = function() {
alert(this.messages.HELLO);
};
module.exports = MyApp;
You can separate all languages from your main app by using -r (require) and -x (external) in your browserify command.
Bundle languages together to one file, could look like this:
browserify -r ./lang/en.js -r ./lang/ru.js > languages.js
RECOMMENDED: You can create a separate bundle for each language file with the above command. Just use -r once.
Then include the new file (languages.js) in your html page before MyApp.js. Then you have to ignore them while building MyApp.js.
browserify --ignore-missing -x ./lang/en.js -x ./lang/ru.js -d app.js > MyApp.js
You are still allowed to require those languages.
NOTE: If you have a separate bundle for each language (see RECOMMENDED), you are only allowed to require the included ones in your main app.
There is no browserify-way to do that automatically for each file in lang/.
I recommend you to write a *.cmd (batch) file that executes the above commands for every language file in lang/. So you can still include your favored language.
EDIT: use --ignore-missing or --im when bundleing MyApp.js. So you can require all languages and when they are missing they are still undefined.

Compiling dynamically required modules with Browserify

I am using Browserify to compile a large Node.js application into a single file (using options --bare and --ignore-missing [to avoid troubles with lib-cov in Express]). I have some code to dynamically load modules based on what is available in a directory:
var fs = require('fs'),
path = require('path');
fs.readdirSync(__dirname).forEach(function (file) {
if (file !== 'index.js' && fs.statSync(path.join(__dirname, file)).isFile()) {
module.exports[file.substring(0, file.length-3)] = require(path.join(__dirname, file));
}
});
I'm getting strange errors in my application where aribtrary text files are being loaded from the directory my compiled file is loaded in. I think it's because paths are no longer set correctly, and because Browserify won't be able to require() the correct files that are dynamically loaded like this.
Short of making a static index.js file, is there a preferred method of dynamically requiring a directory of modules that is out-of-the-box compatible with Browserify?
This plugin allows to require Glob patterns: require-globify
Then, with a little hack you can add all the files on compilation and not executing them:
// Hack to compile Glob files. Don´t call this function!
function ಠ_ಠ() {
require('views/**/*.js', { glob: true })
}
And, for example, you could require and execute a specific file when you need it :D
var homePage = require('views/'+currentView)
Browserify does not support dynamic requires - see GH issue 377.
The only method for dynamically requiring a directory I am aware of: a build step to list the directory files and write the "static" index.js file.
There's also the bulkify transform, as documented here:
https://github.com/chrisdavies/tech-thoughts/blob/master/browserify-include-directory.md
Basically, you can do this in your app.js or whatever:
var bulk = require('bulk-require');
// Require all of the scripts in the controllers directory
bulk(__dirname, ['controllers/**/*.js']);
And my gulpfile has something like this in it:
gulp.task('js', function () {
return gulp.src('./src/js/init.js')
.pipe(browserify({
transform: ['bulkify']
}))
.pipe(rename('app.js'))
.pipe(uglify())
.pipe(gulp.dest('./dest/js'));
});

How to conditionally compile (using Grunt) only changed jade files with template includes

Using a version of what grunt-contrib-watch recommends for compiling only changed files in here: https://github.com/gruntjs/grunt-contrib-watch#compiling-files-as-needed
var changedFiles = Object.create(null);
var onChange = grunt.util._.debounce(function() {
grunt.config('jshint.all.src', Object.keys(changedFiles));
changedFiles = Object.create(null);
}, 200);
grunt.event.on('watch', function(action, filepath) {
changedFiles[filepath] = action;
onChange();
});
This works fine (again with a variation I wrote for it here: https://gist.github.com/pgilad/6897875)
The problem is when using include inside Jade templates, meaning you are including other Jade templates in order to build the complete html file.
Using the singular solution for compile doesn't work because if a .jade file you are working on is embeded using include current_working_jade.jade - the including file won't get recompiled.
Are there any workarounds for this besides compiling all of your jade files from scratch? This causes a problem when you have around ~60 large jade files to compile every time.
The only possible solution I can think of is either mapping jade templates dependencies either externally or with directories, but I don't know any tools/plugins which do that...
After already starting to work on a scaffold that will generate a sortof jade sourcemap I found this great project, that already solves this issue:
Jade Inheritance
Usage is as follows:
Install package using: npm install jade-inheritance --save-dev
Where you want to get a list of dependent files from a jade:
var JadeInheritance = require('jade-inheritance');
var inheritance = new JadeInheritance(file, basedirname, {basedir:basedirname});
Then when you want to get the file:
depenedentFiles = inheritance.files;
The project also demonstrates how to apply the concept with grunt.watch in order to compile only changed jade files with their dependents, exactly what I needed:
Using jade-inheritance with grunt watch
I imagine something like checking all jade files and if they include your changed file then recompile that as well. Shouldn't be too hard. Pseudo code:
var allFiles = getAllJadeFileWithIncludesAndProjectPathMap();
//allFiles now contains something like this
{
'jade/index.jade': ['jade/menu.jade', 'jade/home.jade'],
'jade/about.jade': ['jade/menu.jade']
}
var rootFiles = [];
_.each(allFiles, function (includes, parent) {
_.each(includes, function (includePath) {
var parent;
while (parent = getParentPath(includePath)) {
//nothing needed if getParentPath returns null for files that aren't included
}
if (rootFiles.indexOf(parent) !== -1) {
rootFiles.push(parent);
}
});
});
Now add these files to the compile task.

Class autoloader in Ember.js?

I am looking for an autoloader, similar to how they operate in languages (e.g. http://php.net/manual/en/language.oop5.autoload.php). I merely specify the algorithm for finding the file and it's automatically loaded into the app.
My initial thinking is a build process that scans directories and builds an index file. Is there a better way?
Here's my solution using browserify and a node.js build script, but I'm curious if anyone has a better solution:
build.js:
var glob = require("glob");
var fs = require('fs');
var path = require('path');
function buildFile(directory, build_file, suffix) {
glob(directory, function(err, files) {
if (fs.existsSync(build_file)) {
fs.unlinkSync(build_file);
}
fs.appendFileSync(build_file, 'module.exports = {');
var controllers = {};
files.forEach(function (file) {
var key = path.basename(file, '.js')+suffix;
var value = "require('"+file+"')";
fs.appendFileSync(build_file, '\n '+key+': '+value + ',');
});
fs.appendFileSync(build_file, '\n}');
});
};
buildFile('./controllers/*.js' , './controllers.js', 'Controller');
buildFile('./routes/*.js' , './routes.js' , 'Route');
app.js:
var App = Ember.Application.create();
App.reopen(require('./controllers.js'));
App.reopen(require('./routes.js'));
routes.js (example output from build.js):
module.exports = {
ApplicationRoute: require('./routes/Application.js'),
IndexRoute: require('./routes/Index.js'),
RecoverRoute: require('./routes/Recover.js'),
RegisterRoute: require('./routes/Register.js'),
UsersRoute: require('./routes/Users.js'),
UsersNewRoute: require('./routes/UsersNew.js'),
ValidateRoute: require('./routes/Validate.js'),
}
I use Grunt.js to watch and rebuild automatically when changes occur.
You probably want to use something like RequireJS: http://requirejs.org/
RequireJS will allow you to specify dependencies which will be loaded as needed. You can also run the RequireJS optimizer to compile your templates and JavaScript in to one file to deploy to your production servers.
One could use a pre-made tool like Yeoman's ember generator or ember tools. They are opinionated about the project's folder structure.

Categories