I've recently started exploring grunt and I've done so by trying out the
https://github.com/ng-vu/ng-express-boilerplate seed.
It uses a gruntfile to copy static assets, css, javascript to a destination folder with this code:
dist_js: {
files: [{
src: ['<%= app_files.js %>', '<%= vendor_files.js %>'],
dest: '<%= dist_dir %>/public',
cwd: '.',
expand: true
}]
}
where dist_dir is 'dist'.
Now in the config files it is specified that in development we want to serve assets, src and vendor files from their original location, so the configuration of express for development is as follows:
app.use('/assets', express.static(path.resolve(__dirname, '../assets')));
app.use('/src', express.static(path.resolve(__dirname, '../../src')));
app.use('/vendor', express.static(path.resolve(__dirname, '../../vendor')));
In production, the code is:
app.use(express.static(path.join(__dirname, 'public')));
When I move this to production (specifically to openshift), the index.html template is served with all the script and link tags, but the scripts and css are not found. What I get is 'Cannot GET file.css' for example.
I run grunt build and compile on the cloud and the output is 'Done, without errors', but the files are nowhere to be found. I've tried one too many things and I've run out of ideas. Have I missed out something fundamental about grunt?
EDIT:
The problem was that the javascript files doing the routing are located inside src/server, so __dirname pointed to that location. Grunt however did the copying inside the project root.
app.use(express.static(path.join(__dirname, '../../dist/public')));
This fixed the problem.
You need to expose your assets with a route.
app.use(express.static(path.join(__dirname, 'dist/public')));
You need to modify your package.json file to call grunt after running npm install. To do this add the following.
"scripts": {
"start": "node ./src/server/app.js",
"postintall": "grunt build; grunt compile"
}
Additionally it might be good to define a new grunt task that combines these two tasks.
Gruntfile.js
grunt.registerTask("prep-for-cloud", [ "compile", "build" ]);
Then your package.json would look like the following.
"scripts": {
"start": "node ./src/server/app.js",
"postintall": "grunt prep-for-cloud"
}
Also you should add grunt-cli to your package.json file as a dependency.
Related
My nodejs based application uses Pug for rendering; specifically, i set the view engine and then set the view directory (which lives inside of the src directory that all other source lives in.
app.set('view engine', 'pug');
app.set('views', __dirname + '/views');
[...]
res.render('login')
This works correctly for the development version, which simply runs and interprets the typescript file that the above code is in. However, I would like to compile to javascript in production, and have the following (seemingly standard) typescript configuration:
{
"compilerOptions": {
"target": "esnext",
"module": "CommonJS",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir":"./dist",
"rootDir": "./src"
},
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Recommended",
"files": ["src/index.ts"],
}
Which reflects the directory structure of my code:
Unfortunately, this fails to work when the application is compiled and ruins index.js; it cannot find the .pug files in /dist/views:
Error: Failed to lookup view "posts" in views directory "/app/dist/views"
I understand the error message, but not how to fix the problem. Should I be copying the templates to that folder as part of the build process? Should I be invoking them in another way that the compiler can 'understand' and include? Is compiling typescript to javascript simply an obsolete build step entirely?
app.set('views', __dirname + '/views');
This line configures Express to load the templates from the views folder, located in the same place as the executed script (which would be /dist in that case).
The options are:
Configure the build to copy the templates to /dist/views.
Change the path settings, and put for example all .ts sources in /src, view templates in /views and configure app.set('views', __dirname + '/../views'); so it would be seen from /src as well as from /dist.
Or skip the build step altogether and use ts-node, it can execute your .ts files directly.
edit: Bundling template scripts with the compiled .js file would only make sense or be useful if the bundle had to be sent to a Web browser, but as Express applications typically run server-side and not in a browser, Express does not support such a handling.
Copy non-typescript files to outDir
I was facing the same problem but there is just a simple solution to get all non-ts files in your build folder
here the package I used Typescript-cp
{
//...
"scripts": {
"start": "tsc -w & tscp -w",
"build": "tsc && tscp"
},
//...
}
it will copy all your non-ts files in the build folder but you can specify which one to copy I think read Docs
I use several Node/Express modules in my app, and everything works fine for every module as long as I do const module = require('module');. I don't need to define a static path for these modules as app.use(express.static(path.join(__dirname, 'public')));.
However, for the sweetalert module, if I define in my layout.pug (base pug file) script(src="/node_modules/sweetalert/dist/sweetalert.min.js"), I get a 404 Error (not found) unless I include in app.js the following static path: app.use("/node_modules", express.static(__dirname + "/node_modules"));.
My question is: is this the normal behaviour or is it something I'm not doing right? (I'm kinda confused why I have to define a static path just for one of several modules I use.
Here's whats going on:
app.use(express.static(path.join(__dirname, 'public'))); is declaring that the public directory is accessible to the browser. You should put all your front end resources in that folder. This will help separate what can be accessed from the server and what can be accessed from the client.
When you reference script(src="/node_modules/sweetalert/dist/sweetalert.min.js") the browser throws a 404 because that file is not located in the public directory, therefore off limits to the browser.
Adding this line app.use("/node_modules", express.static(__dirname + "/node_modules")); "fixes" your issue but now exposes all your node_modules to the browser. This probably isn't a good idea and I'm sure a security expert could elaborate why this shouldn't be done.
How I would resolve this issue: Go through your .pug code and look at any resources your front end requires. Then copy them over to the public folder and fix your references to use the copy of the resource.
Here's an example of a script I use to move resources from the node_module directory to a public/assets directory:
build.js:
const path = require('path');
var fs = require('fs');
const ASSETS = [
'jquery/dist/jquery.min.js',
'sweetalert/dist/sweetalert.min.js'
];
if (!fs.existsSync('./public/assets')){
fs.mkdirSync('./public/assets');
}
ASSETS.map(asset => {
let filename = asset.substring(asset.lastIndexOf("/") + 1);
let from = path.resolve(__dirname, `./node_modules/${asset}`)
let to = path.resolve(__dirname, `./public/assets/${filename}`)
if (fs.existsSync(from)) {
fs.createReadStream(from).pipe(fs.createWriteStream(to));
} else {
console.log(`${from} does not exist.\nUpdate the build.js script with the correct file paths.`)
process.exit(1)
}
});
then I update my package.json to include this in the scripts:
package.json:
"scripts": {
"build": "node ./build.js || true",
"start": "node ./bin/www"
}
then in any of my views pages I reference the resource by using the new path
random.pug:
script(src="/assets/jquery.min.js")
script(src="/assets/sweetalert.min.js")
Finally before you deploy your app you now must run the following command:
npm run build then npm start
You will only need to run the build command if your front end resources change. So if you only ever use sweetalert.min.js you will only need to run the build the first time you run your app. If later on you add another resource aNewResource.js you will need to update the build.js file and run npm run build again.
I have in my Scripts folder a "Lib" folder and an "App" folder.
The Lib folder contains 3rd part library JavaScript files. Some of these are minified without the original sources, others we have the original sources for.
The App folder contains all of our own JavaScript files, all of which are not minified.
I'm new to Grunt but I have a gruntfile which does the following:
Uglifies all the JS files in the Lib folder and produces minified versions with sourcemaps.
Uglifies all the JS files in the App folder and produces minified versions with sourcemaps.
Obvious problem: some of the files in the Lib folder are minified, so minifying them again/generating source maps is a bad idea and can fail for various reasons.
My solution: I run Uglify only on .js files in the Lib folder into lib-unmin.min.js. I then concat all the already minified files into a lib-min.min.js file, then I concat both those files together to get lib.min.js.
The new problem
What if I can't concat the already minified scripts to the end of the other minififed scripts without it breaking?
I have a dependency issue like this:
scripts/lib/a.js (required for b to run)
scripts/lib/b.min.js (required for c to run)
scripts/lib/c.js (required for the App scripts to run)
If I have an array of these file paths in my gruntfile, in that order, what's the easiest way of uglifying/concating all the files into a single minified JS file in that order, making sure we don't attempt to minify the minified file?
What do other developers do in similar situations?
Thanks!
I like to concat the files then uglify all of them together. This way uglify makes sure there aren't duplicate variable values overriding each other when it compresses the variable names.
You can bundle as many files as you want in the concats. Make different concat groups to uglify together and maintain the 'first, second, third, ...' order like this:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
app: {
options: {
sourceMap: true,
sourceMapName: 'build/maps/map.map'
},
files: {
'build/app.min.js': ['build/js/app-first-unmin.js', 'build/js/app-second-min.js', 'build/js/app-third-unmin.js']
}
}
},
concat: {
options: {
separator: ';'
},
firstUnminified: {
src: [
'lib/underscore.js'
],
dest: 'build/js/app-first-unmin.js'
},
secondMinified: {
src: [
'lib/moment.min.js'
],
dest: 'build/js/app-second-min.js'
},
thirdUnminified: {
src: [
'lib/favico.js'
],
dest: 'build/js/app-third-unmin.js'
}
},
});
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('default', ['concat:firstUnminified', 'concat:secondMinified', 'concat:thirdUnminified','uglify']);
};
Is it possible to run Grunt Karma locally?
Start the Karma server, assign a port to it and then open different browsers on my computer and run tests by inserting the localhost:port?
I have a Github project running Travis and have strange results in some Browsers. I can run the tests locally but only with "virtual" PhantomJS. Would be nice to check my Specs in a real browser.
I regularly use karma-chrome-launcher and know that there is also karma-firefox-launcher as well.
In your karma.conf.js file, or in your Gruntfile.js options area you can define:
browsers: ['Phantomjs', 'Chrome'],
and then in the plugins section include:
plugins: [
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-phantomjs-launcher'
]
https://github.com/karma-runner has a list of launcher plugins and other useful plugins. There is even a karma-browserstack-launcher, though that wouldn't be local.
Documentation on what you can configure either total in the Gruntfile.js or by referencing a karma.conf.js in your Gruntfile.js can be found:
https://github.com/karma-runner/grunt-karma
http://karma-runner.github.io/0.10/config/configuration-file.html
I like the functionality of using a karma.conf.js file to separate out the majority of my karma config, so in my Gruntfile.js I do the following:
karma: {
options: {
configFile: 'karma.conf.js'
},
unit: {
autoWatch: true,
singleRun: true
},
watch: { // still needs watch integration and testing
browsers: ['PhantomJS'],
background: true
}
},
Then in my karma.conf.js file I follow the base structure shown https://github.com/karma-runner/karma/blob/master/test/client/karma.conf.js
I'm just getting started with the MEAN stack (https://github.com/linnovate/mean), so I'm pretty sure my question is going to look very basic to an expert, so my apologies in advance!
While I think it would be a gread addition to what this stack already has to offer, I cannot manage to integrate Uglify.js and stylus
Also someone already asked this, but it would make sense to me to use Jade template for both server and public views, at least for a matter of standardization.
I have tried playing with the grunt file and server.js, renaming some files, but all I managed to achieve so far, is break the original project...
Thanks in advance!
EDIT: Just found a fork of this project which has just added support for jade templates for public views: https://github.com/tutley/mean
This post explains how to integrate Stylus pre-processing to the MEAN stack: http://to-s.tk/integrate-stylus-to-the-mean-stack/
Short version:
Move public/css to a new assets/stylesheets and rename all the .css files to .styl
Install grunt-contrib-stylus through npm's package.json, as both a dev and runtime dependency.
-Configure stylus compilation in your Gruntfile
// ...
grunt.initConfig({
// ...
watch: {
// ...
stylus: {
files: ['assets/stylesheets/**/*.styl'],
tasks: ['stylus']
},
// ...
},
// ...
stylus: {
compile: {
options: {
paths: ['assets/stylesheets/**']
},
files: [{
dest: 'public/css/',
cwd: 'assets/stylesheets/',
src: '*.styl',
ext: '.css',
expand: true
}]
}
},
// ...
});
//...
//Load NPM tasks
// ...
grunt.loadNpmTasks('grunt-contrib-stylus');
// ...
Import views stylus files (or any substylus) in common.styl using #require statements
Remove references to views or other substylesheets in head.jade.
Then all assets/stylesheets/*.styl files should be automatically compiled into public/css/*.css, as long as grunt is running. To trigger a compile without relying on watch, you can run grunt stylus.