BuZz
BuZz

Reputation: 17425

Angular: How to parse JSON variables into SCSS

Within an Angular application, I do D3 visuals through either plain D3 or Vega. There's also SCSS styling going on.

I'd like to be able to refer to the same global variables for styling from Javascript and from SCSS. JSON files do the trick very well for storing settings that I load into Typescript through a simple import statement. But how does one go about doing the same from SCSS ?

node-sass-json-importer seems like a good candidate but adding it to an Angular 9 CLI application isn't obvious to me.

This StackOverflow post brushed on the topic a while back, but it involved modifying resources under node_modules which is hardly sustainable.

There are also some inputs in the orginal doc as to how one can go about tweaking webpack in a non-Angular app. I do not know how to relate this to an Angular app built through the CLI.

Webpack / sass-loader Blockquote

Webpack v1

import jsonImporter from 'node-sass-json-importer';

// Webpack config
export default {
  module: {
    loaders: [{
      test: /\.scss$/,
      loaders: ["style", "css", "sass"]
    }],
  },
  // Apply the JSON importer via sass-loader's options.
  sassLoader: {
    importer: jsonImporter()
  }
};

Webpack v2

import jsonImporter from 'node-sass-json-importer';

// Webpack config
export default {
  module: {
    rules: [
      test: /\.scss$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            importLoaders: 1
          },
        },
        {
          loader: 'sass-loader',
          // Apply the JSON importer via sass-loader's options.
          options: {
            importer: jsonImporter(),
          },
        },
      ],
    ],
  },
};

Upvotes: 8

Views: 5537

Answers (4)

hvedemelsbof
hvedemelsbof

Reputation: 21

Working in angular 10: Use custom webpack as others have mentioned, but the only working way i could get to work in angular 10 was editing a function, originally suggested by Kishorevarma.

webpack.config.js

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

const webpack = require("webpack");

const mode =
  process.env.NODE_ENV === "production" ? "production" : "development";

module.exports = function (config) {
  const rules = config.module.rules || [];
  rules.forEach((rule) => {
    if (String(rule.test) === String(/\.scss$|\.sass$/)) {
      const loaders = rule.use;
      const transformedLoaders = loaders.map((l) => {
        if (l.loader && l.loader.search("sass-loader") !== -1) {
          l.options.sassOptions.importer = jsonImporter();
        }
        return l;
      });
      rule.use = transformedLoaders;
    }
  });
  return config;
};

I've been importing breakpoints originally created for angular flexlayout.

breakpoints.json

{
  "breakpoints": [
    {
      "alias": "xs",
// Notice the quoting
      "mediaQuery": "'screen and (min-width: 0px) and (max-width: 599.98px)'",
      "priority": 1000
    },
    ...
]
}

breakpoints.scss

@import "./breakpoints.json";

$bps: ();

// Reformat from json
@each $breakpoint in $breakpoints {
  $i: index($breakpoints, $breakpoint);
  $bps: map-merge(
    $bps,
    (
      map-get($breakpoint, alias): map-get($breakpoint, mediaQuery),
    )
  );
}

// Can be used with
// @media #{(map-get($bps, xs))} {
//      margin: 0 -15px;
//    }

breakpoints.ts

import { BreakPoint } from '@angular/flex-layout';

// Custom breakpoints
const brkpnts: BreakPoint[] = require('./breakpoints.json').breakpoints;
export const JSONBreakpoints = brkpnts.map((bp) => {
  // Remove the single qoutes needed for the sass variables
  return { ...bp, mediaQuery: bp.mediaQuery.substring(1).slice(0, -1) };
});

app.module.ts

import { JSONBreakpoints } from './breakpoints';

@NgModule({
  imports: [
FlexLayoutModule.withConfig({ disableDefaultBps: true }, JSONBreakpoints)
  ]
})

Upvotes: 1

kemsky
kemsky

Reputation: 15261

