How to write Gruntfile for Kraken Image Optimizer - gruntjs

I can't figure out how to write the Gruntfile for Kraken Image Optimizer (not KrakenJs): https://github.com/kraken-io/kraken-node
For example, the Gruntfile for grunt-contrib-imagemin looks something like:
imagemin: { // Task
static: { // Target
options: { // Target options
optimizationLevel: 3,
//use: [mozjpeg()]
},
files: { // Dictionary of files
'dist/img.png': 'src/img.png', // 'destination': 'source'
'dist/img.jpg': 'src/img.jpg',
'dist/img.gif': 'src/img.gif'
}
},
dynamic: { // Another target
files: [{
expand: true, // Enable dynamic expansion
cwd: 'src/', // Src matches are relative to this path
src: ['**/*.{png,jpg,gif}'], // Actual patterns to match
dest: 'dist/' // Destination path prefix
}]
}
},
However the documentation for using Kraken on NodeJs looks like this:
var Kraken = require('kraken');
var kraken = new Kraken({
'api_key': 'your-api-key',
'api_secret': 'your-api-secret'
});
var params = {
file: '/path/to/image/file.jpg',
wait: true
};
kraken.upload(params, function(status) {
if (status.success) {
console.log('Success. Optimized image URL: %s', status.kraked_url);
} else {
console.log('Fail. Error message: %s', status.error);
}
});
// For advanced API usage simply alter the `params` object. For example:
var params = {
file: '/path/to/image/file.jpg',
wait: true,
resize: {
width: 100,
height: 75,
strategy: 'crop'
},
s3_store: {
key: 'AKIAO4AD5J4F1NA33Q12',
secret: 'AAwQwJSEgG9WpeCV20GlHI5hGKxKuw5qY80t4CCe',
bucket: 'assets',
path: 'images/layout/header.jpg'
},
webp: true,
lossy: true
};
How can I convert this to something that works in my Gruntfile?

Related

Wrong generated quasar-user-options

