├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── Module.php ├── WebpackAssetBundle.php ├── commands └── WebpackController.php └── templates ├── package.json ├── tsconfig.json ├── webpack-yii2.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | tests/_output/* 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Yii2 Webpack Change Log 2 | ======================= 3 | 4 | 1.4.0 December 21, 2018 5 | ----------------------- 6 | 7 | * Enh: adding SRI 8 | * Enh: allow the developper to not add specific bundles (js/css) 9 | * Fix: fix problem with asset generator 10 | 11 | 1.3.0 May 15, 2018 12 | ------------------ 13 | 14 | * Enh: Make code compatible with PHP7.2 15 | 16 | 1.2.0 July 11, 2017 17 | ------------------- 18 | 19 | * Fix: Remove `DefinePlugin` to avoid collisions with Aurelia Bootstrapper 20 | * Enh: Adding `.gitattributes` 21 | 22 | 1.1.1 May 8, 2017 23 | ----------------- 24 | 25 | * Enh: Enable / disable cache system 26 | * Fix: Fix bad cache management 27 | * Enh: Add documentation to handle special cases (when a vendor module uses typescript) 28 | 29 | 1.1.0 April 7, 2017 30 | ------------------- 31 | 32 | * Enh: Allow use of separate `webpack-yii2.json` in `assets` directories 33 | * Enh: Allow `sass` and `scss` files 34 | * Fix: Fix `webpack-yii2.json` generation (create objects instead of arrays) 35 | * Enh: adding Webpack alias in `webpack-yii2.json` 36 | * Enh: adding Webpack externals in `webpack-yii2.json` 37 | 38 | 1.0.1 March 28, 2017 39 | -------------------- 40 | 41 | * Fix: change sample app id 42 | * Fix: correct sample app 43 | * Fix: add missing tsconfig.json 44 | 45 | 1.0.0 March 28, 2017 46 | -------------------- 47 | 48 | * Chg: Prepare documentation 49 | * Chg: Initial public release 50 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Sweelix Webpack Manager is free software. It is released under the terms of the following BSD License. 2 | 3 | Copyright 2010-2018 by Sweelix SAS 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | * Neither the name of Sweelix SAS nor the names of its contributors may be used 15 | to endorse or promote products derived from this software without specific 16 | prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Yii2 Webpack integration 2 | ======================== 3 | 4 | This extension allow the developper to use [Webpack 2](https://webpack.js.org/) as the asset manager. 5 | 6 | Webpack2 comes preconfigured with the following loaders 7 | * javascript 8 | * typescript 9 | * sass 10 | * less 11 | * css 12 | 13 | 14 | [![Latest Stable Version](https://poser.pugx.org/sweelix/yii2-webpack/v/stable)](https://packagist.org/packages/sweelix/yii2-webpack) 15 | [![License](https://poser.pugx.org/sweelix/yii2-webpack/license)](https://packagist.org/packages/sweelix/yii2-webpack) 16 | 21 | 22 | 23 | [![Latest Development Version](https://img.shields.io/badge/unstable-devel-yellowgreen.svg)](https://packagist.org/packages/sweelix/yii2-webpack) 24 | 29 | 30 | Installation 31 | ------------ 32 | 33 | If you use Packagist for installing packages, then you can update your `composer.json like this : 34 | 35 | ``` json 36 | { 37 | "require": { 38 | "sweelix/yii2-webpack": "*" 39 | } 40 | } 41 | ``` 42 | 43 | Warning 44 | ------- 45 | 46 | When `vendor` modules use typescript, typescript code can collide. In order to avoid this, you should update your `tsconfig.json` to exclude `vendor` modules 47 | 48 | ### Generic `tsconfig.json` 49 | 50 | ```json 51 | { 52 | "compilerOptions": { 53 | "sourceMap": true, 54 | "noImplicitAny": true, 55 | "module": "commonjs", 56 | "target": "es5", 57 | "jsx": "react", 58 | "allowJs": true 59 | } 60 | } 61 | ``` 62 | 63 | ### Amended `tsconfig.json` 64 | 65 | If `vendor` modules are in folder **vendor** you should update your `tsconfig.json` like this: 66 | 67 | ```json 68 | { 69 | "compilerOptions": { 70 | "sourceMap": true, 71 | "noImplicitAny": true, 72 | "module": "commonjs", 73 | "target": "es5", 74 | "jsx": "react", 75 | "allowJs": true 76 | }, 77 | "exclude": [ 78 | "node_modules", 79 | "vendor" 80 | ] 81 | } 82 | ``` 83 | 84 | Howto use it 85 | ------------ 86 | 87 | Add extension to your console configuration to enable CLI commands 88 | 89 | ``` php 90 | return [ 91 | // add webpack to console bootstrap to attach console controllers 92 | 'bootstrap' => ['log', 'webpack'], 93 | //.... 94 | 'modules' => [ 95 | 'webpack' => [ 96 | 'class' => 'sweelix\webpack\Module', 97 | ], 98 | // ... 99 | ], 100 | ]; 101 | ``` 102 | 103 | ### Generate everything from scratch (init webpack stuff) 104 | 105 | ``` 106 | php protected/yii webpack 107 | ``` 108 | 109 | * **Generating package.json** 110 | * **Relative path to composer.json ?** If you are in main directory, this is probably `composer.json` 111 | * **Application name ?** Application name which will be used in package.json 112 | * **Author ?** Your name 113 | * **Description ?** Description of package.json 114 | * **License ?** License of the application 115 | 116 | * **Generating webpack-yii2.json** 117 | * **Webpack assets path relative to package.json** This is where you will write your front app (`protected/assets/webpack` for example) 118 | * **Webpack assets source directory ?** Name of the directory (inside webpack assets relative path) where you will create sources `src` 119 | * **Webpack assets distribution directory ?** Name of the directory (inside webpack assets relative path) where bundles will be generated `dist` 120 | * **Webpack assets distribution scripts directory ?** Name of the directory (inside `dist`) where scripts will be stored (`js`) 121 | * **Webpack assets distribution styles directory ?** Name of the directory (inside `dist`) where styles will be stored (`css`) 122 | * **Webpack catalog filename ?** Name of catalog file which will allow the asset manager to find the bundles 123 | 124 | * **Generating webpack.config.js** 125 | 126 | if you need to regenerate one of the files, you can use the following CLI commands : 127 | 128 | * `php protected/yii webpack/generate-config` : regenerate `webpack-yii2.json` 129 | * `php protected/yii webpack/generate-package` : regenerate `package.json` 130 | * `php protected/yii webpack/generate-webpack` : regenerate `webpack.config.js` 131 | * `php protected/yii webpack/generate-generate-typescript-config` : regenerate `tsconfig.json` 132 | 133 | ### Sample application structure 134 | 135 | If your application has the following directory structure : 136 | 137 | * *index.php* 138 | * *composer.json* 139 | * **protected** 140 | * *yii* (console script) 141 | * **assets** 142 | * *WebpackAsset.php* 143 | * **webpack** 144 | * **src** 145 | * *app.ts* 146 | * **css** 147 | * *app.css* 148 | * *...* 149 | 150 | #### Run webpack init to prepare application 151 | 152 | The typical answer when running `php protected/yii webpack` would be : 153 | 154 | * **Generating package.json** 155 | * **Relative path to composer.json ?** Leave default value 156 | * **Application name ?** Leave default value 157 | * **Author ?** Leave default value 158 | * **Description ?** Leave default value 159 | * **License ?** Leave default value 160 | 161 | * **Generating webpack-yii2.json** 162 | * **Webpack assets path relative to package.json** `protected/assets/webpack` 163 | * **Webpack assets source directory ?** Leave default value 164 | * **Webpack assets distribution directory ?** Leave default value 165 | * **Webpack assets distribution scripts directory ?** Leave default value 166 | * **Webpack assets distribution styles directory ?** Leave default value 167 | * **Webpack catalog filename ?** Leave default value 168 | 169 | * **Generating webpack.config.js** 170 | 171 | * **Generating tsconfig.json** 172 | 173 | Application structure with generated files will be 174 | 175 | * *index.php* 176 | * *composer.json* 177 | * *package.json* 178 | * *webpack-yii2.json* 179 | * *webpack.config.js* 180 | * *tsconfig.json* 181 | * **protected** 182 | * *yii* (console script) 183 | * **assets** 184 | * *WebpackAsset.php* 185 | * **webpack** 186 | * *assets-catalog.json* -> generated by webpack 187 | * **dist** -> generated by webpack 188 | * **js** 189 | * *js bundles* 190 | * **css** 191 | * *css bundles* 192 | * **src** 193 | * *app.ts* 194 | * **css** 195 | * *app.css* 196 | * *...* 197 | 198 | #### Create Webpack aware asset 199 | 200 | ```php 201 | namespace app\assets; 202 | 203 | use sweelix\webpack\WebpackAssetBundle; 204 | 205 | class WebpackAsset extends WebpackAssetBundle 206 | { 207 | 208 | /** 209 | * @var bool enable caching system (default to false) 210 | */ 211 | public $cacheEnabled = false; 212 | 213 | /** 214 | * @var \yii\caching\Cache cache name of cache to use, default to `cache` 215 | */ 216 | public $cache = 'cache'; 217 | 218 | /** 219 | * @var string base webpack alias (do not add /src nor /dist, they are automagically handled) 220 | */ 221 | public $webpackPath = '@app/assets/webpack'; 222 | 223 | /** 224 | * @var array list of webpack bundles to publish (these are the entries from webpack) 225 | * the bundles (except for the manifest one which should be in first position) must be defined 226 | * in the webpack-yii2.json configuration file 227 | */ 228 | public $webpackBundles = [ 229 | 'manifest', 230 | 'app' 231 | ]; 232 | 233 | } 234 | ``` 235 | 236 | #### Generate the assets 237 | 238 | For development run 239 | 240 | ``` 241 | webpack 242 | ``` 243 | or to enable automatic build on file change 244 | ``` 245 | npm run watch 246 | ``` 247 | 248 | 249 | For production run 250 | 251 | ``` 252 | npm run dist-clean 253 | ``` 254 | 255 | #### Add files to your repository 256 | 257 | When your assets are ready, you have to make sure following files are added to your repository : 258 | 259 | * `package.json` node package management 260 | * `webpack.config.js` needed to run webpack 261 | * `webpack-yii2.json` needed by webpack.config.js to define you app entry points and the target directories 262 | * `tsconfig.json` needed by webpack.config.js to handle Typescript files 263 | 264 | * `/assets/webpack/assets-catalog.json` to let the webpack aware asset find the dist files 265 | * `/assets/webpack/dist` to keep the assets (they are not dynamically generated when asset is registered) 266 | * `/assets/webpack/src` because you want to keep your sources :-) 267 | 268 | 269 | ### File `webpack-yii2.json` explained 270 | 271 | ```json 272 | { 273 | "sourceDir": "protected\/assets\/webpack", 274 | "entry": { 275 | "app": "./app.ts" 276 | }, 277 | "commonBundles": [ 278 | "manifest" 279 | ], 280 | "externals": { 281 | 282 | }, 283 | "subDirectories": { 284 | "sources": "src", 285 | "dist": "dist" 286 | }, 287 | "assets": { 288 | "styles": "css", 289 | "scripts": "js" 290 | }, 291 | "sri": "sha256", 292 | "catalog": "assets-catalog.json" 293 | } 294 | ``` 295 | 296 | #### Specific to yii2-webpack module 297 | 298 | * **sourceDir** relative path to the directory where assets will be managed 299 | * **subDirectories** subpath (in *< sourceDir >*) of sources and distribution files 300 | * **assets** subpath (in *< sourceDir >/< subDirectories.dist >*) of styles and scripts assets 301 | * **catalog** name of assets catalog, must be in synch with `WebpackAssetBundle::$webpackAssetCatalog` 302 | 303 | #### Mapped to Webpack vars 304 | 305 | * **entry** object syntax entry points [Webpack entry documentation](https://webpack.js.org/concepts/entry-points/#object-syntax) 306 | * **commonBundles** explicit vendor chunk [Webpack CommonChunkPlugin documentation](https://webpack.js.org/plugins/commons-chunk-plugin/#explicit-vendor-chunk) 307 | * **externals** object syntax externals [Webpack externals documentation](https://webpack.js.org/configuration/externals/#object) 308 | * **alias** object syntax resolve.alias [Webpack resolve.alias documentation](https://webpack.js.org/configuration/resolve/#resolve-alias) 309 | 310 | 311 | ### Allow multiple `webpack-yii2.json` 312 | 313 | If your project needs multiple webpack configurations (take care of `manifest.js` collision), you can create the `webpack-yii2.json` directly in the assets directory. 314 | 315 | #### Example 316 | 317 | Instead of having `webpack-yii2.json` in root directory, you can put it in your assets directory. 318 | 319 | In this case, application structure should look like this: 320 | 321 | * *index.php* 322 | * *composer.json* 323 | * *package.json* 324 | * *webpack-yii2.json* 325 | * *webpack.config.js* 326 | * *tsconfig.json* 327 | * **protected** 328 | * *yii* (console script) 329 | * **assets** 330 | * *WebpackAsset.php* 331 | * **webpack** 332 | * *webpack-yii2.json* -> **Webpack specific file** 333 | * *assets-catalog.json* -> generated by webpack 334 | * **dist** -> generated by webpack 335 | * **js** 336 | * *js bundles* 337 | * **css** 338 | * *css bundles* 339 | * **src** 340 | * *app.ts* 341 | * **css** 342 | * *app.css* 343 | * *...* 344 | 345 | In order to run this specific configuration, 346 | 347 | For development run 348 | 349 | ``` 350 | webpack --env.config=protected/assets/webpack 351 | ``` 352 | or to enable automatic build on file change 353 | ``` 354 | webpack --watch --env.config=protected/assets/webpack 355 | ``` 356 | 357 | For production run 358 | 359 | ``` 360 | webpack -p --env.config=protected/assets/webpack 361 | ``` 362 | 363 | This will take the `webpack-yii2.json` which is in `protected/assets/webpack` everything else is unchanged 364 | 365 | Contributing 366 | ------------ 367 | 368 | All code contributions - including those of people having commit access - 369 | must go through a pull request and approved by a core developer before being 370 | merged. This is to ensure proper review of all the code. 371 | 372 | Fork the project, create a [feature branch ](http://nvie.com/posts/a-successful-git-branching-model/), and send us a pull request. 373 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sweelix/yii2-webpack", 3 | "description": "PHP 7.1+ Webpack integration for the Yii framework", 4 | "keywords": ["yii2", "webpack", "assets"], 5 | "type": "yii2-extension", 6 | "license": "BSD-3-Clause", 7 | "authors": [ 8 | { 9 | "name": "Philippe Gaultier", 10 | "email": "pgaultier@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "yiisoft/yii2": "~2.0.13", 15 | "php" : ">=7.1.0" 16 | }, 17 | "require-dev" : { 18 | "codeception/codeception": "~2.2" 19 | }, 20 | "autoload": { 21 | "psr-4": { "sweelix\\webpack\\": "src" } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Module.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2010-2017 Philippe Gaultier 9 | * @license http://www.sweelix.net/license license 10 | * @version 1.1.0 11 | * @link http://www.sweelix.net 12 | * @package sweelix\webpack 13 | */ 14 | 15 | namespace sweelix\webpack; 16 | 17 | use yii\base\BootstrapInterface; 18 | use yii\base\Module as BaseModule; 19 | use yii\console\Application as ConsoleApplication; 20 | 21 | 22 | /** 23 | * Webpack Module definition 24 | * 25 | * @author Philippe Gaultier 26 | * @copyright 2010-2017 Philippe Gaultier 27 | * @license http://www.sweelix.net/license license 28 | * @version 1.1.0 29 | * @link http://www.sweelix.net 30 | * @package sweelix\webpack 31 | * @since XXX 32 | */ 33 | class Module extends BaseModule implements BootstrapInterface 34 | { 35 | /** 36 | * @inheritdoc 37 | */ 38 | public function bootstrap($app) 39 | { 40 | if ($app instanceof ConsoleApplication) { 41 | $this->mapConsoleControllers($app); 42 | } 43 | } 44 | 45 | /** 46 | * Update controllers map to add console commands 47 | * @param ConsoleApplication $app 48 | * @since 1.0.0 49 | */ 50 | protected function mapConsoleControllers(ConsoleApplication $app) 51 | { 52 | $app->controllerMap['webpack'] = [ 53 | 'class' => 'sweelix\webpack\commands\WebpackController', 54 | ]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/WebpackAssetBundle.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2010-2017 Ibitux 9 | * @license http://www.ibitux.com/license license 10 | * @version 1.1.0 11 | * @link http://www.ibitux.com 12 | * @package sweelix\webpack 13 | */ 14 | namespace sweelix\webpack; 15 | 16 | use yii\caching\Cache; 17 | use yii\caching\FileDependency; 18 | use yii\di\Instance; 19 | use yii\helpers\Json; 20 | use yii\web\AssetBundle; 21 | use Exception; 22 | use Yii; 23 | 24 | /** 25 | * Base webpack assets manager 26 | * 27 | * @author Philippe Gaultier 28 | * @copyright 2010-2017 Ibitux 29 | * @license http://www.ibitux.com/license license 30 | * @version 1.1.0 31 | * @link http://www.ibitux.com 32 | * @package sweelix\webpack 33 | * @since 1.0.0 34 | */ 35 | class WebpackAssetBundle extends AssetBundle 36 | { 37 | /** 38 | * string base cache key 39 | */ 40 | const CACHE_KEY = 'webpack:bundles:'; 41 | 42 | /** 43 | * @var bool enable caching system 44 | */ 45 | public $cacheEnabled = false; 46 | 47 | /** 48 | * @var \yii\caching\Cache cache 49 | */ 50 | public $cache = 'cache'; 51 | 52 | /** 53 | * @var array list of bundles which are css only (to not add useless js in head) 54 | */ 55 | public $cssOnly = [ 56 | ]; 57 | 58 | /** 59 | * @var array list of bundles which are js only (to not add useless css in head) 60 | */ 61 | public $jsOnly = [ 62 | ]; 63 | 64 | /** 65 | * @var string name of webpack asset catalog, should be in synch with webpack.config.js 66 | */ 67 | public $webpackAssetCatalog = 'assets-catalog.json'; 68 | 69 | /** 70 | * @var string default dist directory, should be in synch with webpack.config.js 71 | */ 72 | public $webpackDistDirectory = 'dist'; 73 | 74 | /** 75 | * @var string base webpack alias 76 | */ 77 | public $webpackPath; 78 | 79 | /** 80 | * @var array list of webpack bundles to add 81 | */ 82 | public $webpackBundles = []; 83 | 84 | /** 85 | * Merge webpack bundles with classic bundles and cache it if needed 86 | * @return void 87 | * @since 1.0.0 88 | */ 89 | protected function mergeWebpackBundles() 90 | { 91 | try { 92 | if ((isset($this->webpackPath) === true) && (is_array($this->webpackBundles) === true)) { 93 | $cacheKey = self::CACHE_KEY . get_called_class(); 94 | $this->sourcePath = $this->webpackPath . '/' . $this->webpackDistDirectory; 95 | $cache = $this->getCache(); 96 | if (($cache === null) || ($cache->get($cacheKey) === false)) { 97 | $assetsFileAlias = $this->webpackPath . '/' . $this->webpackAssetCatalog; 98 | $bundles = file_get_contents(Yii::getAlias($assetsFileAlias)); 99 | $bundles = Json::decode($bundles); 100 | if ($cache !== null) { 101 | $cacheDependency = new FileDependency([ 102 | 'fileName' => $assetsFileAlias 103 | ]); 104 | $cache->set($cacheKey, $bundles, 0, $cacheDependency); 105 | } 106 | } else { 107 | $bundles = $cache->get($cacheKey); 108 | } 109 | foreach($this->webpackBundles as $bundle) { 110 | if (isset($bundles[$bundle]['js']) === true && in_array($bundle, $this->cssOnly) === false) { 111 | $this->js[] = $bundles[$bundle]['js']; 112 | } 113 | if (isset($bundles[$bundle]['css']) === true && in_array($bundle, $this->jsOnly) === false) { 114 | $this->css[] = $bundles[$bundle]['css']; 115 | } 116 | } 117 | } 118 | } catch(Exception $e) { 119 | Yii::error($e->getMessage(), 'sweelix\webpack'); 120 | throw $e; 121 | } 122 | } 123 | 124 | /** 125 | * @return null|Cache 126 | * @since XXX 127 | */ 128 | private function getCache() 129 | { 130 | if ($this->cacheEnabled === true) { 131 | $this->cache = Instance::ensure($this->cache, Cache::class); 132 | } 133 | return $this->cacheEnabled ? $this->cache : null; 134 | } 135 | 136 | /** 137 | * @inheritdoc 138 | */ 139 | public function init() 140 | { 141 | $this->mergeWebpackBundles(); 142 | parent::init(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/commands/WebpackController.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2010-2017 Philippe Gaultier 9 | * @license http://www.sweelix.net/license license 10 | * @version 1.1.0 11 | * @link http://www.sweelix.net 12 | * @package sweelix\webpack\commands 13 | */ 14 | 15 | namespace sweelix\webpack\commands; 16 | 17 | use yii\console\Controller; 18 | use yii\console\ExitCode; 19 | use yii\helpers\Console; 20 | use yii\helpers\Json; 21 | use Yii; 22 | 23 | /** 24 | * Manage assets through Webpack2 25 | * 26 | * @author Philippe Gaultier 27 | * @copyright 2010-2017 Philippe Gaultier 28 | * @license http://www.sweelix.net/license license 29 | * @version 1.1.0 30 | * @link http://www.sweelix.net 31 | * @package sweelix\webpack\commands 32 | * @since 1.0.0 33 | */ 34 | class WebpackController extends Controller 35 | { 36 | 37 | /** 38 | * @var string alias to package.json template file 39 | */ 40 | public $packageJson = '@sweelix/webpack/templates/package.json'; 41 | 42 | /** 43 | * @var string alias to webpack-yii2.json template file 44 | */ 45 | public $webpackConfigJson = '@sweelix/webpack/templates/webpack-yii2.json'; 46 | 47 | /** 48 | * @var string alias to webpack.config.js template file 49 | */ 50 | public $webpackConfigJs = '@sweelix/webpack/templates/webpack.config.js'; 51 | 52 | /** 53 | * @var string alias to tsconfig.json template file 54 | */ 55 | public $tsConfigJson = '@sweelix/webpack/templates/tsconfig.json'; 56 | 57 | /** 58 | * @var string relative path to composer.json 59 | */ 60 | protected $composerJsonPath; 61 | 62 | /** 63 | * @inheritdoc 64 | */ 65 | public $defaultAction = 'init'; 66 | 67 | /** 68 | * Initialize webpack for current project (webpack.config.js, package.json, webpack-yii2.json) 69 | * 70 | * @return int 71 | * @since 1.0.0 72 | */ 73 | public function actionInit() 74 | { 75 | 76 | $this->generatePackageJson(); 77 | $this->generateConfigJson(); 78 | $this->generateWebpackConfigJs(); 79 | $this->generateTsConfigJson(); 80 | $this->stdout('You can now configure your webpack assets with:'."\n", Console::BOLD, Console::FG_GREEN); 81 | $this->stdout("\t".' * '.pathinfo($this->webpackConfigJson, PATHINFO_BASENAME)."\n", Console::BOLD, Console::FG_GREEN); 82 | $this->stdout("\t".' * '.pathinfo($this->packageJson, PATHINFO_BASENAME)."\n", Console::BOLD, Console::FG_GREEN); 83 | $this->stdout('Install npm dependencies: npm install'."\n", Console::BOLD, Console::FG_GREEN); 84 | $this->stdout('Build assets: webpack'."\n", Console::BOLD, Console::FG_GREEN); 85 | return ExitCode::OK; 86 | } 87 | 88 | /** 89 | * Generate webpack.config.js file 90 | * 91 | * @return int 92 | * @since 1.0.0 93 | */ 94 | public function actionGenerateWebpack() 95 | { 96 | $this->generateWebpackConfigJs(); 97 | $this->stdout('Build assets: webpack'."\n", Console::BOLD, Console::FG_GREEN); 98 | return ExitCode::OK; 99 | } 100 | 101 | /** 102 | * Generate package.json file 103 | * 104 | * @return int 105 | * @since 1.0.0 106 | */ 107 | public function actionGeneratePackage() 108 | { 109 | $this->generatePackageJson(); 110 | $this->stdout('Install npm dependencies: npm install'."\n", Console::BOLD, Console::FG_GREEN); 111 | return ExitCode::OK; 112 | } 113 | 114 | /** 115 | * Generate webpack-yii2.json file 116 | * 117 | * @return int 118 | * @since 1.0.0 119 | */ 120 | public function actionGenerateConfig() 121 | { 122 | $this->generateConfigJson(); 123 | $this->stdout('Build assets with generated config: webpack'."\n", Console::BOLD, Console::FG_GREEN); 124 | return ExitCode::OK; 125 | } 126 | 127 | /** 128 | * Generate tsconfig.json file 129 | * 130 | * @return int 131 | * @since 1.0.1 132 | */ 133 | public function actionGenerateTypescriptConfig() 134 | { 135 | $this->generateTsConfigJson(); 136 | $this->stdout('Build typescript assets with generated config: webpack'."\n", Console::BOLD, Console::FG_GREEN); 137 | return ExitCode::OK; 138 | } 139 | 140 | /** 141 | * Prepare webpack.config.js file 142 | * @since 1.0.0 143 | */ 144 | protected function generateWebpackConfigJs() 145 | { 146 | $this->stdout('Generating webpack.config.js'."\n", Console::FG_GREEN, Console::BOLD); 147 | $composerJsonPath = $this->findComposerJson(); 148 | $webpackJs = file_get_contents(Yii::getAlias($this->webpackConfigJs)); 149 | $filename = pathinfo($this->webpackConfigJs, PATHINFO_BASENAME); 150 | $webpackConfigJsonFile = $composerJsonPath . '/' . $filename; 151 | if (file_exists($webpackConfigJsonFile) === true) { 152 | $question = $this->ansiFormat('Overwrite '.$filename.' ?', Console::FG_RED, Console::BOLD); 153 | $status = $this->confirm($question); 154 | if ($status === true) { 155 | file_put_contents($webpackConfigJsonFile, $webpackJs); 156 | } 157 | } else { 158 | file_put_contents($webpackConfigJsonFile, $webpackJs); 159 | } 160 | 161 | } 162 | 163 | /** 164 | * Prepare tsconfig.json file 165 | * @since 1.0.1 166 | */ 167 | protected function generateTsConfigJson() 168 | { 169 | $this->stdout('Generating tsconfig.json'."\n", Console::FG_GREEN, Console::BOLD); 170 | $composerJsonPath = $this->findComposerJson(); 171 | $webpackJs = file_get_contents(Yii::getAlias($this->tsConfigJson)); 172 | $filename = pathinfo($this->tsConfigJson, PATHINFO_BASENAME); 173 | $webpackConfigJsonFile = $composerJsonPath . '/' . $filename; 174 | if (file_exists($webpackConfigJsonFile) === true) { 175 | $question = $this->ansiFormat('Overwrite '.$filename.' ?', Console::FG_RED, Console::BOLD); 176 | $status = $this->confirm($question); 177 | if ($status === true) { 178 | file_put_contents($webpackConfigJsonFile, $webpackJs); 179 | } 180 | } else { 181 | file_put_contents($webpackConfigJsonFile, $webpackJs); 182 | } 183 | 184 | } 185 | 186 | /** 187 | * Prepare webpack config for yii 188 | * @since 1.0.0 189 | */ 190 | protected function generateConfigJson() 191 | { 192 | $this->stdout('Generating webpack-yii2.json'."\n", Console::FG_GREEN, Console::BOLD); 193 | $composerJsonPath = $this->findComposerJson(); 194 | 195 | $configJson = Json::decode(file_get_contents(Yii::getAlias($this->webpackConfigJson))); 196 | $sourceDir = $this->prompt('Webpack assets path relative to package.json file ?', [ 197 | 'required' => true 198 | ]); 199 | $configJson['sourceDir'] = $sourceDir; 200 | 201 | $sourceSubDir = $this->prompt('Webpack assets source directory ?', [ 202 | 'required' => true, 203 | 'default' => $configJson['subDirectories']['sources'], 204 | ]); 205 | $configJson['subDirectories']['sources'] = $sourceSubDir; 206 | 207 | $distSubDir = $this->prompt('Webpack assets distribution directory ?', [ 208 | 'required' => true, 209 | 'default' => $configJson['subDirectories']['dist'], 210 | ]); 211 | $configJson['subDirectories']['dist'] = $distSubDir; 212 | 213 | $jsSubDir = $this->prompt('Webpack assets distribution scripts directory ?', [ 214 | 'required' => true, 215 | 'default' => $configJson['assets']['scripts'], 216 | ]); 217 | $configJson['assets']['scripts'] = $jsSubDir; 218 | 219 | $cssSubDir = $this->prompt('Webpack assets distribution styles directory ?', [ 220 | 'required' => true, 221 | 'default' => $configJson['assets']['styles'], 222 | ]); 223 | $configJson['assets']['styles'] = $cssSubDir; 224 | 225 | $catalogFilename = $this->prompt('Webpack catalog filename ?', [ 226 | 'required' => true, 227 | 'default' => $configJson['catalog'], 228 | ]); 229 | $configJson['catalog'] = $catalogFilename; 230 | 231 | $configJson['entry'] = (object)[]; 232 | $configJson['externals'] = (object)[]; 233 | $configJson['alias'] = (object)[]; 234 | $filename = pathinfo($this->webpackConfigJson, PATHINFO_BASENAME); 235 | $webpackConfigJsonFile = $composerJsonPath . '/' . $filename; 236 | if (file_exists($webpackConfigJsonFile) === true) { 237 | $question = $this->ansiFormat('Overwrite '.$filename.' ?', Console::FG_RED, Console::BOLD); 238 | $status = $this->confirm($question); 239 | if ($status === true) { 240 | file_put_contents($webpackConfigJsonFile, Json::encode($configJson, JSON_PRETTY_PRINT)); 241 | } 242 | } else { 243 | file_put_contents($webpackConfigJsonFile, Json::encode($configJson, JSON_PRETTY_PRINT)); 244 | } 245 | 246 | } 247 | 248 | /** 249 | * Search composer.json 250 | * @return string 251 | * @since 1.0.0 252 | */ 253 | protected function findComposerJson() 254 | { 255 | if ($this->composerJsonPath === null) { 256 | $options = [ 257 | 'required' => true, 258 | 'validator' => function($input, &$error) { 259 | if (file_exists($input) === false) { 260 | $error = 'The composer.json file must exist!'; 261 | return false; 262 | } 263 | return true; 264 | } 265 | ]; 266 | if (file_exists('composer.json') === true) { 267 | $options['default'] = 'composer.json'; 268 | } 269 | $composerJson = $this->prompt('Relative path to composer.json ?', $options); 270 | $composerPath = pathinfo($composerJson, PATHINFO_DIRNAME); 271 | $this->composerJsonPath = $composerPath; 272 | } 273 | return $this->composerJsonPath; 274 | 275 | } 276 | 277 | /** 278 | * Search information from composer.json and generate package.json 279 | * @since 1.0.0 280 | */ 281 | protected function generatePackageJson() 282 | { 283 | $this->stdout('Generating package.json'."\n", Console::FG_GREEN, Console::BOLD); 284 | $composerJsonPath = $this->findComposerJson(); 285 | $composerData = Json::decode(file_get_contents($composerJsonPath.'/composer.json')); 286 | 287 | $packageJson = Json::decode(file_get_contents(Yii::getAlias($this->packageJson))); 288 | 289 | $options = [ 290 | 'required' => true, 291 | ]; 292 | if (isset($composerData['name']) === true) { 293 | $options['default'] = str_replace('/', '-', $composerData['name']); 294 | } 295 | $appName = $this->prompt('Application name ?', $options); 296 | $packageJson['name'] = $appName; 297 | 298 | $options = [ 299 | 'required' => true, 300 | ]; 301 | if (isset($composerData['authors'][0]) === true) { 302 | $name = []; 303 | if (isset($composerData['authors'][0]['name'])) { 304 | $name[] = $composerData['authors'][0]['name']; 305 | } 306 | if (isset($composerData['authors'][0]['email'])) { 307 | $name[] = '<'.$composerData['authors'][0]['email'].'>'; 308 | } 309 | if (empty($name) === false) { 310 | $options['default'] = implode(' ', $name); 311 | } 312 | } 313 | $author = $this->prompt('Author ?', $options); 314 | $packageJson['author'] = $author; 315 | 316 | $options = []; 317 | if (isset($composerData['description']) === true) { 318 | $options['default'] = $composerData['description']; 319 | } 320 | $description = $this->prompt('Description ?', $options); 321 | $packageJson['description'] = $description; 322 | 323 | 324 | $options = []; 325 | if (isset($composerData['license']) === true) { 326 | $options['default'] = $composerData['license']; 327 | } 328 | $license = $this->prompt('License ?', $options); 329 | $packageJson['license'] = $license; 330 | 331 | $filename = pathinfo($this->packageJson, PATHINFO_BASENAME); 332 | $packageJsonFile = $composerJsonPath . '/' . $filename; 333 | if (file_exists($packageJsonFile) === true) { 334 | $question = $this->ansiFormat('Overwrite '.$filename.' ?', Console::FG_RED, Console::BOLD); 335 | $status = $this->confirm($question); 336 | if ($status === true) { 337 | file_put_contents($packageJsonFile, Json::encode($packageJson, JSON_PRETTY_PRINT)); 338 | } 339 | } else { 340 | file_put_contents($packageJsonFile, Json::encode($packageJson, JSON_PRETTY_PRINT)); 341 | } 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /src/templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ibitux-yii2-template", 3 | "version": "1.0.0", 4 | "description": "Sweelix webpack", 5 | "keywords": [ 6 | "sweelix", 7 | "webpack", 8 | "saas" 9 | ], 10 | "scripts": { 11 | "watch": "webpack --mode development --progress -w", 12 | "build": "webpack --mode development --progress", 13 | "dist": "webpack --mode production --progress", 14 | "dist-clean": "webpack --mode production --progress --env.cleanup" 15 | }, 16 | "author": "Philippe Gaultier", 17 | "license": "Proprietary", 18 | "dependencies": { 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^9.6.39", 22 | "assets-webpack-plugin": "^3.5.1", 23 | "clean-webpack-plugin": "^0.1.19", 24 | "compression-webpack-plugin": "^1.1.11", 25 | "css-loader": "^0.28.10", 26 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 27 | "file-loader": "^1.1.11", 28 | "jshashes": "^1.0.7", 29 | "node-sass": "^4.7.2", 30 | "sass-loader": "^6.0.7", 31 | "source-map-loader": "^0.2.3", 32 | "style-loader": "^0.20.3", 33 | "ts-loader": "^4.1.0", 34 | "typescript": "^2.7.2", 35 | "webpack": "^4.19.1", 36 | "webpack-cli": "^3.1.0", 37 | "yargs": "^11.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/templates/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "declaration": false, 5 | "experimentalDecorators": true, 6 | "jsx": "react", 7 | "module": "commonjs", 8 | "noImplicitAny": true, 9 | "sourceMap": true, 10 | "strictNullChecks": true, 11 | "target": "es5" 12 | }, 13 | "exclude": [ 14 | "node_modules", 15 | "vendor" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/templates/webpack-yii2.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceDir": "protected/assets/webpack", 3 | "entry": { 4 | 5 | }, 6 | "commonBundles": [ 7 | "manifest" 8 | ], 9 | "externals": { 10 | 11 | }, 12 | "alias": { 13 | 14 | }, 15 | "subDirectories": { 16 | "sources": "src", 17 | "dist": "dist" 18 | }, 19 | "assets": { 20 | "styles": "css", 21 | "scripts": "js" 22 | }, 23 | "sri": "sha256", 24 | "catalog": "assets-catalog.json" 25 | } 26 | -------------------------------------------------------------------------------- /src/templates/webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * webpack.config.js 3 | * 4 | * @author Philippe Gaultier 5 | * @copyright 2010-2018 Redcat 6 | * @license http://www.redcat.io/license license 7 | * @version XXX 8 | * @link http://www.redcat.io 9 | */ 10 | 11 | const argv = require('yargs').argv; 12 | const webpack = require('webpack'); 13 | const path = require('path'); 14 | const fs = require('fs'); 15 | const AssetsWebpackPlugin = require('assets-webpack-plugin'); 16 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 17 | const CompressionWebpackPlugin = require('compression-webpack-plugin'); 18 | const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin'); 19 | const Hashes = require('jshashes'); 20 | 21 | const prodFlag = (process.argv.indexOf('-p') !== -1) || (process.argv.indexOf('production') !== -1); 22 | 23 | var confPath = './webpack-yii2.json'; 24 | if(argv.env && argv.env.config) { 25 | confPath = path.join(__dirname, argv.env.config, 'webpack-yii2.json'); 26 | } 27 | if(!fs.existsSync(confPath)) { 28 | throw 'Error: file "' + confPath + '" not found.'; 29 | } 30 | var version = '1.0.0'; 31 | 32 | var config = require(confPath); 33 | if (argv.env && argv.env.config) { 34 | config.sourceDir = path.relative(__dirname, argv.env.config); 35 | } 36 | 37 | var webpackConfig = { 38 | entry: config.entry, 39 | context: path.resolve(__dirname, config.sourceDir, config.subDirectories.sources), 40 | output: { 41 | path: path.resolve(__dirname, config.sourceDir, config.subDirectories.dist), 42 | filename: prodFlag ? config.assets.scripts + '/[name].[chunkhash:6].js' : config.assets.scripts + '/[name].js' 43 | }, 44 | plugins: [ 45 | new webpack.DefinePlugin({ 46 | PRODUCTION: JSON.stringify(prodFlag), 47 | VERSION: JSON.stringify(prodFlag ? version : version + '-dev'), 48 | }), 49 | new ExtractTextWebpackPlugin({ 50 | filename: function(getPath) { 51 | return getPath(prodFlag ? config.assets.styles + '/[name].[hash:6].css' : config.assets.styles + '/[name].css'); 52 | }, 53 | allChunks: true 54 | }), 55 | new CompressionWebpackPlugin({ 56 | asset: "[path].gz[query]", 57 | algorithm: "gzip", 58 | test: /\.(js|css)/, 59 | threshold: 10, 60 | minRatio: 1 61 | }), 62 | new CleanWebpackPlugin([config.subDirectories.dist], { 63 | root: path.resolve(__dirname, config.sourceDir), 64 | verbose: true, 65 | dry: false, 66 | exclude: [] 67 | }), 68 | new AssetsWebpackPlugin({ 69 | prettyPrint: true, 70 | filename: config.catalog, 71 | path:config.sourceDir, 72 | processOutput: function (assets) { 73 | let i; 74 | let j; 75 | let finalAsset = {}; 76 | for (i in assets) { 77 | if(assets.hasOwnProperty(i)) { 78 | if (finalAsset.hasOwnProperty(i) === false) { 79 | finalAsset[i] = {}; 80 | } 81 | for (j in assets[i]) { 82 | if (assets[i].hasOwnProperty(j)) { 83 | let currentAsset = assets[i][j]; 84 | if (Array.isArray(currentAsset) === true) { 85 | for (let c = 0; c < currentAsset.length; c++) { 86 | if ((typeof currentAsset[c] !== 'string') && (currentAsset[c].file)) { 87 | currentAsset[c] = currentAsset[c].file; 88 | } 89 | if (config.hasOwnProperty('sri') === true && config.sri !== false) { 90 | let file = path.resolve(__dirname, config.sourceDir, config.subDirectories.dist, currentAsset[c]); 91 | let contents = fs.readFileSync(file).toString(); 92 | let hash; 93 | switch (config.sri) { 94 | case 'sha256': 95 | hash = 'sha256-' + new Hashes.SHA256().b64(contents); 96 | break; 97 | case 'sha512': 98 | default: 99 | hash = 'sha512-' + new Hashes.SHA512().b64(contents); 100 | break; 101 | } 102 | 103 | finalAsset[i][j] = { 104 | file: currentAsset[c].replace('\\', '/'), 105 | integrity: hash 106 | }; 107 | } else { 108 | finalAsset[i][j] = currentAsset[c].replace('\\', '/'); 109 | } 110 | 111 | } 112 | } else { 113 | if ((typeof currentAsset !== 'string') && (currentAsset.file)) { 114 | currentAsset = currentAsset.file; 115 | } 116 | if (config.hasOwnProperty('sri') === true && config.sri !== false) { 117 | let file = path.resolve(__dirname, config.sourceDir, config.subDirectories.dist, currentAsset); 118 | let contents = fs.readFileSync(file).toString(); 119 | let hash; 120 | switch (config.sri) { 121 | case 'sha256': 122 | hash = 'sha256-' + new Hashes.SHA256().b64(contents); 123 | break; 124 | case 'sha512': 125 | default: 126 | hash = 'sha512-' + new Hashes.SHA512().b64(contents); 127 | break; 128 | } 129 | 130 | finalAsset[i][j] = { 131 | file: currentAsset.replace('\\', '/'), 132 | integrity: hash 133 | }; 134 | } else { 135 | finalAsset[i][j] = currentAsset.replace('\\', '/'); 136 | } 137 | } 138 | } 139 | } 140 | } 141 | } 142 | return JSON.stringify(finalAsset, null, this.prettyPrint ? 2 : null); 143 | } 144 | }) 145 | ], 146 | externals: config.externals, 147 | module: { 148 | rules: [ 149 | { 150 | enforce: 'pre', 151 | test: /\.js$/, 152 | loader: 'source-map-loader' 153 | }, 154 | { 155 | enforce: 'pre', 156 | test: /\.tsx?$/, 157 | use: 'source-map-loader' 158 | }, 159 | { 160 | test: /\.tsx?$/, 161 | loader: 'ts-loader', 162 | exclude: /node_modules/ 163 | }, 164 | { 165 | test: /\.(ttf|eot|svg|woff|woff2)(\?[a-z0-9]+)?$/, 166 | loader: 'file-loader', 167 | options: { 168 | name: '[path][name].[ext]' 169 | } 170 | }, 171 | { 172 | test: /\.(jpg|png|gif)$/, 173 | loader: 'file-loader', 174 | options: { 175 | name: '[path][name].[ext]' 176 | } 177 | }, 178 | { 179 | test: /\.s[ac]ss$/, 180 | use: ExtractTextWebpackPlugin.extract({ 181 | publicPath: '../', 182 | fallback: 'style-loader', 183 | use: ['css-loader', 'sass-loader'] 184 | }) 185 | }, 186 | { 187 | test: /\.css$/, 188 | use: ExtractTextWebpackPlugin.extract({ 189 | publicPath: '../', 190 | fallback: 'style-loader', 191 | use: ['css-loader'] 192 | }) 193 | } 194 | ] 195 | }, 196 | optimization: { 197 | runtimeChunk: { 198 | name: "manifest" 199 | }, 200 | splitChunks: { 201 | cacheGroups: { 202 | commons: { 203 | test: /[\\/]node_modules[\\/]/, 204 | name: "vendor", 205 | chunks: "all" 206 | } 207 | } 208 | } 209 | }, 210 | resolve: { 211 | alias: config.alias, 212 | extensions: ['.tsx', '.ts', '.js'] 213 | }, 214 | target: 'web' 215 | }; 216 | 217 | if (!prodFlag) { 218 | webpackConfig.devtool = 'source-map'; 219 | 220 | } 221 | 222 | module.exports = webpackConfig; 223 | --------------------------------------------------------------------------------