Another option is to use raw-loader, it is available by default in angular-cli. This way you can load raw scss file into ts component:

// Adding `!!` to a request will disable all loaders specified in the configuration
import scss from '!!raw-loader!./config.scss';

console.info('scss', scss);

You have to declare module in .d.ts file:

declare module '!!raw-loader!./*.scss';

Then you can extract any information you want.

So instead of loading JSON into scss you can load scss into ts.

Upvotes: 3

kacase
kacase

Reputation: 2859

I have a relatively simple approach for you:

Separate the node-sass step form the ng build step and use the generated .css files instead of the sass files in your angular app.

This has two advantages:

  • no need to heavily tweak and edit any node-modules
  • full control over how the sass is compiled

For the implementation i would go ahead as follows:

  1. add ./node_modules/.bin/node-sass --importer node_modules/node-sass-json-importer/dist/cli.js --recursive ./src --output ./src to the scripts in your package.json e.g. with the name build-sass. You can tweak this command to only include the sass files that you need the recursive mode for (make sure to ignore them in the angular.json, so they don't get compiled twice).
  2. run the command once npm run build-sass in order to generate the .css files in the project.
  3. change references in angular components where needed from the .sass to .css
  4. (optionally) add a convenience command npm run build-sass && ng build as compile in your package.json which you can run whenever you want to build the whole project.

While this approach is relatively simple, it comes with a few drawbacks

  • for any components where you want to use the css, the hot-reload feature of ng serve will require you to run your npm run build-sass to see any changes you made to your styles in real-time
  • your project will look a bit messier, as there are .scss and .css files for the same component in the /src folder of which the .css is generated. You will need to make sure (e.g. by adding a suffix or comment) that noone accidentally edits the generated .css and those changes get overridden.

Other approaches would almost always involve heavily editing existing or writing your own webpack compiler.

Upvotes: 2

Christos Lytras
Christos Lytras

Reputation: 37298

You can do it without changing any node_modules files by using @angular-builders/custom-webpack to setup custom Webpack rules and as you mention node-sass-json-importer to import JSON files inside SCSS files.

You'll have to install node-sass for the implementation option because node-sass-json-importer is compatible with node-sass.

  1. Install packages @angular-builders/custom-webpack, node-sass-json-importer and node-sass:

    npm i -D @angular-builders/custom-webpack node-sass-json-importer node-sass
    
  2. Create Webpack config file (webpack.config.js) to the project root directory with the following contents:

    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'
                }
              },
            }
          ],
        }],
      },
    }
    
  3. Change builder to @angular-builders/custom-webpack:[architect-target] and add customWebpackConfig option to build, serve and test build targets inside angular.json:

    "projects": {
      ...
      "[project]": {
        ...
        "architect": {
          "build": {
            "builder: "@angular-builders/custom-webpack:browser",
            "options": {
              "customWebpackConfig": {
                "path": "webpack.config.js"
              },
              ...
            },
            ...
          },
          "serve": {
            "builder: "@angular-builders/custom-webpack:dev-server",
            "customWebpackConfig": {
              "path": "webpack.config.js"
            },
            ...
          },
          "test": {
            "builder: "@angular-builders/custom-webpack:karma",
            "customWebpackConfig": {
              "path": "webpack.config.js"
            },
            ...
          },
        },
        ...
      },
      ...
    }
    

Now you can include any .json file inside any component .scss file:

hello-world.component.scss:

@import 'hello-world.vars.json';

.hello-bg {
  padding: 20px;
  background-color: $bg-color;
  border: 2px solid $border-color;
}

hello-world.vars.json:

{
  "bg-color": "yellow",
  "border-color": "red"
}

I have created a Github repository with all these working that you can clone and test from here: https://github.com/clytras/angular-json-scss

git clone https://github.com/clytras/angular-json-scss.git
cd angular-json-scss
npm install
ng serve

Upvotes: 10

Related Questions