6

I have reproduced my issue at https://github.com/franklin626/custom_webpack_undebuggable.

Starting with a standard Angular 9 CLI application, I had a need to customize the webpack build so that my SCSS files can import JSON configs. This means, in angular.json, moving from

"serve": {
      "builder": "@angular-devkit/build-angular:dev-server"

to

"serve": {
      "builder": "@angular-builders/custom-webpack:dev-server",
      "options": {
        "customWebpackConfig": {
          "path": "webpack.config.js"
        }
       ...

With webpack.config.js containing the following:

const jsonImporter = require('node-sass-json-importer');

module.exports = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.scss$|\.sass$/,
        use: [
          {
            loader: require.resolve('sass-loader'),
            options: {
              implementation: require('node-sass'),
              sassOptions: {
                // bootstrap-sass requires a minimum precision of 8
                precision: 8,
                importer: jsonImporter(),
                outputStyle: 'expanded'
              }
            }
          }
        ]
      }
    ]
  }
};

I am only getting the source maps for the css, but not for the javascript. And yes, my Chrome has JS and CSS maps enabled. Not sure what is going on here ?

angular.json:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "my-web-app": {
      "projectType": "application",
      "schematics": {
        "@schematics/angular:component": {
          "style": "scss"
        }
      },
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-builders/custom-webpack:browser",
          "options": {
            "customWebpackConfig": {
              "path": "./webpack.deploy.config.js",
              "replaceDuplicatePlugins": true
            },
            "outputPath": "dist/my-web-app",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "aot": true,
            "assets": ["src/favicon.ico", "src/assets"],
            "styles": ["src/styles.scss"],
            "scripts": []
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb",
                  "maximumError": "10kb"
                }
              ]
            }
          }
        },
        "serve": {
          "builder": "@angular-builders/custom-webpack:dev-server",
          "options": {
            "customWebpackConfig": {
              "path": "webpack.config.js",
              "sourceMap": true
            },
            "browserTarget": "my-web-app:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "my-web-app:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "my-web-app:build"
          }
        },
        "test": {
          "builder": "@angular-builders/custom-webpack:karma",
          "options": {
            "customWebpackConfig": {
              "path": "webpack.config.js"
            },
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "assets": ["src/favicon.ico", "src/assets"],
            "styles": ["src/styles.scss"],
            "scripts": []
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": ["tsconfig.app.json", "tsconfig.spec.json", "e2e/tsconfig.json"],
            "exclude": ["**/node_modules/**"]
          }
        },
        "e2e": {
          "builder": "@angular-builders/custom-webpack:protractor",
          "options": {
            "customWebpackConfig": {
              "path": "webpack.config.js"
            },
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "my-web-app:serve"
          },
          "configurations": {
            "production": {
              "devServerTarget": "my-web-app:serve:production"
            }
          }
        }
      }
    }
  },
  "defaultProject": "my-web-app"
}

package.json

