/// //config file for grunt, all projects. this is devenv stuff //example usage: "grunt build-dev" from command-line. import _ = require("lodash"); import json5 = require("json5"); import path = require("path"); import convertSourceMap = require("convert-source-map"); //https://www.npmjs.com/package/convert-source-map //import commonDir = require("commondir"); /** entrypoint accessed by grunt on execution. here we load tasks and config them. */ function __entryPoint(grunt: IGrunt) { var config: grunt.config.IProjectConfig = { /** read npm package.json values, so values can be read by various grunt tasks. */ pkg: grunt.file.readJSON("package.json"), }; //function loadExample() { // //first load up your task // grunt.loadNpmTasks("npm-name-here"); // //then configure it // _.merge(config, { some: "config stuff" }); // //then do any fancy code based configurations here // grunt.event.on("watch",(action: string, filepath: string, watchTaskName: string) => { // changedFiles[filepath] = action; // //do stuff here // }); //} //loadExample(); function loadContribWatch() { grunt.loadNpmTasks("grunt-contrib-watch"); //do work on changed files _.merge(config, { watch: {} }); } loadContribWatch(); function loadTsLint() { grunt.loadNpmTasks("grunt-tslint"); //linter on typescript save (used with watch) _.merge(config, { tslint: { //see https://www.npmjs.com/package/grunt-tslint options: { // Task-specific options go here. see https://github.com/palantir/tslint configuration: json5.parse(grunt.file.read("tslint.json")), }, default: { src: ["**/*.ts", "!**/node_modules/**"], }, }, }); } loadTsLint(); function loadTs() { grunt.loadNpmTasks("grunt-ts"); //compile typescript (used with watch) _.merge(config, { ts: { //see https://www.npmjs.com/package/grunt-ts default: { src: ["**/*.ts", "!**/*.d.ts", "!**/node_modules/**"], options: { module: "commonjs", comments: true, compiler: "./node_modules/typescript/bin/tsc", }, }, }, }); } loadTs(); function loadTypeDoc() { grunt.loadNpmTasks("grunt-typedoc"); //autodoc typescript _.merge(config, { typedoc: { //see https://www.npmjs.com/package/grunt-typedoc build: { options: { module: "commonjs", out: "./docs", name: "test-docs", target: "es5", }, src: ["**/*.ts", "!**/*.d.ts", "!**/node_modules*"], }, }, }); } loadTypeDoc(); function loadTsd() { grunt.loadNpmTasks("grunt-tsd"); //auto-update of tsd typings _.merge(config, { tsd: { //see https://www.npmjs.com/package/grunt-tsd refresh: { options: { // execute a command command: 'reinstall', //optional: always get from HEAD latest: true, // optional: specify config file config: './tsd.json', // experimental: options to pass to tsd.API opts: { // props from tsd.Options } } } }, }); } loadTsd(); function loadContribClean() { grunt.loadNpmTasks("grunt-contrib-clean"); //delete files/folders } loadContribClean(); function loadBrowserify() { grunt.loadNpmTasks("grunt-browserify"); //hook browserify compiler _.merge(config, { browserify: {} }); } loadBrowserify(); function loadBrowserifyTsify() { //no grunt task to load, this is a browserify plugin. _.merge(config, { browserify: { watch_target_ts: { options: { plugin: ["tsify"], watch: true, //in BUILD runs, set this to false to avoid full path in bundle. see https://github.com/jmreidy/grunt-browserify/issues/245 //banner: "", //DO NOT USE: causes source-mappings to be off by 1 line. //keepAlive: true, //preBundleCB: (b: BrowserifyObject) => { b.plugin("tsify"); }, postBundleCB: (err: string, src: Buffer, next: (err: string, modifiedSrc: Buffer) => void) => { //fixup sourcemaps: convert windows backslash dir seperators into unix style (chrome only supports unix) //and adjust the bundled script folder to be the same as the webpage (by default browserify assumes page is in the grunt root/baseDir cwd) { var files: _.Dictionary = grunt.config("browserify.watch_target_ts.files"); //console.log("keys= ", _.keys(files)); var bundlePath = path.normalize(_.keys(files)[0]); var bundleDir = path.dirname(bundlePath); //"node-scratch/" var bundleFile = path.basename(bundlePath); //add "back dirs" equal to the bundleDir subdir depth, as our sourcemaps var bundleDirCount = bundleDir.split(path.sep).length; var pageBaseDirAdjust = new Array(bundleDirCount + 1).join(".." + path.sep); //convert windows relative-path source maps to linux style (backslash to slash) //logic inspired from https://github.com/smrq/gulp-fix-windows-source-maps/blob/master/index.js var contents = src.toString(); var sourceMap = convertSourceMap.fromSource(contents); var sources: string[] = sourceMap.getProperty("sources"); _.forEach(sources,(value, index, collection) => { var sourcePath = path.normalize(value); sourcePath = path.join(pageBaseDirAdjust, sourcePath); console.log("sourcePath=", sourcePath); sources[index] = sourcePath.replace(new RegExp("\\\\", "g"), "/"); }); sourceMap.setProperty("sources", sources); } //clean up the sourcemap and extract it to it's own file for uglify use { //try removing the source content to force loading original sourceMap.setProperty("sourcesContent", null); var sourceMapPath = bundlePath + ".map"; grunt.file.write(sourceMapPath, sourceMap.toJSON()); //var modifiedContents = contents.replace(convertSourceMap.commentRegex, sourceMap.toComment()); var modifiedContents = contents.replace(convertSourceMap.commentRegex, "//# sourceMappingURL=" + path.basename(sourceMapPath)); } var modifiedSrc = new Buffer(modifiedContents); next(err, modifiedSrc); }, browserifyOptions: { debug: true, }, }, files: { //"./node-scratch/main.page.bundle.js": ["./node-scratch/main.page.ts"], }, }, }, }); } loadBrowserifyTsify(); function loadUglify() { grunt.loadNpmTasks("grunt-contrib-uglify"); //minifier _.merge(config, { // clean: { //see https://github.com/gruntjs/grunt-contrib-clean ////tsd:["./typings/tsd.d.ts"], // }, uglify: { //see https://www.npmjs.com/package/grunt-contrib-uglify watch_target: { options: { mangle: false, //don't need, use gzip compression. can use this for obfuscation purposes in production build. compress: true, sourceMap: true, sourceMapIn: null, sourceMapRoot: null, //set base path for sourcemap sources. maxLineLen: 255, ASCIIOnly: false, preserveComments: false, beautify: { beautify: true, }, banner: "/* uglify.options.banner: <%= pkg.name %> <%= grunt.template.today(\"isoDateTime\") %> */", //yyyy-mm-ddtHH:mm:ss //footer: "/* uglify.options.footer */", //CAN NOT USE FOOTER!, messes up source mappings }, files: { //'dest/output.min.js': ['src/input.js'], } }, }, }); } loadUglify(); /** es6 to 5 transpiler: https://babeljs.io/docs/using-babel/ */ function loadBabel() { //grunt.loadNpmTasks("load-grunt-tasks"); require("load-grunt-tasks")(grunt); _.merge(config, { babel: { options: { sourceMap: true, //"inline", //comments: false, //inputExtension:".js", //outputExtension: ".es5", //our custom hack }, watch_target: { files: {}// {"source":"dest"} } } }); //set input/output for watch tasks. see https://github.com/gruntjs/grunt-contrib-watch grunt.event.on("watch",(action: string, filepath: string, watchTaskName: string) => { var outPath = filepath + ".es5"; var fileMap = {}; fileMap[outPath] = filepath; grunt.config("babel.watch_target.files", fileMap); }); } loadBabel(); /** custom watches that don't belong in other sections (or in multiple sections)*/ function loadVNextCustomTasks() { function loadCustomWatch() { _.merge(config, { // clean: { //see https://github.com/gruntjs/grunt-contrib-clean ////tsd:["./typings/tsd.d.ts"], // }, watch: { //see https://www.npmjs.com/package/grunt-contrib-watch //full pipeline: compile and other transforms typescriptCompile: { files: ["**/*.ts", "!**/*.d.ts", "!**/node_modules/**"], tasks: ["ts", "tslint"], options: { //if you need to dynamically modify your config, the spawn option must be disabled to keep the watch running under the same context. spawn: false, } }, //don't compile, but do the other post-compile (downstream) processes typescriptDownstream: { files: ["**/*.ts", "!**/node_modules/**"], tasks: ["tslint"], options: { //if you need to dynamically modify your config, the spawn option must be disabled to keep the watch running under the same context. spawn: false, } }, browserify: { files: ["www_**/*.page.ts", "!www_**/node_modules/**"], //["node-scratch/*.page.ts", "!**/node_modules/**"], tasks: ["browserify:watch_target_ts", "uglify:watch_target"], options: { //if you need to dynamically modify your config, the spawn option must be disabled to keep the watch running under the same context. spawn: false, } }, jsBuild: { files: ["**/*.js", "!**/node_modules/**"], tasks: ["babel:watch_target"], options: { //options here: https://www.npmjs.com/package/grunt-contrib-watch spawn: false, //if you need to dynamically modify your config, the spawn option must be disabled to keep the watch running under the same context. debounceDelay: 500, //500 is the default event: ["all"], //all,changed,added,deleted atBegin: false, //the default. set to true to run the task for all files at startup } } } }); } loadCustomWatch(); /** configuration to allow one-off multiwatch, taken from here: https://www.npmjs.com/package/grunt-contrib-watch */ function loadCustomEvents() { /** buffer of pending files to trigger watch events for. clears out after every exec of .onChange() */ var changedFiles: _.Dictionary = {}; /** listener-worker, executes 200ms after the last changed file */ var onChange = _.debounce(function (action: string, filepath: string, watchTaskName: string) { //see "Compiling Files As Needed" section of https://github.com/gruntjs/grunt-contrib-watch for details. var src: {} = grunt.config("tslint.default.src"); src[0] = Object.keys(changedFiles); grunt.config("tslint.default.src", src); src = grunt.config("ts.default.src"); src[0] = Object.keys(changedFiles); grunt.config("ts.default.src", src); var browserifyFiles: _.Dictionary<[string]> = {} var uglifyFiles: _.Dictionary<[string]> = {} _.forIn(changedFiles,(value, key, collection) => { var sourcePath = path.normalize(key); var sourceDir = path.dirname(sourcePath) var sourceFile = path.basename(sourcePath); var ext = path.extname(sourceFile); //var destFile = sourceFile.replace(ext, ".bundle.js"); var bundlePath = sourcePath.replace(ext, ".bundle.js"); var bundlePathSourceMap = bundlePath + ".map"; browserifyFiles[bundlePath] = [sourcePath]; //uglify ext = path.extname(bundlePath); var minBundlePath = bundlePath.replace(ext, ".min.js"); uglifyFiles[minBundlePath] = [bundlePath]; grunt.config("uglify.watch_target.options.sourceMapIn", bundlePathSourceMap); //console.log(destPath, sourceFile); }); grunt.config("browserify.watch_target_ts.files", browserifyFiles); grunt.config("uglify.watch_target.files", uglifyFiles); //clear out our buffer changedFiles = {}; }, 200); grunt.event.on("watch",(action: string, filepath: string, watchTaskName: string) => { changedFiles[filepath] = action; onChange(action, filepath, watchTaskName); }); } loadCustomEvents(); } loadVNextCustomTasks(); grunt.initConfig(config); /** allow toggling of the grunt --force option. usage: grunt.registerTask('foo',['bar','force:on','baz','force:restore']); * from: https://github.com/gruntjs/grunt/issues/810#issuecomment-27363230 */ function forceTaskHack() { var previous_force_state = grunt.option("force"); grunt.registerTask("force", "allow toggling of the grunt --force option. usage: grunt.registerTask('foo',['bar','force:on','baz','force:restore']);",(setting) => { if (setting === "on") { grunt.option("force", true); } else if (setting === "off") { grunt.option("force", false); } else if (setting === "restore") { grunt.option("force", previous_force_state); } }); } forceTaskHack(); function registerCustomTasks() { //grunt.registerTask("refresh-dependencies", ["clean:tsd", "tsd:refresh"]); grunt.registerTask("build-prod", ["tslint", "ts", "typedoc"]); grunt.registerTask("build-dev", ["force:on", "tslint", "force:restore", "ts", "typedoc"]); } registerCustomTasks(); }; export = __entryPoint;