I added the Notify component to my quasar.conf.js however when I run quasar dev my quasar-user-options file looks like the following. This means that I cannot use the notify plugin (or any other quasar plugin). I also cannot manualy change it because qhen I run quasar dev it gets overwritten.
/**
* THIS FILE IS GENERATED AUTOMATICALLY.
* DO NOT EDIT.
*
* You are probably looking on adding startup/initialization code.
* Use "quasar new boot <name>" and add it there.
* One boot file per concern. Then reference the file(s) in quasar.conf.js > boot:
* boot: ['file', ...] // do not add ".js" extension to it.
*
* Boot files are your "main.js"
**/
export default { config: {} }
my quasar.conf.jsfile looks like:
/*
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
* the ES6 features that are supported by your Node version. https://node.green/
*/
// Configuration for your app
// https://quasar.dev/quasar-cli/quasar-conf-js
/* eslint-env node */
const ESLintPlugin = require('eslint-webpack-plugin');
/* eslint func-names: 0 */
/* eslint global-require: 0 */
const { configure } = require('quasar/wrappers');
module.exports = configure((ctx) => ({
// https://quasar.dev/quasar-cli/supporting-ts
supportTS: false,
// https://quasar.dev/quasar-cli/prefetch-feature
// preFetch: true,
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://quasar.dev/quasar-cli/boot-files
boot: [
'axios',
'logger',
],
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css
css: [
'app.scss',
],
// https://github.com/quasarframework/quasar/tree/dev/extras
extras: [
// 'ionicons-v4',
// 'mdi-v5',
// 'fontawesome-v5',
// 'eva-icons',
// 'themify',
// 'line-awesome',
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
'roboto-font', // optional, you are not bound to it
'material-icons', // optional, you are not bound to it
],
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build
build: {
vueRouterMode: 'hash', // available values: 'hash', 'history'
// transpile: false,
// publicPath: '/',
// Add dependencies for transpiling with Babel (Array of string/regex)
// (from node_modules, which are by default not transpiled).
// Applies only if "transpile" is set to true.
// transpileDependencies: [],
// rtl: true, // https://quasar.dev/options/rtl-support
// preloadChunks: true,
// showProgress: false,
// gzip: true,
// analyze: true,
// Options below are automatically set depending on the env, set them if you want to override
// extractCSS: false,
// https://quasar.dev/quasar-cli/handling-webpack
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
chainWebpack(chain) {
chain.plugin('eslint-webpack-plugin')
.use(ESLintPlugin, [{ extensions: ['js', 'vue'] }]);
},
extendWebpack(cfg) {
cfg.module.rules.push({
test: /\.pug$/,
loader: 'pug-plain-loader',
});
},
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer
devServer: {
https: false,
port: 8080,
open: true, // opens browser window automatically
},
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework
framework: {
// config: {},
// iconSet: 'material-icons', // Quasar icon set
// lang: 'en-US', // Quasar language pack
// For special cases outside of where the auto-import strategy can have an impact
// (like functional components as one of the examples),
// you can manually specify Quasar components/directives to be available everywhere:
//
// components: [],
// directives: [],
// Quasar plugins
plugins: [
'Notify',
'Loading',
],
},
// animations: 'all', // --- includes all animations
// https://quasar.dev/options/animations
animations: [],
// https://quasar.dev/quasar-cli/developing-ssr/configuring-ssr
ssr: {
pwa: false,
// manualStoreHydration: true,
// manualPostHydrationTrigger: true,
prodPort: 3000, // The default port that the production server should use
// (gets superseded if process.env.PORT is specified at runtime)
maxAge: 1000 * 60 * 60 * 24 * 30,
// Tell browser when a file from the server should expire from cache (in ms)
chainWebpackWebserver(chain) {
chain.plugin('eslint-webpack-plugin')
.use(ESLintPlugin, [{ extensions: ['js'] }]);
},
middlewares: [
ctx.prod ? 'compression' : '',
'render', // keep this as last one
],
},
env: ctx.dev
? {
API_ROOT: 'http://localhost:6502',
API_KEY: 'rJaPTDExnmgB8mLn6oYhu0KdBfEfMlZA',
}
: { // production global config variables
API_ROOT: '',
API_KEY: '',
},
},
// https://quasar.dev/quasar-cli/developing-pwa/configuring-pwa
pwa: {
workboxPluginMode: 'GenerateSW', // 'GenerateSW' or 'InjectManifest'
workboxOptions: {}, // only for GenerateSW
// for the custom service worker ONLY (/src-pwa/custom-service-worker.[js|ts])
// if using workbox in InjectManifest mode
chainWebpackCustomSW(chain) {
chain.plugin('eslint-webpack-plugin')
.use(ESLintPlugin, [{ extensions: ['js'] }]);
},
manifest: {
name: 'pyramid-app-ais-frontend',
short_name: 'pyramid-app-ais-frontend',
description: 'A Quasar Framework app',
display: 'standalone',
orientation: 'portrait',
background_color: '#ffffff',
theme_color: '#027be3',
icons: [
{
src: 'icons/icon-128x128.png',
sizes: '128x128',
type: 'image/png',
},
{
src: 'icons/icon-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: 'icons/icon-256x256.png',
sizes: '256x256',
type: 'image/png',
},
{
src: 'icons/icon-384x384.png',
sizes: '384x384',
type: 'image/png',
},
{
src: 'icons/icon-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
},
},
// Full list of options: https://quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova
cordova: {
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
},
// Full list of options: https://quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor
capacitor: {
hideSplashscreen: true,
},
// Full list of options: https://quasar.dev/quasar-cli/developing-electron-apps/configuring-electron
electron: {
bundler: 'packager', // 'packager' or 'builder'
packager: {
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
// OS X / Mac App Store
// appBundleId: '',
// appCategoryType: '',
// osxSign: '',
// protocol: 'myapp://path',
// Windows only
// win32metadata: { ... }
},
builder: {
// https://www.electron.build/configuration/configuration
appId: 'pyramid-app-ais-frontend',
},
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
chainWebpackMain(chain) {
chain.plugin('eslint-webpack-plugin')
.use(ESLintPlugin, [{ extensions: ['js'] }]);
},
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
chainWebpackPreload(chain) {
chain.plugin('eslint-webpack-plugin')
.use(ESLintPlugin, [{ extensions: ['js'] }]);
},
},
}));

Create subtasks that override `options` but use task level `src`

My gruntfile thus far
uglify: {
src: [
...
],
dest: 'js/application.min.js',
options: {
'compress': {},
'reserveDOMCache': true,
'enclose': undefined,
'exportAll': false,
'expression': false,
'preserveComments': false,
'report': 'min',
'sourceMap': false,
'sourceMapIn': undefined,
'sourceMapIncludeSources': false,
'sourceMapName': undefined,
'wrap': undefined
},
development: {
options: {
'beautify': false,
'mangle': true
}
},
production: {
options: {
'beautify': true,
'mangle': false
}
}
}
However when I run the task uglify:development it will respond with No files created.
As far as I know this is not possible. You need to explicitly define a src for each target.
You could declare a variable outside of the config and add it to each target:
var mySources = ['file1.txt', 'file2.txt']; //declared outside config
development: {
src: mySources, //add variable to each target
Or you can declare a variable inside the config:
mySourcesInside: ['file1.txt'], //declared within config
development: {
src: '<%= mySourcesInside%>', //reference variable in each target
Alternatively you could use something like grunt-override-config https://github.com/masakura/grunt-override-config and declare just one uglify target and overrides for the options.

livereload fired but does not reload page when changes are generated by contrib-copy or tinyimg in destination (!)

Here is my problem.
All mentioned paths as per below gruntfile.js are watched fine (shown in grunt --verbose -v). Livereload fires whenever I change files (at least --verbose shows livereload fires). But the page is live reloaded ONLY in case I change my /development/index.html (in Sublime Text) or /less/mainpage.less (with contrib-less).
If I change development/img//* or anyting in /test//*, livereload FIRES but do not RELOAD my page.
I would really appreciate if someone could help.
Here is my folder structure:
source location root: /development/
destination location root: /test/
Here is my gruntfile.js:
module.exports = function(grunt) {
grunt.initConfig({
watch: {
livereload: {
files: ['development/*.html', "test/**/*", "development/img/**/*"],
options: {
livereload: true,
spawn: false
}
},
// watch tasks start here
scripts: {
files: ['development/js/**/*.js'],
tasks: ['concat']
},
html: {
files: ['development/*.html'],
tasks: ['copy:html']
},
less_compile: {
files: ['development/a_source/less/**/*.less'],
tasks: ['less', "stripCssComments"]
},
images: {
files: ['development/img/**/*'],
tasks: ['tinyimg']
}
},
// runs local server for livereload
connect: {
sever: {
options: {
hostname: 'localhost',
port: 3000,
base: 'test/',
livereload: true
}
}
},
// *** *.html, *.img copy task here
copy: {
html: {
expand: true,
cwd: 'development',
src: '*.html',
dest: 'test/',
}
},
// *** LESS tasks here
less: {
compile: {
options: {
paths: ["development/b_components/less/"]
},
files: {
"temp/css/style.css": "development/a_source/less/style.less"
}
}
}, // compiles less and put compiled version into /temp/css/style.test
stripCssComments: {
dist: {
files: {
'test/css/style.css': 'temp/css/style.css'
}
}
}, // strips comments from /temp/css/style.css and copies it to /test/
// minify images
tinyimg: {
dynamic: {
files: [{
expand: true, // Enable dynamic expansion
cwd: 'development/img/', // Src matches are relative to this path
src: ['**/*.{png,jpg,gif}'], // Actual patterns to match
dest: 'test/img/' // Destination path prefix
}]
}
}
}); //initConfig
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-strip-css-comments');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-tinyimg');
grunt.registerTask('default', ["connect", "watch"]);
}; //wrapper function
Try this
livereload:{
options:{
livereload:'<%= connect.options.livereload %>'
},
files:[
'app/{,*/}*.html',
'app/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
]
}
This should reload if you make changes in your images folder. (Customize the URL according to your structure.)
OK. I just wanted to wrap up after I solved all my problems.
I arrived at the following acceptably working setup:
- as per above: Grunt serves as only "files" worker doing all compile / concat / copy etc.
work.
- browser-sync is my local server (with bunch of other vital capabilities) and fast livereload tool, that additionally syncs
several open test windows (https://browsersync.io/).
- Divvy: great addition to my workflow capable of arranging windows on the desktop on the fly (http://mizage.com/divvy/).
Update:
Soon after I switched to Gulp + Browsersync. I could not be satisfied more. Gulp works roughly 10 times faster and Browsersync is smooth and convenient.
Here is my gulpfile.js example just to demonstrate the tasks this pair manages for me:
var gulp = require("gulp"),
gutil = require("gulp-util"),
less = require('gulp-less'),
concat = require('gulp-concat'),
browserify = require('gulp-browserify'),
browserSync = require('browser-sync').create(),
gulpcopy = require('gulp-copy'),
newer = require('gulp-newer'),
imagemin = require('gulp-imagemin'),
autoprefixer = require('gulp-autoprefixer'),
uglifyjs = require('gulp-uglify'),
cleanCSS = require('gulp-clean-css'),
uglifycss = require('gulp-uglifycss'),
htmlmin = require('gulp-htmlmin'),
htmlhint = require("gulp-htmlhint"),
htmlv = require('gulp-html-validator'),
validatecss = require('gulp-w3c-css'),
sourcemaps = require('gulp-sourcemaps');
var lessMain = ["src/styles/less/styles.less"],
lessSources = ["src/styles/less/*.less", "src/styles/**/*.css"],
jsSources = "src/js/*.js",
jsonSources = ["src/js/*.json"],
htmlSources = ["src/html/*.html"],
imgSources = ["z_design/images/processed/**/*.*"],
imgDest = ["public/img/**/*.*"],
cssTemp = ["src/temp/css/styles.css"],
srcjQuery = "node_modules/jquery/dist/jquery.min.js",
srcMustache = "node_modules/mustache/mustache.min.js";
gulp.task("message", function() {
gutil.log("============= Gulp script started ==============");
});
// compiling less
gulp.task("less-compile", function() {
gulp.src(lessMain)
// switches sourcemaps on/off
.pipe(sourcemaps.init())
.pipe(less()
.on("error", gutil.log))
// switches sourcemaps on/off
.pipe(sourcemaps.write())
// change .dest("folder") to "public/css"
// to make no-autoprefix
// or to "src/temp/css/" to switch autoprefix on
.pipe(gulp.dest("public/css"))
});
// prepare & copy js files
gulp.task("js", function() {
gulp.src([srcjQuery, srcMustache, jsSources])
.pipe(concat("script.js"))
.pipe(gulp.dest("public/js/"))
});
// .pipe(browserify())
// {bundleExternal: false}
// copy JSON files
gulp.task("copyjson", function() {
gulp.src(jsonSources)
.pipe(newer("public/js/"))
.pipe(gulpcopy("public/js/", {
prefix: 2
}))
});
// copy html files
gulp.task("copyhtml", function() {
gulp.src(htmlSources)
.pipe(newer("public/"))
.pipe(gulpcopy("public/", {
prefix: 2
}))
});
// --- minify & compress images: 2 tasks - auto and manual
// minify & copy images - manual task
gulp.task("img-ondemand", function() {
gulp.src("z_design/images/unprocessed/**/*.*")
.pipe(newer("public/img/"))
.pipe(imagemin({
progressive: true
}))
.pipe(gulp.dest('z_design/images/processed/'))
});
// minify & copy images - automatic task
gulp.task("processimages", function() {
gulp.src(imgSources)
.pipe(newer("public/img/"))
.pipe(imagemin({
progressive: true
}))
.pipe(gulp.dest('public/img/'))
});
// --- end
// forced reload
gulp.task("reload", function() {
browserSync.reload();
});
// autoprefixer
gulp.task("autoprefix", function() {
gulp.src(cssTemp)
.pipe(autoprefixer({
browsers: ['last 3 versions', 'safari 5', 'ie 8', 'ie 9', 'ie 10', "ie11", 'opera 12.1', 'ios 6', 'android 4'],
cascade: false
}))
.pipe(gulp.dest("public/css/"))
});
// watching for changes
gulp.task("watch", function() {
gulp.watch(lessSources, ["less-compile"])
gulp.watch(jsSources, ["js"])
gulp.watch(jsonSources, ["copyjson"])
gulp.watch(htmlSources, ["copyhtml"])
gulp.watch(imgSources, ["processimages"])
gulp.watch(imgDest, ["reload"])
gulp.watch("src/temp/css/styles.css", ["autoprefix"])
});
// serving localhost
gulp.task('browser-sync', function() {
browserSync.init({
server: ["public", "src"],
watchTask: true,
open: false,
files: ["public/*.html", "public/css/*.css", "public/js/*.*",
"public/img/**/*.*"]
});
});
// === production preparations: RUN SEPARATE TASKS ON DEMAND ===
// --- minify & compress HTML, CSS, JS
// uglify JS
gulp.task("compress-js", function() {
gulp.src("public/js/script.js")
.pipe(uglifyjs())
.pipe(gulp.dest('public/js/'))
});
// uglify CSS
gulp.task('uglify-css', function() {
gulp.src('public/css/styles.css')
.pipe(uglifycss({
"debug": true,
"uglyComments": true
}))
.pipe(gulp.dest('public/css/'));
});
// compress HTML
gulp.task('compress-html', function() {
return gulp.src('src/html/*.html')
.pipe(htmlmin({
collapseWhitespace: true,
removeComments: true
}))
.pipe(gulp.dest('public/'));
});
// --- lint HTML and validate CSS
// lint html
gulp.task('lint-html', function() {
gulp.src("public/*.html")
.pipe(htmlhint())
.pipe(htmlhint.reporter())
});
// validate html
// Option format set to html
gulp.task('validate-html', function() {
gulp.src('public/*.html')
.pipe(htmlv({
format: 'html'
}))
.pipe(gulp.dest('src/temp/validation/'));
});
// add css validation
gulp.task('validate-css', function() {
gulp.src('public/css/*.css')
.pipe(validatecss())
.pipe(gulp.dest('src/temp/validation/'));
});
gulp.task("validate", ["validate-html", "validate-css", "lint-html"]);
gulp.task("compress", ["compress-js", "uglify-css", "compress-html"]);
gulp.task("default", ["watch", "browser-sync"]);
// =======================

grunt-contrib-connect running but not working

I am using : https://github.com/gruntjs/grunt-contrib-connect
"grunt-contrib-connect": "^1.0.2",
After run: grunt connect
Running "connect:server" (connect) task
Started connect web server on http://localhost:8000
Done.
So when I check in my browser: localhost:8000 , does not open anything.
can someone help on this?
gruntfile pastenbin : http://pastebin.com/nL771d5j
Gruntfile.js
module.exports = function (grunt) {
var config = {};
//setup the configuration object
var jshint;
//all tasks that must be loaded.
var tasks = [
,'grunt-contrib-watch'
,'grunt-contrib-concat'
,'grunt-contrib-sass'
,'grunt-contrib-connect'
];
//src ===============================
var src;
config.src = src = {
sassMain : 'scss/main.scss',
distFolder : 'public/stylesheets/dist.css',
devFolder : 'public/stylesheets/dev.css',
sassFolder : 'scss/**/*.scss',
serverPort: 9000,
serverHost: '0.0.0.0'
};
//Concat ===============================
var concat
config.concat = concat = {};
concat.dev = {
files: {
"public/myapp.development.js": [
"with-bootstrap/public/js/vendor"
,"with-bootstrap/public/js/**/*.js"
]
}
};
//Watch ===============================
config.watch = {
scripts: {
files: ["<%= src.sassFolder %>"]
,tasks: ["sass:dist"]
}
}
//Sass ===============================
var sass;
config.sass = sass = {};
//distribution
sass.dist = {
options: {
style: "compressed",
noCache: true,
sourcemap: 'none',
update:true
}
, files: {
"<%= src.distFolder %>" : "<%= src.sassMain %>"
}
};
//development env.
sass.dev = {
options: {
style: "expanded",
lineNumber: true,
}
, files: {
"<%= src.devFolder %>" : "<%= src.sassMain %>"
}
};
//grunt serve ===============================
config.connect = {
server: {
options: {
port: 8000,
base: {
path: 'SITE',
options: {
index: 'index.html',
maxAge: 300000
}
}
}
}
};
//Register custom tasks ===============================
grunt.registerTask('default',['dev']);
grunt.registerTask('dev', ['concat:dev','sass:dev']);
grunt.registerTask('dist',['concat:dev','sass:dist']);
//General setup ===============================
grunt.initConfig(config);
tasks.forEach(grunt.loadNpmTasks);
};
If you're running the grunt-contrib-connect plugin by itself, you'll need the property keepalive set to true in your grunt config:
config.connect = {
server: {
options: {
port: 8000,
keepAlive: true,
base: {
path: 'SITE',
options: {
index: 'index.html',
maxAge: 300000
}
}
}
}
};
Worth Noting if you want to use your connect config as part of a chain of tasks, keepAlive will need to be set to false, otherwise tasks after connect task won't run. You can also keep connect running by pairing it with the watch task w/o using the keepAlive option.

How to create standalone bundle of React & React with Addons using Grunt + Browserify?

I am trying to configure Grunt & Browserify to output a standalone bundle containing, among other things, React as CommonJS module so that it can be referenced by other bundles.
The problem that I am having now is that the aliasing does not seem to work. Despite having specified aliases in my external bundle vendor below, and having specified that those modules should be loaded externally in all the other models, I'm still getting an error at run time, stating that the 'react' module cannot be found.
It would be great if anyone knows what might be wrong about my grunt-browserify syntax here:
var externals = [
'react',
'react/addons',
'jquery',
'backbone',
'react-router'
];
module.exports = function(grunt) {
grunt.config.set('browserify', {
main: {
src: 'assets/js/main.jsx',
dest: '.tmp/public/js/main.js',
options: {
debug: true,
extensions: ['.jsx'],
external: externals,
transform: [
['babelify', {'stage': 0}]
]
}
},
signup: {
src: 'assets/js/signup.jsx',
dest: '.tmp/public/js/signup.js',
options: {
debug: true,
extensions: ['.jsx'],
external: externals,
transform: [
['babelify', {'stage': 0}]
]
}
},
login: {
src: 'assets/js/login.jsx',
dest: '.tmp/public/js/login.js',
options: {
debug: true,
insertGlobals: true,
extensions: ['.jsx'],
external: externals,
transform: [
['babelify', {'stage': 0}]
]
}
},
vendor: {
src: [
'./node_modules/react/dist/react.js',
'./node_modules/react/dist/react-with-addons.js',
'./node_modules/jquery/dist/jquery.js',
'./node_modules/backbone/backbone.js',
],
dest: '.tmp/public/js/dependencies/vendor.js',
options: {
debug: false,
alias: {
'react:': './node_modules/react/dist/react.js',
'react/addons': './node_modules/react/dist/react-with-addons.js',
'jquery': './node_modules/jquery/dist/jquery.js',
'backbone': './node_modules/backbone/backbone.js',
'react-router': './node_modules/react-router/lib/index.js'
},
shim: {
react_router: {
path: './node_modules/react-router/lib/index.js',
exports: 'react-router'
}
},
external: null
}
}
});
grunt.loadNpmTasks('grunt-browserify');
};
I've found this link helpful for me. Following this approach your vendor section should look like
vendor: {
src: ['.'],
dest: '.tmp/public/js/dependencies/vendor.js',
options: {
debug: false,
alias: externals.map(function(module) {
return module + ':';
}),
shim: {
react_router: {
path: './node_modules/react-router/lib/index.js',
exports: 'react-router'
}
},
external: null
}
}
I'm not sure about the shim section above, because I've tried this only for the react module.
I am working on a Sails 0.11.0 project with ReactJS. I start my grunt watchify task to handle rebundling/transforming my app code, while using the vendor task to bundle the moduled code. I additionally had to add the vendor.js to my layout file.
I am fairly new to the grunt world, so there may be more efficient ways to do this.
browserify.js
var externals = [
'react',
'react/addons',
'jquery',
'react-router',
'events'
]
module.exports = function(grunt){
grunt.config.set('browserify', {
dist: {
options: {
external: externals,
transform: [
['babelify', {
loose: 'all'
}]
]
},
files: {
".tmp/public/js/bundle.js": ["assets/js/bundle.js", "react/**/*"]
}
},
vendor: {
src: [
'./node_modules/react/dist/react.js',
'./node_modules/react/dist/react-with-addons.js',
'./node_modules/jquery/dist/jquery.js',
'./node_modules/react-router/lib/index.js',
'./node_modules/events/events.js'
],
dest: '.tmp/public/js/dependencies/vendor.js',
options: {
alias: {
'react': './node_modules/react/dist/react.js',
'react/addons': './node_modules/react/dist/react-with-addons.js',
'jquery': './node_modules/jquery/dist/jquery.js',
'react-router': './node_modules/react-router/lib/index.js',
'events': './node_modules/events/events.js'
}
}
}
});
grunt.loadNpmTasks('grunt-browserify');
}
tasks/config:
module.exports = function(grunt) {
grunt.config.set('watch', {
api: {
// API files to watch:
files: ['api/**/*', '!**/node_modules/**']
},
assets: {
// Assets to watch:
files: ['assets/**/*', 'tasks/pipeline.js', '!**/node_modules/**'],
// When assets are changed:
tasks: ['syncAssets' , 'linkAssets', 'browserify:dist']
},
react: {
files: ['react/**/*'],
tasks: ['browserify:dist']
}
});
grunt.loadNpmTasks('grunt-contrib-watch');
};
Here is the full Grunt configuration for the React:
https://github.com/koshkarov/reactjs-grunt-configuration
I've created a dummy project so you can build it to test.
Gruntfile.js for the project:
module.exports = function (grunt) {
let concat = {};
let clean = {};
let uglify = {};
let copy = {};
let htmlmin = {};
let cssmin = {};
let browserify = {};
let watch = {};
let template = {};
let run = {};
/* React configuration. */
const reactSourcePath = './source';
const reactCompiledPath = './client';
const reactHtmlPathDest = './client/index.html'
const reactTargetName = "react";
const reactFileName = "react_main";
/* ### TASK CONFIGURATIONS ### */
/* Clean compiled files. */
clean[reactTargetName] = [
`${reactCompiledPath}`
];
/* Concatenate all CSS files to one. */
const cssConcatSourceTemplate = `${reactSourcePath}/**/**.css`;
const cssDestinationFile = `${reactCompiledPath}/css/${reactFileName}.css`;
concat[reactTargetName] = {
src: [cssConcatSourceTemplate],
dest: cssDestinationFile
};
/* Convert JSX to JS, prepare JS files for a browser and copy to the destination. */
const jsSourceFile = `${reactSourcePath}/index.js`;
const jsDestinationFile = `${reactCompiledPath}/js/${reactFileName}.js`;
browserify[reactTargetName] = {
options: {
transform: [['babelify', {presets: ['es2015', 'react']}]]
},
files: {
[jsDestinationFile]: jsSourceFile
}
};
/* Replace js/css placeholders and copy html file to destination. */
const applicationData = {
css: [
'./css/react_main.css'
],
js: [
'./js/react_main.js'
]
};
var jsFiles = "";
var cssFiles = "";
applicationData.css.forEach(function(item) {
cssFiles = cssFiles + `\n<link rel="stylesheet" type="text/css" href=${item}>`;
});
applicationData.js.forEach(function(item) {
jsFiles = jsFiles + `\n<script type="text/javascript" src=${item}></script>`;
});
template[reactTargetName] = {
options: {
data: {
appName: '<%= pkg.name %>' + '-react',
productVersion: '<%= pkg.version %>',
reactEmbeddedCssFiles: cssFiles,
reactEmbeddedJsFiles: jsFiles
}
},
files: {
[`${reactHtmlPathDest}`]: `${reactSourcePath}/index.template.html`,
}
};
/* Uglify react JS file. */
uglify[reactTargetName] = {
files: {
[jsDestinationFile]: jsDestinationFile
}
};
/* Copy bootstrap CSS/JS files. */
copy[reactTargetName] = {
files: {
[`${reactCompiledPath}/css/bootstrap.min.css`]: 'node_modules/bootstrap/dist/css/bootstrap.min.css',
[`${reactCompiledPath}/js/bootstrap.min.js`]: 'node_modules/bootstrap/dist/js/bootstrap.min.js',
[`${reactCompiledPath}/js/jquery.min.js`]: 'node_modules/jquery/dist/jquery.min.js',
}
}
/* Minify HTML files. */
htmlmin[reactTargetName] = {
options: {
removeComments: true,
collapseWhitespace: true
},
files: {
[`${reactHtmlPathDest}`]: `${reactHtmlPathDest}`
}
};
/* Minify react CSS file. */
cssmin[reactTargetName] = {
files: {
[cssDestinationFile]: cssDestinationFile
}
};
/* Watch for any changes in react app.
There are three separate watches for css, js, and html files. */
watch[reactTargetName + '_css'] = {
files: [`${reactSourcePath}/**/*.css`],
tasks: [`concat:${reactTargetName}`],
options: {
livereload: true
}
};
watch[reactTargetName + '_js'] = {
files: [`${reactSourcePath}/**/*.js`],
tasks: [`browserify:${reactTargetName}`],
options: {
livereload: true
}
};
watch[reactTargetName + '_hmtl'] = {
files: [`${reactSourcePath}/**/*.html`],
tasks: [`template:${reactTargetName}`],
options: {
livereload: true
}
};
/* Jest tests */
jestTestsTaskName = reactTargetName + '_jest_tests';
run[jestTestsTaskName] = {
exec: 'npm test'
};
/* Generate task names for react. */
var reactTasks = {
debug: [
"clean",
"browserify",
"concat",
"copy",
"template"
].map(x => x + `:${reactTargetName}`),
release: [
"clean",
"browserify",
"concat",
"copy",
"template",
"htmlmin",
"uglify",
"cssmin"
].map(x => x + `:${reactTargetName}`)
};
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
watch:watch,
copy:copy,
concat:concat,
clean:clean,
uglify:uglify,
template:template,
browserify: browserify,
htmlmin: htmlmin,
cssmin: cssmin,
run:run
});
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-template');
grunt.loadNpmTasks("grunt-browserify");
grunt.loadNpmTasks("grunt-contrib-htmlmin");
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-run');
grunt.registerTask('react_build_debug', reactTasks.debug);
grunt.registerTask('react_build_release', reactTasks.release);
}

Resources