{"id":303,"date":"2015-12-31T15:29:17","date_gmt":"2015-12-31T23:29:17","guid":{"rendered":"http:\/\/www.antunkarlovac.com\/blog\/?p=303"},"modified":"2015-12-31T15:29:17","modified_gmt":"2015-12-31T23:29:17","slug":"writing-a-grunt-plugin-that-uses-other-plugins","status":"publish","type":"post","link":"https:\/\/www.antunkarlovac.com\/blog\/2015\/12\/31\/writing-a-grunt-plugin-that-uses-other-plugins\/","title":{"rendered":"Writing a Grunt Plugin that Uses other Plugins"},"content":{"rendered":"<p>I&#8217;ve been\u00a0using <a href=\"http:\/\/gruntjs.com\">Grunt<\/a> for a while now, and wanted to package some reusable functionality from one project into a plugin, so that it could easily be used in other projects. For some reason, this turned out to be a lot less intuitive than I was expecting. I read the official page on <a href=\"http:\/\/gruntjs.com\/creating-plugins\">Creating Plugins<\/a>, but it doesn&#8217;t mention a variety of details you need to pay attention to if you want to encapsulate\u00a0other plugins within your own plugin.<\/p>\n<p>There are several gotchas. First, if you simply port the 3rd-party tasks&#8217; configurations to the plugin&#8217;s Gruntfile.js, your plugin will work when you test it (from the plugin source), but it will fail when you use your plugin, since the plugin&#8217;s Gruntfile doesn&#8217;t get processed when it is run as a plugin. You also have to explicitly load tasks you want to use, and be mindful of how you use paths to reference them.<\/p>\n<p>Here are my tips on writing plugins:<\/p>\n<ol>\n<li>Clone the\u00a0grunt-init gruntplugin repository and run grunt-init gruntplugin to setup the plugin template, as described in the <a href=\"http:\/\/gruntjs.com\/creating-plugins\">Grunt documentation on creating plugins<\/a>. This will be your <strong>plugin source<\/strong>. You&#8217;ll be able to run Grunt in there to test it.<\/li>\n<li>Right away, add your plugin to the <strong>project<\/strong> you wish to use it in, so that you can test it\u00a0<em>as a plugin<\/em>\u00a0while you develop. You don&#8217;t need to publish it, instead, in your project package.json, add it as a devDependency with a URL to its GIT repository:\n<pre><strong>myproject\/package.json<\/strong>\r\n\u00a0\u00a0\"devDependencies\": {\r\n\u00a0 \u00a0\u00a0\"grunt-myplugin\": \"git@bitbucket.org:antun\/grunt-myplugin.git\"\r\n\u00a0 }<\/pre>\n<p>As you make updates, you&#8217;ll need to commit\/push, then uninstall\/reinstall the plugin from your project as follows:<\/p>\n<pre>$ npm rm grunt-myplugin\r\n$ npm install<\/pre>\n<\/li>\n<li><strong>Add your 3rd-party plugins to your plugin source&#8217;s package file as dependencies<\/strong>, rather than devDependencies. This will ensure they&#8217;re installed under your project when your plugin is installed.\n<pre><strong>package.json<\/strong>\r\n\u00a0\u00a0\"dependencies\": {\r\n\u00a0 \u00a0\u00a0\"grunt-shell\" : \"1.1.1\"\r\n\u00a0 },<\/pre>\n<\/li>\n<li><strong>Explicitly load the 3rd party task you want to use<\/strong>, from your plugin source&#8217;s task file. This is necessary since your plugin won&#8217;t automatically have access to its own dependencies, when it&#8217;s used as a plugin in another project. Do this near the top of the task file. e.g. If you rely on the grunt-shell module within your plugin, would do:\n<pre><strong>tasks\/buttonimage.js<\/strong>\r\nmodule.exports = function(grunt) {\r\n\u00a0\u00a0var path = require('path');\r\n\u00a0 grunt.task.loadTasks(path.resolve(__dirname, '..\/node_modules\/grunt-shell\/tasks'));\r\n\u00a0 \/\/ load any other tasks, and task registration\r\n}<\/pre>\n<p>Note also that\u00a0I used the <a href=\"https:\/\/nodejs.org\/api\/path.html#path_path_resolve_from_to\">Node.js path.resolve()<\/a>\u00a0method to handle loading the URL relative to the plugin, no matter where it&#8217;s run from. Since path is a core Node.js module, you can require it without any special dependency handling.<\/li>\n<li><strong>Define your plugin&#8217;s custom task(s) in its source\u00a0Gruntfile<\/strong>. Organize the options as you would like to see them used when it is used as a plugin. Remember, this configuration will only be used when you run your plugin&#8217;s tasks from within the source.\n<pre><strong>Gruntfile.js<\/strong>\r\n\u00a0\u00a0grunt.initConfig({\r\n\u00a0 \u00a0\u00a0buttonimage: {\r\n\u00a0 \u00a0 \u00a0\u00a0testButton: {\u00a0\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0\u00a0output: 'dist\/testButton.gif'\r\n\u00a0 \u00a0 \u00a0 }\r\n\u00a0 \u00a0 }\r\n    \/* Other configuration, test, stuff *\/\r\n\u00a0 }<\/pre>\n<p>You can use\u00a0this.options() inside of your<\/li>\n<li><strong>Don&#8217;t bother adding configuration for\u00a0your 3rd-party tasks\u00a0in the plugin source Gruntfile<\/strong>. The source Gruntfile doesn&#8217;t get processed when you run your plugin in another project. Instead, set the configuration procedurally from within your task file using <a href=\"http:\/\/gruntjs.com\/api\/grunt.config#grunt.config.set\">grunt.config.set()<\/a>\u00a0before running the task. e.g.\n<pre><strong>tasks\/buttonimage.js<\/strong>\r\n\u00a0\u00a0grunt.registerMultiTask('buttonimage', 'Generate localized buttons with text labels.', function() {\r\n\u00a0 \u00a0 var cmd = 'some shell command you built up';\r\n\u00a0 \u00a0 grunt.config.set('shell', {\r\n\u00a0 \u00a0 \u00a0 \u00a0 buttonImage: {\r\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 command: cmd\r\n\u00a0 \u00a0 \u00a0 \u00a0 }\r\n\u00a0 \u00a0 \u00a0 }\r\n\u00a0 \u00a0 );\r\n\u00a0 \u00a0 grunt.task.run(['shell:buttonImage']);\r\n\u00a0 });<\/pre>\n<\/li>\n<li>Don&#8217;t change the working directory in your task, so work with absolute paths instead. <strong>Use path.resolve() to get an absolute path relative to the top-level Gruntfile<\/strong> for files that you will want to operate on. When\u00a0you define file paths in the Gruntfile, whether that&#8217;s the one in your plugin source config or eventually the one in the project where you will use your plugin, you will want to convert them to absolute paths. To do this, use the Node.js path.resolve() method to convert the input path &#8211; which is relative to the root Gruntfile &#8211; to an absolute path:\n<pre><strong>tasks\/buttonimage.js<\/strong>\r\n\u00a0\u00a0grunt.registerMultiTask('buttonimage', 'Generate localized buttons with text labels.', function() {\r\n\u00a0 \u00a0\u00a0var outputPath = path.resolve(this.options().output);\r\n\u00a0 \u00a0 var cmd = \"convert -some arguments \" + outputPath\r\n\u00a0 \u00a0 \/* Set the config and run your task *\/\r\n\u00a0 }<\/pre>\n<p>If you need to get an <strong>absolute path relative to your task definition<\/strong> (e.g. to run a Node.js script that you have bundled inside your plugin), you can use <a href=\"http:\/\/gruntjs.com\/api\/inside-tasks#this.options\">path.resolve(__dirname, &#8216;path\/relative\/to\/task\/file.js&#8217;)<\/a> to get that.<\/li>\n<\/ol>\n<p>Hopefully these will save you some time!<\/p>\n<!-- AddThis Advanced Settings generic via filter on the_content --><!-- AddThis Share Buttons generic via filter on the_content -->","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve been\u00a0using Grunt for a while now, and wanted to package some reusable functionality from one project into a plugin, so that it could easily be used in other projects. For some reason, this turned out to be a lot less intuitive than I was expecting. I read the official page on Creating Plugins, but &hellip; <a href=\"https:\/\/www.antunkarlovac.com\/blog\/2015\/12\/31\/writing-a-grunt-plugin-that-uses-other-plugins\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Writing a Grunt Plugin that Uses other Plugins<\/span><\/a><!-- AddThis Advanced Settings generic via filter on get_the_excerpt --><!-- AddThis Share Buttons generic via filter on get_the_excerpt --><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[14],"tags":[],"_links":{"self":[{"href":"https:\/\/www.antunkarlovac.com\/blog\/wp-json\/wp\/v2\/posts\/303"}],"collection":[{"href":"https:\/\/www.antunkarlovac.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.antunkarlovac.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.antunkarlovac.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.antunkarlovac.com\/blog\/wp-json\/wp\/v2\/comments?post=303"}],"version-history":[{"count":2,"href":"https:\/\/www.antunkarlovac.com\/blog\/wp-json\/wp\/v2\/posts\/303\/revisions"}],"predecessor-version":[{"id":306,"href":"https:\/\/www.antunkarlovac.com\/blog\/wp-json\/wp\/v2\/posts\/303\/revisions\/306"}],"wp:attachment":[{"href":"https:\/\/www.antunkarlovac.com\/blog\/wp-json\/wp\/v2\/media?parent=303"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.antunkarlovac.com\/blog\/wp-json\/wp\/v2\/categories?post=303"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.antunkarlovac.com\/blog\/wp-json\/wp\/v2\/tags?post=303"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}