{
  "name": "my-web-app",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "test-headless": "ng test --watch=false --browsers=ChromeHeadless",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "doc": "node src/scripts/runMarked.js",
    "prestart": "npm run doc",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook"
  },
  "dependencies": {
    "@angular/animations": "^9.1.4",
    "@angular/cdk": "^9.0.0",
    "@angular/common": "^9.1.4",
    "@angular/compiler": "^9.1.4",
    "@angular/core": "^9.1.4",
    "@angular/forms": "^9.1.4",
    "@angular/material": "^9.0.0",
    "@angular/platform-browser": "^9.1.4",
    "@angular/platform-browser-dynamic": "^9.1.4",
    "@angular/router": "^9.1.4",
    "@azure/msal-angular": "^1.0.0-beta.3",
    "@types/vega": "^3.2.0",
    "build": "^0.1.4",
    "d3": "^5.15.0",
    "karma-viewport": "^1.0.5",
    "marked": "^0.8.0",
    "msal": "^1.2.2-beta.0",
    "ng": "0.0.0",
    "ngx-spinner": "^9.0.2",
    "rxjs": "~6.5.4",
    "tslib": "^1.10.0",
    "vega": "^5.9.1",
    "vega-embed": "^6.2.2",
    "vega-lite": "^4.4.0",
    "vega-typings": "^0.12.4",
    "zone.js": "~0.10.2"
  },
  "devDependencies": {
    "@angular-builders/custom-webpack": "^9.0.0",
    "@angular-devkit/build-angular": "^0.901.0",
    "@angular/cli": "^9.1.4",
    "@angular/compiler-cli": "^9.1.4",
    "@angular/language-service": "^9.1.4",
    "@babel/core": "^7.9.0",
    "@storybook/addon-actions": "^5.3.18",
    "@storybook/addon-links": "^5.3.18",
    "@storybook/addon-notes": "^5.3.18",
    "@storybook/addons": "^5.3.18",
    "@storybook/angular": "^5.3.18",
    "@types/jasmine": "~3.5.0",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "^12.11.1",
    "babel-loader": "^8.1.0",
    "codelyzer": "^5.1.2",
    "compression-webpack-plugin": "^3.1.0",
    "jasmine-core": "~3.5.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~4.3.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage-istanbul-reporter": "~2.1.0",
    "karma-jasmine": "~2.0.1",
    "karma-jasmine-html-reporter": "^1.4.2",
    "karma-junit-reporter": "^2.0.1",
    "minimist": "^1.2.0",
    "node-sass": "^4.13.1",
    "node-sass-json-importer": "^4.1.2",
    "prettier": "1.19.1",
    "protractor": "~5.4.3",
    "terser-webpack-plugin": "^2.3.5",
    "ts-node": "~8.3.0",
    "tslint": "~5.18.0",
    "tslint-config-prettier": "^1.18.0",
    "typescript": "~3.7.5"
  }
}
BuZz
  • 16,318
  • 31
  • 86
  • 141
  • 1
    Are you talking about js or css source maps? – nickbullock May 07 '20 at 06:28
  • @nickbullock it's js I am after. – BuZz May 10 '20 at 19:23
  • 1
    I've added a github repo that minimally replicates my setup and issue. Upon `ng serve`, you'll see in the developer tools that only original .scss are served. All the typescript is missing. https://github.com/franklin626/custom_webpack_undebuggable – BuZz May 12 '20 at 20:08

2 Answers2

5

To be honest I can't reproduce your issue with sourcemaps (I mean js sourcemaps, maybe you turned them off in browser? I usually turn js source maps off in angular applications to debug ngfactories and etc), but I almost sure that you got into conflicting webpack rules problem.

Please change your custom webpack config to the following to understand the problem:

const merge = require('webpack-merge');
const jsonImporter = require('node-sass-json-importer');

module.exports = function (defaultConfig) {

  console.log('>>>>> debug default config rules', defaultConfig.module.rules);

  const config = {
    mode: 'development',
    module: {
      rules: [
        {
          test: /\.scss$|\.sass$/,
          use: [
            {
              loader: require.resolve('sass-loader'),
              options: {
                implementation: require('node-sass'),
                sassOptions: {
                  // bootstrap-sass requires a minimum precision of 8
                  precision: 8,
                  importer: jsonImporter(),
                  outputStyle: 'expanded'
                }
              }
            }
          ]
        }
      ]
    }
  };

  return merge(defaultConfig, config);
}

In this log you will see that default anguar config already have two rules for /\.scss$|\.sass$/.
First one for component or directive styles (component.scss):

  {
    exclude: [
      '/Users/nikitabalakirev/Desktop/Projects/angular-webpack-custom-sass-rule-example/src/styles.scss'
    ],
    test: /\.scss$|\.sass$/,
    use: [ ... ]
  },

And the second for global styles:

  {
    include: [
      '/Users/nikitabalakirev/Desktop/Projects/angular-webpack-custom-sass-rule-example/src/styles.scss'
    ],
    test: /\.scss$|\.sass$/,
    use: [
      ...
    ]
  },

