nrwl / nx

Smart Monorepos · Fast CI
https://nx.dev
MIT License
23.28k stars 2.32k forks source link

feat(schematics/bazel): allow apps/libs to reference libs styles #54

Closed mkennedy3000 closed 4 years ago

mkennedy3000 commented 6 years ago

Use Case

I have styles and scss variables I want to share across all my applications. I made a styles library and would like to reference it with import "@myprojectname/mystylelib/styles" in all applications and other libraries.

Proposal

Similar to how Typescript is setup to resolve imports like @myprojectname/mylib, it would be nice to include:

"stylePreprocessorOptions": {
  "includePaths": [
    "../../../libs"
  ]
},

when creating apps and libs in .angular-cli.json. Unfortunately, these won't be namespaced with @myprojectname/... with the current directory setup provided by Nx.

A potential solution would be to change the directory structure of libs to be libs/@myprojectname/{libraries go here}. It would make the imports make more sense to the users and allow style focused libraries to be imported in the same manner within css, scss, and less files.

If this looks good, I would gladly like to help with the implementation. Thanks again for all your hard work, Nx is awesome!

tomwanzek commented 6 years ago

First of, I love the effort you are putting forward with nx!

Not having support for shared styles (i.e. similar to Angular Material theming support) at the library level is a major roadblock to adopting the current monorepo approach, however.

So far (using other library builders), I always had to opt for building and linking the libraries to ensure style import paths can be resolved, e.g. import "~mylib/styles/theming" during the development process. Addressing this use case would be most welcome.

sean-perkins commented 6 years ago

I'm a little confused - as I had no issues implementing this across apps from a shared library.

I have all my shared assets (sass, i18n translations, images, etc.) inside libs/theme. My NX workspace scope is @maestro.

In my root package.json, I add a reference to my shared assets lib.

 "@maestro/theme": "file:libs/theme",

This will allow you to access SASS imports like so in your apps/ directory:

@import '~@maestro/theme/sass/global/tools/colors';

To get shared non-sass files to work, you need to update the .angular-cli.json to copy the glob pattern from libs/theme into your app.

"assets": [
        "assets",
        "favicon.ico",
        {
            "glob": "**/*",
            "input": "../../../libs/theme",
            "output": "./assets/"
        }
      ],

Now everything inside of libs/theme is available to your Angular projects from /assets.

tomwanzek commented 6 years ago

@sean-perkins I'll double check my setup, conceivably it was an issue with a particular version of the underlying Angular cli/Angular version. I ran into resolution errors w.r.t. the SASS file imports for the theme.

paesku commented 6 years ago

@mkennedy3000's request is doable with @sean-perkins mentioned approach. Unfortunately the files are locked in your node_module and will not be updated on changes unless you remove the reference and do fresh install.

We completely extracted styles into a theme library which almost only contains mixins. They can be used in all other libs and apps. We would like to import them either with @import '~@theme-lib/src/sass/mixins/buttons'; or import '~theme-lib/src/sass/mixins/buttons'. The reference in package.json feels wrong. Any other ideas?

apps
 |-app-1
   |- app-using-ui-lib-and-theme-styles
 |-app-2
libs
 |-ui-lib
    |-lib-using-theme-styles
 |-api-lib
 |-chart-lib
 |-another-lib
 |-theme-lib
    |- src
        |-sass
killbox-code commented 6 years ago

@paesku is spot on - the suggestion from @sean-perkins has me up and running, but it's been much more pain than usual for me to put my 'guess-and-check' styles hat on lately. I'd be happy to spearhead the work and submit a PR, I just don't know where to start other than some ugly pre-start script that copies everything to my app's src/assets and syncs changes back to the lib. Thinking there has to be a better way...

drakenfly commented 6 years ago

@paesku's request is exactly what I need.

I tried @sean-perkins approach and it seems to work for me (without reinstall), but of course it would be great if there is any way nx could provide a native way for this in future.

Is there any ongoing work or anything I can help with?

CanKattwinkel commented 6 years ago

I also ran into this challenge - would be great if nx had a solution for this. Maybe we can already collect requirements?

milocosmopolitan commented 6 years ago

I've been using webpack to compile LESS into css, separately from Angular libraries

libs
|_ styles
    |_ css
    |_ less
    |_ package.json
    |_ webpack.conf.js

In my webpack config

var webpack = require('webpack');
var path = require('path');
var MODULE_BUILD_DIR = path.resolve(__dirname, 'css');

const ExtractTextPlugin = require("extract-text-webpack-plugin");

const extractLess = new ExtractTextPlugin({
  filename: "output_name.css",
  disable: process.env.NODE_ENV === "development"
});

