aandis
aandis

Reputation: 4212

Import scss from node_modules within ember addon

I'm developing an ember addon which imports sass from it's dependencies. To use it I have the following in my addon -

# my-addon/package.json
...
"dependencies": {
  "lib1": "^1.5.3",
  "lib2": "1.2.3",
  "ember-cli-sass": "^10.0.1",
  "sass": "^1.23.3"
}
...
# my-addon/addon/styles/addon.scss

@import "lib1/assets/stylesheets/styles";
@import "lib2/assets/stylesheets/styles";
# my-addon/ember-cli-build.js

let app = new EmberAddon(defaults, {
  // Add options here
  sassOptions: {
    includePaths: [
      'node_modules'
    ]
  }
});

This way, the dummy app at tests/dummy can resolve the imports. But when I use the addon in my host app, I get

Error: Can't find stylesheet to import.
  ╷
1 │ @import "lib1/assets/stylesheets/styles";
  │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I can modify my host app's ember-cli-build.js to

let app = new EmberAddon(defaults, {
  // Add options here
  sassOptions: {
    includePaths: [
      'node_modules/my-addon/node_modules'
    ]
  }
});

and ideally it should work but sass runs out of memory because it tries to import everything in node_modules of my host app. How do I make this work for both the dummy app and the host app still be able to import namespaced lib scss?

Upvotes: 2

Views: 593

Answers (1)

James Almeida
James Almeida

Reputation: 21

Writing my addon I had the same problems!

Researching a lot and mixing the learnings, I came to the following resolution.

Describing scenario:

Writing an EmberJS Addon to provide material design components installing the library locally and only for components and behaviors that I want my addon, reuse and expose as custom ember components. Something like the adopted addon Ember paper.

Ember version and @material dependencies.

  // my-addon/package.json
  ...
  "ember-source": "~4.5.0",
  "@material/theme": "^14.0.0",
  "@material/typography": "^14.0.0",
  "ember-cli-sass": "^11.0.1",
  "sass": "^1.53.0",
  ...
  "ember": {
    "edition": "octane"
  },
  ...

Notice that for this answer I only added @material/typography

  // my-addon/app/styles/my-addon.scss
  @use "@material/typography" with (
    // Customize material font-family
    $font-family: unquote("Any-font-you-want, sans-serif")
  );
  // Import material typography styles
  @use "@material/typography/mdc-typography";

For the first time, I fell into the same mistake -> Add sassOptions to ember-cli-build.jsincluding the wholenode_modulespath. But the comment that the addon-cli wrote in theember-cli-build` file made me realize that I was wrong.

/* This build file specifies the options for the dummy test app of this addon, located in /tests/dummy This build file does not influence how the addon or the app using it behave. You most likely want to be modifying ./index.js or app's build file */


The Solution

To begin we should install some npm packages

"resolve": "^1.22.1",
"broccoli-funnel": "^3.0.8",
"broccoli-merge-trees": "^4.2.0",

resolve:

Asynchronously resolve the module path string id into cb(err, res [, pkg]), where pkg (if defined) is the data from package.json

broccoli-funnel:

Given an input node, the Broccoli Funnel plugin returns a new node with only a subset of the files from the input node. The files can be moved to different paths. You can use regular expressions to select which files to include or exclude.

broccoli-merge-trees:

Copy multiple trees of files on top of each other, resulting in a single merged tree.

  // my-addon/index.js

  // Import packages to work with trees and paths
  const path = require('path');
  const resolve = require('resolve/sync');
  const funnel = require('broccoli-funnel');
  const { MergeTrees } = require('broccoli-merge-trees');

  /*
   Below you can see an array of objects representing the
   material path and the component name. Notice that we only
   installed the @material/typography but the component uses other
   material dependencies internally.

   We will merge all `scss` and `sass` files from those paths
   to allow us imports the @material/typography at
   #my-addon/app/styles/my-addon.scss.

   Following this design, if you forget to include some path
   you will receive errors saying which paths are missing. 
  */
  
  const materialPackages = [
    { name: 'theme', path: '@material/' },
    { name: 'typography', path: '@material/' },
    { name: 'feature-targeting', path: '@material/' },
  ];

module.exports = {
  name: require('./package').name,

  included: function (/* app */) {
    this._super.included.apply(this, arguments);
  },

  treeForStyles(tree) {
    let trees = [];

    let pathBase;
    materialPackages.forEach(function (pkg) {
      pathBase = path.dirname(
        resolve(`${pkg.path}/${pkg.name}/package.json`, {
          basedir: __dirname,
        })
      );

      trees.push(
        // Using broccoli to return a new tree with the
        // files that matches the include property rules.
        // import: ['**/*.{scss,sass}']
        funnel(pathBase, {
          destDir: `${pkg.path}/${pkg.name}`,
          include: ['**/*.{scss,sass}'],
        })
      );
    });

    // Push existing tree
    tree && trees.push(tree);

    // Returninng merged broccoli tress
    return new MergeTrees(trees, { overwrite: true });
  }
}

With this, you should be able to use the addon with its external sass files in the dummy app or in another application using the npm link or publishing the addon.

Notice that I preferred to force addon consumers to import the @import 'my-addon-scss';. I had not test providing styles directly on the #my-addon/addon/styles/addon.scss.

I believe this answer can start the discussion about creating ember add-ons.

I hope I receive answers to make my code better.

References

Upvotes: 1

Related Questions