This means that your rule and default rules overlapping each other and that can lead to unexpected behavior.
What you can do with this information:

  1. If you need scss files to be able to import json files you need to change existing default rule, not to add your own, something like that:
const myRule =    {
              loader: require.resolve('sass-loader'),
              options: {
                implementation: require('node-sass'),
                sassOptions: {
                  // bootstrap-sass requires a minimum precision of 8
                  precision: 8,
                  importer: jsonImporter(),
                  outputStyle: 'expanded'
                }
              }
            }
const scssComponentRule = defaultConfig.module.rules.find(rule => rule.test.toString().includes('scsss'));
scssComponentRule.use.unshift(myRule);

  1. If you need NOT ALL of your scss files to be able to load jsons, use special "domain" name for such files:
    test: /\.special\.scss$|\.sass$/, and file name for example styles.special.scss, but you still need to exclude them from Angular's rules!
  2. If you don't need Angular's rules you can remove them from default config. But from my experience it's a huge page to support angular application with custom styling rules. It can crash after each update.

Tip: you can find and research angular default config here

Also please make sure that your angular.json looks like this:

        "build": {
          "builder": "@angular-builders/custom-webpack:browser",
          "options": {
            "customWebpackConfig": {
              "path": "./webpack.config.js"
            },

and

        "serve": {
          "builder": "@angular-builders/custom-webpack:dev-server",
          "options": {
            "browserTarget": "angular-webpack-custom-sass-rule-example:build"
          },

UPD
I opened a pr in your repo, thanks for the easy reproduction.
As I said previously it is about default and custom config conflicts.
Angular uses SourceMapDevToolPlugin for source maps so you don't need to set devtool property, just leave it blank.

nickbullock
  • 6,424
  • 9
  • 27
  • Thanks you. I've looked carefully into your answer and replicated it, I don't think I have an issue that is directly linked to this additional SCSS rule I have. I seems to be just due to the fact I am having lazy loaded modules AND use `@angular-builders/custom-webpack:dev-server` instead of the default. I automatically get minified/bundled code upon `ng serve`.... It's annoying. – BuZz May 10 '20 at 19:23
  • Where can I see the `console.log` output ? – BuZz May 12 '20 at 13:18
  • in your terminal after running ng serve – nickbullock May 12 '20 at 15:34
  • can you please describe/show how do you load lazy modules and share your package.json and angular.json? – nickbullock May 12 '20 at 15:39
  • I thought so, but I couldn't find anything of interest upon `ng serve` :o – BuZz May 12 '20 at 18:42
  • added angular.json and package.json – BuZz May 12 '20 at 18:42
2

Here is working the webpack and angular.json configurations:

angular.json

{
  ...
  "architect": {
    "build": {
      "builder": "@angular-builders/custom-webpack:browser", // <---
      "options": {
        "customWebpackConfig": {
          "path": "./webpack.config.js", // <---
        },
        ...
      }
    }
  },
  "serve": {
    "builder": "@angular-builders/custom-webpack:dev-server", // <---
    "options": {
      "browserTarget": "test:build" // <---
    },
    ...
  },
  ...
}

webpack.config.js

const jsonImporter = require("node-sass-json-importer");

module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$|\.sass$/,
        use: [
          {
            loader: require.resolve("sass-loader"),
            options: {
              implementation: require("node-sass"),
              sassOptions: {
                // bootstrap-sass requires a minimum precision of 8
                precision: 8,
                importer: jsonImporter(),
                outputStyle: "expanded",
              },
            },
          },
        ],
      },
    ],
  }
};

ng serve/build/build --prod work. And I got this result (changed colors as in your json file) enter image description here

In general, the past answer looks correct. But something wrong with the merging of default and custom webpack configs. @angular-builders/custom-webpack merges configs itself if the custom webpack config returns an object. If it returns a function then this function should merge them itself. And maybe this merging produced and issue.

Dmitrii Makarov
  • 757
  • 3
  • 11