module.exports = {
  entry: './libs/styles/less/style.less',
  output: {
    path: MODULE_BUILD_DIR,
    filename: 'output_name.css'
  },
  module: {
    rules: [
      {
        test: /\.less$/,
        use: extractLess.extract({
          use: [
            {
              loader: "css-loader"
            },
            {
              loader: "less-loader",
              options:{
                sourceMap: true,
              }
            },
          ],
          fallback: "style-loader"
        })
      },
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '../[path][name].[ext]'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    extractLess
  ]
};

When I'm bundling libs using ng-packagr it copied styles library as well.

Then able to use @import "~@scope/lib-name"; in LESS file when npm package is installed from file directly "@scope/lib-name": "file:libs/lib-name",

Toktik commented 6 years ago

Any chance to implement ability for referencing library's scss from apps?

HMubaireek commented 6 years ago

I'd love a solution for this. @sean-perkins your solution didn't work for me. When I type npm install, it doesn't install my local lib. However, if I type npm i @fs/theme it will install a link for the package in node modules, and add new entry in package.json with the name theme only without @fs/ part.

woppa684 commented 6 years ago

Not a solution obivously, but for now I just have a symbolic link in place ...

mklink /D .\node_modules\@myscope\theme ..\..\libs\theme

At least this gets my project going for now ... And it DOES refresh the dev server when I change something in the theme.

dgroh commented 6 years ago

I created a styles.scss in the root of my lib. Where can I reference it?

image

dgroh commented 6 years ago

I just realised i opened an issue related to that one. So any updates on this?

https://github.com/nrwl/nx/issues/718

crestamr commented 5 years ago

Is this issue resolved? Can anyone suggest a better way for referencing library's scss from apps?

@paesku, How did you accomplish this, can you help me?

paesku commented 5 years ago

@amrstha it's pity that there is no proper solution for this issue. I'm still referencing styles within apps or *libs like this @import 'scss-library/src/sass/scss-file'; altough my IDE (webstorm) comlaints about unresolved path, it all works fine.

├── apps
    ├── app-1
    └── app-2
└── libs
    ├── library-1
    ├── library-2
    └── scss-library

whereas the scss-library is a normal library setup with nx.

HMubaireek commented 5 years ago

The solution I followed is this.

brianpkelley commented 5 years ago

Key component to getting this to work:

Run npm install after adding your libraries to the package.json

markgoho commented 5 years ago

So I've tried this but I'm wondering if anyone can find a problem with:

angular.json:

"projects": {
  "dashboard": {
    "architect": {
      "build": {
        "options": {
          "styles": [
            "libs/theme/src/lib/styles.scss",
          ],

I'm including the "theme" scss file from the theme lib.

You do get the application to rebuild when these files are edited and saved.

With the package.json method above, you have to break the build, npm install, and recompile each time you make changes to the theme scss files when included this way.

🎄

achimschrepfer commented 5 years ago

@markgoho how do you use the styles of styles.scss then in your application? I tried your approach, VSCode is able to resolve the styles (see screenshot) but when I run ng serve, I get an error (just like the mixin or the variable does not exist).

screenshot 2019-02-13 at 09 19 36 screenshot 2019-02-13 at 09 21 19
achimschrepfer commented 5 years ago

@markgoho seems that I've found the reason: the SCSS style gets completely preprocessed and compiled by the build process when you include it your way. So we do not have a chance to use mixins or variables this way.

achimschrepfer commented 5 years ago

Ok guys, this is what works for my so far: I added the folder where I place my global SCSS files to the stylePreprocessorOptions section of angular.json (see also https://github.com/angular/angular-cli/wiki/stories-global-styles). This way, everything works as expected (I can use the styles, variables and mixins in SCSS scripts throughout the workspace plus reload on change works).

The only drawback is that VSCode does not recognize this and therefore does not offer any Intellisense-support on global styles. To achieve this, I also added a search pattern to the styles section in angular.json - see screenshot for complete config.

screenshot 2019-02-13 at 09 46 48

I'm not quite sure if this has any side effects, especially for the build process (we try to go towards incremental builds for the whole workspace), but for now it works.

After all: I'd highly appreciate if NRWL came up with a solution/best practice for this use case since many users of NX face it for years.

skydever commented 5 years ago

Hi!

My setup looks like this, maybe this would help somehow:

// you have access to all variables defined at _variables.scss of the lib



So basically you have to import the scss file where you defined the variables to have access to them.
achimschrepfer commented 5 years ago

@skydever thanks for sharing - the same works for me. What IDE/Editor are you using? Does it support this approach with Intellisense/Autocompletion?

skydever commented 5 years ago

hi @achimschrepfer! I am using vscode but the intellisense/autocompletion seems not work. Should it work out of the box or is there an extension required for this?

achimschrepfer commented 5 years ago

I'm using the SCSS Intellisense extension for VScode. But if I set up as you suggested, Intellisense does not work at all.

As you can see in my last comment above yours, I've managed to get Intellisense support by adding the styles not only to stylePreprocessorOptions but also to the styles option in the architect settings.

skydever commented 5 years ago

I see, thx :+1:

markgoho commented 5 years ago

Hi @achimschrepfer

So I have the theme lib: image

And in the styles.scss I have:

@charset "UTF-8";

@import './scss/normalize';
@import './scss/typography';
@import './scss/color';
@import './scss/spacing';
@import './scss/forms';

:root {
  --header-height: 48px;
  --nav-width: 256px;
  --border-radius: 6px;
  --box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2);
  --border: 1px solid var(--color-neutral-600);
}

... more styles

In my angular.json for the apps that I want to use my theme lib styles:

projects: {
  "dashboard": { <---- my app name
    "architect": {
      "build": {
        "options": {
          "styles": [
            "libs/theme/src/lib/styles.scss",
          ],

I'm only using CSS Custom Properties (CSS Variables) declared at the root level and thus are accessible anywhere in any component without importing any special files.

So, for example in my _color.scss file:

:root {
  --color-primary-1000: hsl(234, 62%, 26%);
  --color-primary-900: hsl(232, 51%, 36%);
  --color-primary-800: hsl(230, 49%, 41%);
  --color-primary-700: hsl(228, 45%, 45%);
  --color-primary-600: hsl(227, 42%, 51%);
  --color-primary-500: hsl(227, 50%, 59%);
  --color-primary-400: hsl(225, 57%, 67%);
  --color-primary-300: hsl(224, 67%, 76%);
  --color-primary-200: hsl(221, 78%, 86%);
  --color-primary-100: hsl(221, 68%, 93%);
}

and in a component in another lib:

.header-text {
  color: var(--color-primary-500);
}
debuggerpk commented 5 years ago

i have found a much easier solution to it with lerna.

  1. I do a lerna init in my project root folder to generate me a lerna.json.

  2. Then i edit my lerna.json to have

    {
     "packages": ["libs/*"],
     "version": "independent",
     "npmClient": "yarn",
     "stream": true,
     "useWorkspaces": true
    }
  3. Add a workspaces property to my package which looks like this

     "private": true,
     "workspaces": [
        "libs/*"
      ],
  4. And finally, lerna bootstrap.

This allows me to import from my theme doing

@import '~@workspace/theme/styles/abstracts/variables';

The trick is lerna bootstrap command, which creates a symlink in my root node_modules folder. However, I haven't gotten intensense to work with it.

intellix commented 5 years ago

Wanting to do: https://material.angular.io/guide/theming-your-components

ng g lib layout
ng g c header --project=layout
ng g c footer --project=layout

Creates a tree like:

libs
  /layout
    /src
      /lib
        /header
          header.component.html
          header.component.scss
          header.component.spec.ts
          header.component.ts
          header.theme.scss
        /footer
          footer.component.html
          footer.component.scss
          footer.component.spec.ts
          footer.component.ts
          footer.theme.scss

Now, you can create a file next to index.ts for that lib /libs/layout/src/theming.scss:

@import './lib/header/header.theme.scss';
@import './lib/footer/footer.theme.scss';

and inside your app's styles.scss:

@import 'libs/layout/src/theming';

@include header-theme($theme) { ... }
@include footer-theme($theme) { ... }

It seems to work well IMO. You're whitelisting/exporting only theme files so you don't start duplicating SCSS as you import.

You don't need to update stylePreprocessorOptions.includePaths.

For sure this would be cool, but it only looks prettier:

@import '~@angular/material/theming';
@import '~@myapp/layout/theming';
NgxDev commented 5 years ago

How about another use case with monorepos, when you have multiple apps and a common ui components library? And those ui components need to have different colors (and possibly other style properties that differ), depending on which app consumes them.

For example:

apps/
    - app1
    - app2
libs/
    - ui/
        - dropdown/

Depending on which app consumes the dropdown, the dropdown needs to have different colors for borders, active items, hovered items, shadow etc. That's the issue I'm facing right now. How can we achieve that? (ie without taking all styles from the ui components library and making them global) since it's impossible to know at the ui component's css level what app consumes the component...

brianpkelley commented 5 years ago

@MrCroft The way I have mine set up is that each component has its included styles in scss file and then its "changeable" values in another wrapped in a mixin/function as above. Much like

libs/
    - ui/
        - dropdown/
            - dropdown.component.scss
            - _dropdown.theme.scss

Then you just @import '~@lib/ui/dropdown/dropdown.theme' file in your app.scss file and @include dropdown-theme($theme);

lupeportias commented 5 years ago

I created a theme folder in my libs... and inside the styles.scss of my app I imported the global scss...

@import '../../../libs/common-components/src/lib/theme/global.scss';

inglkruiz commented 5 years ago

I found a way to share mixins, variables, and functions between apps and libs. I created a folder in the root styles and then I imported what I need in any scss files just by adding @import 'styles/variables', for example. If you want to share a base style for your components then from there is quite easy. Let me know what do you think.

tcoz commented 5 years ago

Hmm…doesn’t there have to be a path or preProcessor config somewhere to inform the builder how to resolve the implied path? When I just created a root folder and dumped styles into it, the imports all had to be relative. scss imports don’t seem to recognize “paths” in tsconfig, and adding preprocessor options in angular.json without a library all failed. If somehow it does work, it’d be interesting if you could run an AOT build on a given app and see if there are any problems.

The way I use after some Nrwl repo chat: create a library, which adds an entry to “paths” in your tsconfig, it’ll look something like ‘@myrepo/somefolder’. This lib now serves as a namespace wrapper that you can use to resolve assets in it. Dump all your scss into the lib directory of that library, then configure the preprocessor options of each project that would use that implied path. Your app builder should now be able to resolve imports in sass files by just using (assuming the file “variables.scss” is in the lib folder) @import ‘variables’;

On Jul 11, 2019, at 5:32 AM, LuisK Ruiz notifications@github.com wrote:

I found a way to share mixins, variables, and functions between apps and libs. I created a folder in the root styles and then I imported what I need in any scss files just by adding @import 'styles/variables', for example. If you want to share a base style for your components then from there is quite easy. Let me know what do you think.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/nrwl/nx/issues/54?email_source=notifications&email_token=AAJIGE6HNHFDR4XJNDTBSG3P634ZPA5CNFSM4D65JNS2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODZWDZPI#issuecomment-510409917, or mute the thread https://github.com/notifications/unsubscribe-auth/AAJIGE72JYQKEHG5YH3V6ATP634ZPANCNFSM4D65JNSQ.

vsavkin commented 4 years ago

Folks. I'm going to close this.

We are reimplementing bazel support using the new capabilities provided by the bazel team. We are hoping to release an early beta in January.

gerardcastell commented 4 years ago

Is there any solution from Nx team already? Thx

helgetan commented 4 years ago

Any Updates here?

michael-letcher commented 4 years ago

@vsavkin I really dislike closing a ticket for the sake of closing the ticket and saying "something will happen".

As you can already see, two comments months later than your "early January" of wanting updates to this issue.

Is there a solution and why hasn't it been linked to the what I'd consider an open ticket.

markgoho commented 4 years ago

@michael-letcher there are a number of workable solutions presented in this thread

  1. create a theme lib
  2. import styles and assets from that lib in angular.json file for any apps that need it
  3. use css custom properties to share variables between libs

There is a ponyfill for css custom properties if you need to support IE.

michael-letcher commented 4 years ago

@markgoho I understand there are community workarounds. But I'm after a response from the developers. As their last comment was an offical solution coming in January.

I'm already using a very close variation of your solution.

If they comeback saying what they were trying failed and to just use the workarounds here. Then so be it. But closing a ticket and not actually resolving it I think is a bad practise.

Eladigo commented 3 years ago

So... January (of 2021..) is here.. I got a little lost from all the possibilities, what is the recommended way according to NX itself?

markgoho commented 3 years ago

@Eladigo I use this method on all new projects. Please let me know if you need assistance understanding this strategy or help getting it set up for your repo.

evanjmg commented 3 years ago

There should be lib support also for React apps with lerna etc without using css modules. Is this possible? Right now I am just copying the scss file?

B1Z1 commented 3 years ago

Yo, I find out great solution, to provide lib/styles into app. You can look here in the example of nextjs https://github.com/erkobridee/nx-nextjs

Splaktar commented 3 years ago

@markgoho's approach seems to work pretty well. However, I think that I've run into an edge case.

Here's what we're seeing.

  1. We have libs/theme/task/_comment.scss and libs/theme/task/_task.scss that imports comment.
  2. In our app's build config in angular.json's styles array, we have libs/theme/task/_task.scss.
  3. Using ng serve client-app, we make a change to libs/theme/task/_comment.scss and the app reloads.
  4. Push to CI and have a production build done and stored in the cache.
  5. Push a Sass change to libs/theme/task/_comment.scss to CI and the production build hits the cache and quickly replays the cache result.
  6. Test the output of this build, the Sass changes from (5) are missing.

So most everything works fine, but the Nx build cache doesn't detect this change.

It looks like I'll need to add

  "implicitDependencies": {
    "libs/theme/**/*.scss": ["client-app"]
  },
raberana commented 1 year ago

Hello. Is there already a more elegant solution for this requirement? We would like to create a pure css library that can be imported by an angular, react and vuejs libraries/applications.

github-actions[bot] commented 1 year ago

This issue has been closed for more than 30 days. If this issue is still occuring, please open a new issue with more recent context.