├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── get-custom-webpack-config.js ├── index.js ├── logo.png ├── merge-configs.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 4, 4 | "printWidth": 100 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nathan Friend 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Logo 2 | 3 | # angular-cli-customizer 4 | A small command-line Node module that allows Angular CLI's Webpack config file to be customized 5 | 6 | ## Installation 7 | 8 | ```bash 9 | npm install @angular/cli -g 10 | npm install angular-cli-customizer -g 11 | ``` 12 | 13 | ## Usage 14 | 15 | 1. Define a `webpack.config.js` file at the root of your project containing the Webpack configuration settings you wish modify/add to the Angular CLI's build: 16 | 17 | ```javascript 18 | const path = require('path'); 19 | 20 | module.exports = { 21 | resolve: { 22 | alias: { 23 | 24 | // all upgraded AngularJS modules will now be forced to 25 | // use the same version of AngularJS, removing the 26 | // "Tried to load angular more than once." warning. 27 | angular: path.resolve(__dirname, 'node_modules/angular') 28 | } 29 | } 30 | }; 31 | ``` 32 | 33 | 2. Use the command line interface exposed by this module - `cng` (think "customized" `ng`) - exactly as you would with the `@angular/cli` module (`ng`): 34 | 35 | ```bash 36 | cng serve --open 37 | ``` 38 | 39 | ## API 40 | 41 | The `cng` command is simply a wrapper for the `ng` command, so its API is identical to the standard `ng` command! 42 | 43 | ## Why is this necessary? 44 | 45 | [Angular's CLI](https://cli.angular.io/) is a fantastic tool for generating and developing Angular applications. Unfortunately, it has one major flaw - at the time of writing, there is no way to customize or extend the CLI's stock functionality. Even though the Angular CLI is built on top of the infinitely configurable [Webpack](https://webpack.js.org/), the CLI doesn't allow its internal Webpack configuration to be altered. 46 | 47 | ## What does this module do? 48 | 49 | This module does some acrobatics (modifying the `require` function) to surgically replace the internal function (`buildConfig()`) that is responsible for generating the CLI's Webpack configuration object. The replacement function takes the result of the original `buildConfig()` call, merges in any changes defined in the custom `webpack.config.js` file, and returns the merged result. 50 | 51 | Other than that, the `cng` command is simply a wrapper for the `ng` command. 52 | 53 | ## How do I know what to modify? 54 | 55 | If you run [`ng eject`](https://github.com/angular/angular-cli/wiki/eject), the Angular CLI will create a `webpack.config.js` file at the root of your project's directory that contains the configuration it uses at runtime. You can use this file as a reference when designing your own `webpack.config.js` file that will augment this configuration. 56 | 57 | Note that running `ng eject` prevents the usage of some CLI features; to "uneject", delete the generated `webpack.config.js` and [flip the `ejected` property to `false` in your project's `.angular.cli.json`](https://stackoverflow.com/a/43026220/1063392). 58 | 59 | ## Isn't this a hack? 60 | 61 | Yep. It depends on internal implementation details of the CLI, which could change during any update of the `@angular/cli` package. Hopefully a better customization solution will be eventually be built into the CLI. 62 | 63 | 64 | -------------------------------------------------------------------------------- /get-custom-webpack-config.js: -------------------------------------------------------------------------------- 1 | var findup = require('findup'); 2 | var path = require('path'); 3 | var chalk = require('chalk'); 4 | 5 | /** 6 | * Gets the custom webpack.config.js from the file system. 7 | * If no webpack.config.js is found, undefined is returned. 8 | */ 9 | module.exports = function getCustomWebpackConfigs() { 10 | // get the path to the configuration file 11 | var configPath; 12 | try { 13 | var configDir = findup.sync(process.cwd(), 'webpack.config.js'); 14 | configPath = path.join(configDir, 'webpack.config.js'); 15 | } catch (err) { 16 | console.info( 17 | chalk.blue( 18 | 'No custom webpack.config.js file was found. ' + 19 | 'The default CLI build will not be customized.' 20 | ) 21 | ); 22 | return undefined; 23 | } 24 | 25 | // require the configuration file 26 | var customConfig; 27 | try { 28 | customConfig = require(configPath); 29 | } catch (err) { 30 | console.error( 31 | chalk.red( 32 | 'There was an issue require()-ing the custom ' + 33 | 'Webpack configuration file at "' + 34 | configPath + 35 | '". The default CLI build will not be customized.\n' 36 | ), 37 | err 38 | ); 39 | } 40 | 41 | return customConfig; 42 | }; 43 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var path = require('path'); 5 | var fs = require('fs'); 6 | var gmp = require('global-modules-path'); 7 | var yarnModulesPath = require('yarn-global-modules'); 8 | var chalk = require('chalk'); 9 | var mergeConfigs = require('./merge-configs'); 10 | var getCustomWebpackConfig = require('./get-custom-webpack-config'); 11 | 12 | // store a reference to the original require() function 13 | var Module = require('module'); 14 | var originalRequire = Module.prototype.require; 15 | 16 | // Get the custom Webpack configuration, if it exists 17 | var customWebpackConfig = getCustomWebpackConfig(); 18 | 19 | /** 20 | * Creates a function which calls the original "buildConfig" function 21 | * and merges the result with a custom configuration, if it exists. 22 | * @param {*} originalBuildConfigFn Angular CLI's original "buildConfig" 23 | * function which will be called during this function's execution 24 | */ 25 | function getCustomBuildConfigFn(originalBuildConfigFn) { 26 | return function customBuildConfig() { 27 | var buildConfigResult = originalBuildConfigFn.apply(this, arguments); 28 | return mergeConfigs(buildConfigResult, customWebpackConfig); 29 | }; 30 | } 31 | 32 | // Replace the global "require" function with one 33 | // that calls through to the original "require" function 34 | // in all cases but one - when the CLI is requesting the 35 | // class that is responsible for building and returning 36 | // its internal Webpack configuration object. Replace this 37 | // function with our own version that includes the 38 | // custom merging logic. 39 | // 40 | // Only do this if a custom Webpack config has been 41 | // defined, though. If not, leave the require() function alone. 42 | if (customWebpackConfig) { 43 | Module.prototype.require = function() { 44 | var result = originalRequire.apply(this, arguments); 45 | 46 | if (/webpack-config$/gi.test(arguments[0])) { 47 | if ( 48 | result && 49 | result['NgCliWebpackConfig'] && 50 | result['NgCliWebpackConfig'].prototype.buildConfig 51 | ) { 52 | var originalBuildConfig = result['NgCliWebpackConfig'].prototype.buildConfig; 53 | result['NgCliWebpackConfig'].prototype.buildConfig = getCustomBuildConfigFn( 54 | originalBuildConfig 55 | ); 56 | } 57 | } 58 | 59 | return result; 60 | }; 61 | } 62 | 63 | // Now that we have our custom require() function set up the way we want, 64 | // find the location of the Angular CLI's entry point and start the CLI 65 | var angularCliBasePath = gmp.getPath('@angular/cli'); 66 | if (!angularCliBasePath) { 67 | var candidatePath = path.join(yarnModulesPath(), 'node_modules/@angular/cli'); 68 | angularCliBasePath = fs.existsSync(candidatePath) && candidatePath; 69 | } 70 | if (!angularCliBasePath) { 71 | console.error( 72 | chalk.red( 73 | "Couldn't find the global installation of the Angular CLI... is it installed?\n" + 74 | 'You can install the CLI globally using "npm install @angular/cli -g"' 75 | ) 76 | ); 77 | } else { 78 | var angularCliPath = path.join(angularCliBasePath, 'bin/ng'); 79 | 80 | // start the CLI 81 | require(angularCliPath); 82 | } 83 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nfriend/angular-cli-customizer/3c132617b77db14498dcaa039d2a35fb3393b14e/logo.png -------------------------------------------------------------------------------- /merge-configs.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | /** 4 | * Merges two Webpack config objects together. 5 | * Note that merging can only add or modify properties, not remove. 6 | * @param {*} originalConfig The original Angular Webpack config 7 | * @param {*} customConfig The custom Webpack config to merge in 8 | */ 9 | module.exports = function mergeConfigs(originalConfig, customConfig) { 10 | // the causes arrays to be concatenated 11 | // instead of replaced index-by-index. 12 | // i.e. merging [1, 2] and [3] becomes 13 | // [1, 2, 3], not [3, 2]. 14 | // taken from https://lodash.com/docs/4.17.5#mergeWith 15 | function customizer(objValue, srcValue) { 16 | if (_.isArray(objValue)) { 17 | return objValue.concat(srcValue); 18 | } 19 | } 20 | 21 | return _.mergeWith({}, originalConfig, customConfig, customizer); 22 | }; 23 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-cli-customizer", 3 | "version": "1.0.5", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/chalk": { 8 | "version": "2.2.0", 9 | "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-2.2.0.tgz", 10 | "integrity": "sha512-1zzPV9FDe1I/WHhRkf9SNgqtRJWZqrBWgu7JGveuHmmyR9CnAPCie2N/x+iHrgnpYBIcCJWHBoMRv2TRWktsvw==", 11 | "requires": { 12 | "chalk": "2.4.1" 13 | } 14 | }, 15 | "@types/lodash": { 16 | "version": "4.14.106", 17 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.106.tgz", 18 | "integrity": "sha512-tOSvCVrvSqFZ4A/qrqqm6p37GZoawsZtoR0SJhlF7EonNZUgrn8FfT+RNQ11h+NUpMt6QVe36033f3qEKBwfWA==" 19 | }, 20 | "ansi-styles": { 21 | "version": "3.2.1", 22 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 23 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 24 | "requires": { 25 | "color-convert": "1.9.2" 26 | } 27 | }, 28 | "chalk": { 29 | "version": "2.4.1", 30 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", 31 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", 32 | "requires": { 33 | "ansi-styles": "3.2.1", 34 | "escape-string-regexp": "1.0.5", 35 | "supports-color": "5.4.0" 36 | } 37 | }, 38 | "color-convert": { 39 | "version": "1.9.2", 40 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", 41 | "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", 42 | "requires": { 43 | "color-name": "1.1.1" 44 | } 45 | }, 46 | "color-name": { 47 | "version": "1.1.1", 48 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", 49 | "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=" 50 | }, 51 | "colors": { 52 | "version": "0.6.2", 53 | "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", 54 | "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" 55 | }, 56 | "commander": { 57 | "version": "2.1.0", 58 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz", 59 | "integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E=" 60 | }, 61 | "escape-string-regexp": { 62 | "version": "1.0.5", 63 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 64 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 65 | }, 66 | "findup": { 67 | "version": "0.1.5", 68 | "resolved": "https://registry.npmjs.org/findup/-/findup-0.1.5.tgz", 69 | "integrity": "sha1-itkpozk7rGJ5V6fl3kYjsGsOLOs=", 70 | "requires": { 71 | "colors": "0.6.2", 72 | "commander": "2.1.0" 73 | } 74 | }, 75 | "global-modules-path": { 76 | "version": "2.1.0", 77 | "resolved": "https://registry.npmjs.org/global-modules-path/-/global-modules-path-2.1.0.tgz", 78 | "integrity": "sha512-3DrmGj2TP+96cABk9TfMp6f3knH/Y46dqvWznTU3Tf6/bDGLDAn15tFluQ7BcloykOcdY16U0WGq0BQblYOxJQ==" 79 | }, 80 | "has-flag": { 81 | "version": "3.0.0", 82 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 83 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 84 | }, 85 | "homedir-polyfill": { 86 | "version": "1.0.1", 87 | "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", 88 | "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", 89 | "requires": { 90 | "parse-passwd": "1.0.0" 91 | } 92 | }, 93 | "lodash": { 94 | "version": "4.17.5", 95 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", 96 | "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" 97 | }, 98 | "parse-passwd": { 99 | "version": "1.0.0", 100 | "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", 101 | "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" 102 | }, 103 | "supports-color": { 104 | "version": "5.4.0", 105 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 106 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 107 | "requires": { 108 | "has-flag": "3.0.0" 109 | } 110 | }, 111 | "yarn-config-directory": { 112 | "version": "1.0.2", 113 | "resolved": "https://registry.npmjs.org/yarn-config-directory/-/yarn-config-directory-1.0.2.tgz", 114 | "integrity": "sha512-vSeu9mdbkaQB1ivgQ+hgLWWlPGja+eHRMWe+KmBzNIkfCEqk12T4PKeR19PX/k00A84SqK83o7hCvWCq2iY6jg==", 115 | "requires": { 116 | "homedir-polyfill": "1.0.1" 117 | } 118 | }, 119 | "yarn-global-modules": { 120 | "version": "1.0.1", 121 | "resolved": "https://registry.npmjs.org/yarn-global-modules/-/yarn-global-modules-1.0.1.tgz", 122 | "integrity": "sha512-KoXxzwjZ65oCETpcd4sUnJZD6iUFrzo4k2p2a4C3h0OXOpjUvDnehNxA4jdmjAI3p4M6UFA+xoDtU8uN5u63Eg==", 123 | "requires": { 124 | "yarn-config-directory": "1.0.2" 125 | } 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-cli-customizer", 3 | "version": "1.0.5", 4 | "description": "A small command-line Node module that allows Angular CLI's Webpack config file to be customized", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "bin": { 10 | "cng": "./index.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/nfriend/angular-cli-customizer.git" 15 | }, 16 | "keywords": [ 17 | "angular", 18 | "cli", 19 | "webpack", 20 | "config", 21 | "customize", 22 | "edit", 23 | "extend" 24 | ], 25 | "author": "Nathan Friend", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/nfriend/angular-cli-customizer/issues" 29 | }, 30 | "homepage": "https://github.com/nfriend/angular-cli-customizer#readme", 31 | "dependencies": { 32 | "@types/chalk": "^2.2.0", 33 | "@types/lodash": "^4.14.106", 34 | "chalk": "^2.3.2", 35 | "findup": "^0.1.5", 36 | "global-modules-path": "^2.1.0", 37 | "lodash": "^4.17.5", 38 | "yarn-global-modules": "^1.0.1" 39 | } 40 | } 41 | --------------------------------------------------------------------------------