├── dist ├── js.js ├── js.min.js ├── style.min.css ├── style.css └── style.css.map ├── assets ├── style │ └── main.scss └── js │ └── manual-concat-file.js ├── bower.json ├── language ├── english │ ├── MYMODULE_lang.php │ └── index.html └── index.html ├── .idea ├── inspectionProfiles │ └── profiles_settings.xml ├── PerfexCRM-Module-Boilerplate.iml └── modules.xml ├── index.html ├── views ├── index.html └── module-view.php ├── examples ├── index.html └── KitchenSink.php ├── helpers └── index.html ├── libraries └── index.html ├── models └── index.html ├── controllers └── index.html ├── composer.json ├── webpack.config.js ├── package.json ├── PerfexCRM-Module-Boilerplate.php ├── .gitignore ├── README.md └── GruntFile.js /dist/js.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/js.min.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/style/main.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/js/manual-concat-file.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/style.min.css: -------------------------------------------------------------------------------- 1 | body{background:#00f} -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "perfex-crm-module-boilerplate" 3 | } -------------------------------------------------------------------------------- /dist/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: blue; } 3 | 4 | /*# sourceMappingURL=style.css.map */ -------------------------------------------------------------------------------- /language/english/MYMODULE_lang.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /helpers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /language/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /libraries/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /models/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /controllers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dist/style.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "style.css", 4 | "sources": [ 5 | "../assets/style/main.scss" 6 | ], 7 | "names": [], 8 | "mappings": "AAAA,AAAA,IAAI,CAAC;EACH,UAAU,EAAE,IAAI,GACjB" 9 | } -------------------------------------------------------------------------------- /language/english/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lunardevelopment/perfex-crm-module-boilerplate", 3 | "authors": [ 4 | { 5 | "name": "LunarDevelopment", 6 | "email": "development.lunar@gmail.com" 7 | } 8 | ], 9 | "require": {} 10 | } 11 | -------------------------------------------------------------------------------- /views/module-view.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/PerfexCRM-Module-Boilerplate.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019. 3 | * 4 | */ 5 | 6 | const path = require('path'); 7 | 8 | module.exports = { 9 | entry: './src/js/index.js', 10 | output: { 11 | filename: 'js/main.min.js', 12 | path: path.resolve(__dirname, 'assets') 13 | }, 14 | module: { 15 | rules: [{ 16 | test: /\.scss$/, 17 | use: [ 18 | "style-loader", // creates style nodes from JS strings 19 | "css-loader", // translates CSS into CommonJS 20 | "sass-loader" // compiles Sass to CSS, using Node Sass by default 21 | ] 22 | }] 23 | } 24 | }; 25 | 26 | 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PerfexCRM-Module-Boilerplate", 3 | "version": "1.0.0", 4 | "description": "A Module Boilerplate for PerfexCRM", 5 | "main": "index.js", 6 | "directories": { 7 | "example": "examples" 8 | }, 9 | "scripts": { 10 | "start": "node ./bin/www" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/LunarDevelopment/PerfexCRM-Module-Boilerplate.git" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/LunarDevelopment/PerfexCRM-Module-Boilerplate/issues" 21 | }, 22 | "homepage": "https://github.com/LunarDevelopment/PerfexCRM-Module-Boilerplate#readme", 23 | "devDependencies": { 24 | "autoprefixer": "^7.1.1", 25 | "grunt": "^1.3.0", 26 | "grunt-concurrent": "^2.3.1", 27 | "grunt-contrib-concat": "^1.0.1", 28 | "grunt-contrib-cssmin": "^2.0.0", 29 | "grunt-contrib-imagemin": "^1.0.1", 30 | "grunt-contrib-uglify": "^2.2.0", 31 | "grunt-contrib-watch": "^1.0.0", 32 | "grunt-pagespeed": "^2.0.1", 33 | "grunt-postcss": "^0.8.0", 34 | "grunt-sass": "^2.0.0", 35 | "imagemin-mozjpeg": "^5.1.0", 36 | "load-grunt-tasks": "^3.5.2", 37 | "pixrem": "^3.0.2", 38 | "time-grunt": "^1.4.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /PerfexCRM-Module-Boilerplate.php: -------------------------------------------------------------------------------- 1 | input->post(); 48 | $client_id = $this->input->post('client_id'); 49 | 50 | // Get data from GET request 51 | 52 | $data = $this->input->get(); 53 | $client_id = $this->input->get('client_id'); 54 | 55 | 56 | function example_database_function($data) 57 | { 58 | $CI = &get_instance(); 59 | $CI->db->where('id', $data['id']); 60 | $CI->db->update(db_prefix() . 'table_name', [ 61 | 'column' => $data['key'], 62 | ]); 63 | } 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ### macOS template 3 | # General 4 | .DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | ### JetBrains template 30 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 31 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 32 | 33 | # User-specific stuff: 34 | .idea/**/workspace.xml 35 | .idea/**/tasks.xml 36 | .idea/dictionaries 37 | 38 | # Sensitive or high-churn files: 39 | .idea/**/dataSources/ 40 | .idea/**/dataSources.ids 41 | .idea/**/dataSources.xml 42 | .idea/**/dataSources.local.xml 43 | .idea/**/sqlDataSources.xml 44 | .idea/**/dynamic.xml 45 | .idea/**/uiDesigner.xml 46 | 47 | # Gradle: 48 | .idea/**/gradle.xml 49 | .idea/**/libraries 50 | 51 | # CMake 52 | cmake-build-debug/ 53 | 54 | # Mongo Explorer plugin: 55 | .idea/**/mongoSettings.xml 56 | 57 | ## File-based project format: 58 | *.iws 59 | 60 | ## Plugin-specific files: 61 | 62 | # IntelliJ 63 | out/ 64 | 65 | # mpeltonen/sbt-idea plugin 66 | .idea_modules/ 67 | 68 | # JIRA plugin 69 | atlassian-ide-plugin.xml 70 | 71 | # Cursive Clojure plugin 72 | .idea/replstate.xml 73 | 74 | # Crashlytics plugin (for Android Studio and IntelliJ) 75 | com_crashlytics_export_strings.xml 76 | crashlytics.properties 77 | crashlytics-build.properties 78 | fabric.properties 79 | 80 | # Dependencies 81 | node_modules 82 | bower_components 83 | vendor 84 | 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PerfexCRM-Module-Boilerplate 2 | A Module Boilerplate for PerfexCRM 3 | 4 | ## References 5 | 6 | [Introduction to Modules](https://help.perfexcrm.com/introduction-to-perfex-crm-modules/) 7 | 8 | [Module Security](https://help.perfexcrm.com/module-security/) 9 | 10 | [Basics of Modules](https://help.perfexcrm.com/module-basics/) 11 | 12 | ## Need to know 13 | 14 | All modules should be added in the modules folder in your root directory where Perfex CRM is installed and each module must have unique folder name and init file with the same name as your module folder name. 15 | 16 | It’s very important to make sure that your module folder name and the .php file name are the same, otherwise, your module won’t be shown in the modules list. 17 | 18 | This boilerplate uses composer for dependency management and npm with 19 | 20 | ## Getting Started 21 | 22 | You can create a new module with this repo by running the following commands: 23 | 24 | ```bash 25 | # these are system wide and only required as a one off 26 | npm i -g node-sass 27 | npm i -g sassdoc 28 | npm i -g grunt-cli 29 | npm i -g bower 30 | 31 | git clone https://github.com/LunarDevelopment/PerfexCRM-Module-Boilerplate.git MYMODULENAME 32 | 33 | cd MYMODULENAME 34 | mv ./PerfexCRM-Module-Boilerplate.php ./MYMODULENAME.php 35 | mv ./language/english/MYMODULE_lang.php ./language/english/REPLACETHISBIT_lang.php 36 | 37 | npm install 38 | bower install 39 | composer install 40 | 41 | # Not needed but handy to know what bower is installing for you: 42 | # bower install --save-dev jQuery 43 | # bower install --save-dev normalize-css 44 | # bower install --save-dev font-awesome 45 | 46 | # Remember to update the doc block in ./MYMODULENAME.php with the name of your module and a description 47 | 48 | ``` 49 | 50 | ## Developing 51 | 52 | "Frontend", so to speak, is handled by grunt tasks - do not edit any files in dist/ but in stead work on your js, scss and place images in the assets/ path which will then be compiled with the below commands, either individually or as a group. It's handy to use the watch command while 53 | 54 | Executes the Sass -> CSS task: 55 | 56 | `grunt css` 57 | 58 | Executes the JS task: 59 | 60 | `grunt js` 61 | 62 | Executes the Image task: 63 | 64 | `grunt image` 65 | 66 | Executes Sass/CSS, JS and Images at once: 67 | 68 | `grunt build` 69 | 70 | File Watcher 71 | A File watcher will run the specified task/s when a file has been changed, you could simply start one with 72 | 73 | `grunt watch css` 74 | 75 | if you just want to let it run when you change a Sass (*.scss) file. 76 | 77 | Or let it watch for everything: 78 | 79 | `grunt watch` 80 | 81 | As all images will be changed everytime, depending on your deployment it could be that you should exclude images from the watchers. Also it could take a lot of time when you got a lot of pictures. So run `grunt image` when you are finished with handling images. 82 | 83 | 84 | -------------------------------------------------------------------------------- /examples/KitchenSink.php: -------------------------------------------------------------------------------- 1 | load->helper('module_name/helper_name'); 14 | $CI->load->library('module_name/library_name'); 15 | 16 | /* 17 | * Get data from POST request 18 | */ 19 | $data = $this->input->post(); 20 | $client_id = $this->input->post('client_id'); 21 | 22 | /* 23 | * Get data from GET request 24 | */ 25 | $data = $this->input->get(); 26 | $client_id = $this->input->get('client_id'); 27 | 28 | 29 | /* 30 | * Connect to the database with CI ORM 31 | */ 32 | function example_database_function($data) 33 | { 34 | $CI = &get_instance(); 35 | $CI->db->where('id', $data['id']); 36 | $CI->db->update(db_prefix() . 'table_name', [ 37 | 'column' => $data['key'], 38 | ]); 39 | } 40 | 41 | 42 | /* 43 | * Create a Table 44 | */ 45 | 46 | if (!$CI->db->table_exists(db_prefix() . 'new_table')) { 47 | $CI->db->query('CREATE TABLE `' . db_prefix() . "new_table` ( 48 | `id` INT(11) NOT NULL, 49 | `subject` VARCHAR(191) NOT NULL, 50 | `description` TEXT NOT NULL, 51 | `start_date` DATE NOT NULL, 52 | `end_date` DATE NOT NULL, 53 | `goal_type` INT(11) NOT NULL, 54 | `contract_type` INT(11) NOT NULL DEFAULT '0', 55 | `achievement` INT(11) NOT NULL, 56 | `notify_when_fail` TINYINT(1) NOT NULL DEFAULT '1', 57 | `notify_when_achieve` TINYINT(1) NOT NULL DEFAULT '1', 58 | `notified` INT(11) NOT NULL DEFAULT '0', 59 | `staff_id` INT(11) NOT NULL DEFAULT '0' 60 | ) ENGINE=InnoDB DEFAULT CHARSET=" . $CI->db->char_set . ';'); 61 | } 62 | 63 | 64 | /* 65 | * Options 66 | */ 67 | 68 | /** 69 | * Add a module option, the name must be unique amongst all modules. 70 | * @param string $name The name of the option to be added, make sure it’s unique and prefixed with E.q. your module name. 71 | * @param string $value Value to store. 72 | * @param string $autoload if you are using the option a lot in the views then autoload it. 73 | * @return mixed 74 | */ 75 | add_option($name, $value, $autoload); 76 | get_option($option_name); 77 | update_option($option_name, $new_value); 78 | 79 | 80 | /* 81 | * Available Hooks 82 | */ 83 | 84 | /** 85 | * Register module activation hook 86 | * @param string $module module system name 87 | * @param mixed $function function for the hook 88 | * @return mixed 89 | */ 90 | 91 | register_activation_hook($module, $function); 92 | 93 | /** 94 | * Register module deactivation hook 95 | * @param string $module module system name 96 | * @param mixed $function function for the hook 97 | * @return mixed 98 | */ 99 | 100 | register_deactivation_hook($module, $function); 101 | 102 | /** 103 | * Register module uninstall hook 104 | * @param string $module module system name 105 | * @param mixed $function function for the hook 106 | * @return mixed 107 | */ 108 | 109 | register_uninstall_hook($module, $function); 110 | 111 | /** 112 | * Register module cron task, the cron task is executed after the core cron tasks are finished 113 | * @param mixed $function function/class parameter for the hook 114 | * @return null 115 | */ 116 | 117 | register_cron_task($function); 118 | 119 | /** 120 | * Inject custom payment gateway into the payment gateways array 121 | * @param string $idpayment gateway id, should equal like the libraries/classname e.q. gateways/New_gateway 122 | * @param string $module module name to load the gateway if not already loaded 123 | */ 124 | 125 | register_payment_gateway($id, $module); 126 | 127 | /** 128 | * Register module language files to support custom_lang.php file 129 | * @param string $module module system name 130 | * @param array $languages array of language file names without the _lang.php 131 | * @return null 132 | */ 133 | 134 | register_language_files($module, $languages); 135 | 136 | /** 137 | * Module URL 138 | * e.q. https://crm-installation.com/module_name/ 139 | * @param string $module module system name 140 | * @param string $segment additional string to append to the URL 141 | * @return string 142 | */ 143 | 144 | module_dir_url($module, $segment = ''); 145 | 146 | /** 147 | * Module directory absolute path 148 | * @param string $module module system name 149 | * @param string $concat append additional string to the path 150 | * @return string 151 | */ 152 | 153 | module_dir_path($module, $concat = ''); 154 | 155 | /** 156 | * Module libraries path 157 | * e.q. modules/module_name/libraries 158 | * @param string $module module name 159 | * @param string $concat append additional string to the path 160 | * @return string 161 | */ 162 | 163 | module_libs_path($module, $concat = ''); 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /GruntFile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019. 3 | * 4 | */ 5 | 6 | module.exports = function(grunt) { 7 | // 8 | // Paths 9 | // 10 | 11 | /** 12 | * with trailing slash 13 | * bower_components/ (mostly) 14 | * @type {string} 15 | */ 16 | let path_bower = 'bower_components/'; 17 | /** 18 | * with trailing slash 19 | * vendor/ (mostly) 20 | * @type {string} 21 | */ 22 | let path_composer = 'vendor/'; 23 | 24 | /** 25 | * This folder will be watched and JS concated, mangled, minified 26 | * 27 | * with trailing slash 28 | * @type {string} 29 | */ 30 | let path_js_src_dir = 'assets/js/'; 31 | /** 32 | * The folder in which the JS output should be saved 33 | * 34 | * with trailing slash 35 | * @type {string} 36 | */ 37 | let path_js_build_dir = 'dist/'; 38 | 39 | /** 40 | * The main Sass file that should be transpiled, but: 41 | * 42 | * without extension 43 | * @type {string} 44 | */ 45 | let path_sass_src_file = 'assets/style/main'; 46 | /** 47 | * The folder where most sass files are located, will be used for the CSS file watcher 48 | * 49 | * with trailing slash 50 | * @type {string} 51 | */ 52 | let path_sass_src_dir = 'assets/style/'; 53 | /** 54 | * The folder in which the CSS should be saved 55 | * 56 | * with trailing slash 57 | * @type {string} 58 | */ 59 | let path_sass_build_dir = 'dist/'; 60 | /** 61 | * Name of the CSS file, but: 62 | * 63 | * without extension 64 | * @type {string} 65 | */ 66 | let path_sass_build_file = 'style'; 67 | 68 | /** 69 | * The source image folder, will be watched and all images optimized and copied into path_img_build 70 | * 71 | * with trailing slash 72 | * @type {string} 73 | */ 74 | let path_img_src = 'assets/media/'; 75 | /** 76 | * The folder in which the optimized images are saved 77 | * 78 | * with trailing slash 79 | * @type {string} 80 | */ 81 | let path_img_build = 'dist/media/'; 82 | 83 | // 84 | // JS concat 85 | // 86 | 87 | let js_concat = [ 88 | path_bower + 'jQuery/dist/jquery.min.js', 89 | path_js_src_dir + '**/*.js' 90 | ]; 91 | 92 | // 93 | // Options 94 | // 95 | 96 | /** 97 | * imagemin level of optimization for png and dynamic (svg|gif) 98 | * @type {number} 99 | */ 100 | let img_optimization_lvl = 3; 101 | /** 102 | * imagemin level of builded image quality for jpeg and dynamic (svg|gif) 103 | * @type {number} 104 | */ 105 | let img_quality_lvl = 90; 106 | 107 | // 108 | // Watcher 109 | // 110 | 111 | /** 112 | * The more files must be scanned the longer it takes, keep the list clean! 113 | * @type {[*]} 114 | */ 115 | let watch_css = [ 116 | path_sass_src_dir + '**/*.scss', 117 | '!**/node_modules/**', 118 | '!**/*.min.css' 119 | ]; 120 | /** 121 | * The more files must be scanned the longer it takes, keep the list clean! 122 | * @type {[*]} 123 | */ 124 | let watch_js = [ 125 | path_js_src_dir + '**/*.js', 126 | '!**/node_modules/**', 127 | '!**/*.min.js' 128 | ]; 129 | /** 130 | * The more files must be scanned the longer it takes, keep the list clean! 131 | * @type {[*]} 132 | */ 133 | let watch_img = [ 134 | path_img_src + '**/*.{gif,svg,png,jpg}', 135 | ]; 136 | 137 | require('time-grunt')(grunt); 138 | 139 | require('load-grunt-tasks')(grunt); 140 | 141 | grunt.initConfig({ 142 | pkg: grunt.file.readJSON('package.json'), 143 | 144 | // JS 145 | concat: { 146 | dist: { 147 | // warns when something was not found but was specified 148 | nonull: true, 149 | src: js_concat, 150 | dest: path_js_build_dir + 'js.js' 151 | } 152 | }, 153 | uglify: { 154 | build: { 155 | options: { 156 | sourceMap: true, 157 | mangle: { 158 | properties: true, 159 | toplevel: false, 160 | reserved: ['jQuery', 'jquery'] 161 | } 162 | }, 163 | src: path_js_build_dir + 'js.js', 164 | dest: path_js_build_dir + 'js.min.js' 165 | } 166 | }, 167 | 168 | // CSS 169 | sass: { 170 | options: { 171 | sourceMap: true 172 | }, 173 | dist: { 174 | files: { 175 | [path_sass_build_dir + path_sass_build_file + '.css']: path_sass_src_file + '.scss' 176 | } 177 | } 178 | }, 179 | cssmin: { 180 | target: { 181 | files: [{ 182 | expand: true, 183 | cwd: path_sass_build_dir, 184 | src: [path_sass_build_file + '.css', '!' + path_sass_build_file + '.css.map'], 185 | dest: path_sass_build_dir, 186 | ext: '.min.css' 187 | }] 188 | } 189 | }, 190 | postcss: { 191 | options: { 192 | map: false, 193 | processors: [ 194 | require('pixrem')(), // add fallbacks for rem units 195 | require('autoprefixer')({browsers: 'last 4 versions'}) 196 | ] 197 | }, 198 | dist: { 199 | src: path_sass_build_dir + path_sass_build_file + '.min.css' 200 | } 201 | }, 202 | 203 | // Image 204 | imagemin: { 205 | png: { 206 | options: { 207 | optimizationLevel: img_optimization_lvl 208 | }, 209 | files: [{ 210 | expand: true, 211 | cwd: path_img_src, 212 | src: ['**/*.png'], 213 | dest: path_img_build 214 | }] 215 | }, 216 | jpg: { 217 | options: { 218 | quality: img_quality_lvl, 219 | progressive: true, 220 | use: [require('imagemin-mozjpeg')()] 221 | }, 222 | files: [{ 223 | expand: true, 224 | cwd: path_img_src, 225 | src: ['**/*.jpg'], 226 | dest: path_img_build 227 | }] 228 | }, 229 | dynamic: { 230 | options: { 231 | optimizationLevel: img_optimization_lvl, 232 | quality: img_quality_lvl, 233 | svgoPlugins: [{removeViewBox: false}] 234 | }, 235 | files: [{ 236 | expand: true, 237 | cwd: path_img_src, 238 | src: ['**/*.{gif,svg}'], 239 | dest: path_img_build 240 | }] 241 | } 242 | }, 243 | 244 | // Multi Tasking 245 | concurrent: { 246 | image: ['imagemin:png', 'imagemin:jpg', 'imagemin:dynamic'], 247 | build: [['js'], ['css'], 'concurrent:image'] 248 | }, 249 | 250 | // JS and CSS/Sass file watcher 251 | watch: { 252 | css: { 253 | files: watch_css, 254 | tasks: ['css'] 255 | }, 256 | js: { 257 | files: watch_js, 258 | tasks: ['js'] 259 | }, 260 | image: { 261 | files: watch_img, 262 | tasks: ['image'] 263 | } 264 | } 265 | }); 266 | 267 | // Multi-Thread Task Runner 268 | grunt.loadNpmTasks('grunt-concurrent'); 269 | 270 | // JS 271 | grunt.registerTask('js', ['concat', 'uglify']); 272 | 273 | // SASS 274 | grunt.registerTask('css', ['sass', 'cssmin', 'postcss']); 275 | 276 | // Images 277 | grunt.registerTask('image', ['concurrent:image']); 278 | 279 | // Build All 280 | grunt.registerTask('build', ['concurrent:build']); 281 | }; 282 | --------------------------------------------------------------------------------