Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@import files as modules #353

Closed
moraes opened this issue Apr 15, 2012 · 29 comments
Closed

@import files as modules #353

moraes opened this issue Apr 15, 2012 · 29 comments
Assignees
Labels
enhancement New feature or request

Comments

@moraes
Copy link

moraes commented Apr 15, 2012

I love SASS, but there's something that always bugged me about how @import works. I'd like that files were real 'packages' (or 'modules') that, when imported, don't drop all names into the global scope -- instead, they use the import name as the namespace for the names contained in the file. This would make it a lot more modular and modern.

The idea in a nutshell:

@import "foo"

.bar {
    @include foo.bar;
}

Here, foo.scss contains a mixin bar. To use it we call 'foo.bar' -- prefixing it with the 'foo' namespace. The idea requires also to support renaming the imported package on import:

@import "foo" as "fooAlias"

I've used the keyword 'as' from Python, but it could be something else; it doesn't matter. After imported using a custom name, we use it as:

.bar {
    @include fooAlias.bar;
}

This originated here:

http://stackoverflow.com/questions/10164995/css-preprocessor-with-modules-and-sane-scope

@nex3
Copy link
Contributor

nex3 commented Apr 16, 2012

We're definitely thinking about possible ways to accomplish this. We're planning on replacing @import with a more powerful, module-aware import mechanism, but we haven't figured out exactly the right semantics for it yet. There are a number of use cases it needs to support, and we need it to support them all cleanly. This is a tall order, and we have yet to start really working on it.

@jleclanche
Copy link

@nex3 Do you have a list of what needs to be supported? I'll be glad to draw up a proposal.

@nex3
Copy link
Contributor

nex3 commented Apr 16, 2012

Nothing really written down. There are a number of issues in the tracker that are related. Off the top of my head:

  • Non-transitive imports (if A imports B and B imports C, A shouldn't necessarily see everything from C).
  • A way of getting around non-transitive imports.
  • Namespacing, as in this proposal. Not just for mixins but also variables and probably placeholder selectors as well.
  • Disabling CSS output, both selectively and wholesale. The importer may want to @extend the rules in the importee but not have them concretely present, or it may want to suppress rules matching a certain selector.
  • Renaming classes. This isn't something I'm 100% sold on, but it has been requested. Maybe the correct way to do this is some combination of namespacing and @extend.

@chriseppstein Can you think of any I'm missing?

@jleclanche
Copy link

Disabling css output should be done with a flag imho. Eg @import "file.scss" !noinclude;

Non-transitive imports.. Python solves this with __all__. I think it would be a better way to go. For the record, in python, when you do from foo import *, it includes everything, unless an __all__ variable is specified, which contains a list of variables which are deemed "public" by the author.

Namespacing.. yes. I would find it weird by default. Especially since I don't believe there's any namespacing currently in scss?
There was another proposal I think about reusing properties from the same rule, I threw something like:

.foo {
    background-color: red;
    color: lighten(10%, &->background-color);
}

I think this would be good syntax for namespacing in general. So you could do:
import "mystyle.scss" as mystyle !noinclude;

.foo {
    @include(mystyle->myGenericGreen());
}

.bar {
    color: (.foo a:hover)->color; // some shade of green
}

so to sum it up:

@import "file.css", "file2.scss";

-> imports file.css and file2.scss, both are compiled in.

@import "file.css", "file2.css" !native;

-> translates to "native css import":

@import "file.css"
@import "file2.css"


@import "file.css", "file2.scss" !native;

-> errors because you cannot natively import file2.scss

@import "file.css", "file2.scss" !noinclude;

-> imports file.scss and file2.scss and makes all their rules available. I am not sure whether !noinclude should have to be specified for every file in the rule. I think css would not like that but then again there aren't exactly many bang-rules in css.

@import "file.css" as foo, "file2.scss" as bar;

-> imports file.css accessible under the namespace foo, and file2.scss accessible under the namespace bar. I don't currently know whether importing a file "as another" should imply !noinclude (actual keyword name to be decided, i dont really like noinclude). I would go with "least surprise", but I can't actually figure out what behaviour would be the least surprising. I might expect importing "as" to be "obviously, I want this namespaced, so don't go compiling this for me". But then again, I might expect "I have to specify !noinclude in other cases, why not now?"

non-transitive imports undefined atm. I'd recommend an __all__-like variable or rule or something.

@jleclanche
Copy link

https://github.com/import This poor guy must be getting spammed. Sorry mate.

@moraes
Copy link
Author

moraes commented Apr 16, 2012

@adys, there're some good ideas there.

I'd require the namespace to be always named in order to use namespaces. So:

@import "file.css", "file2.css";

...is automatically translated to "native import", while:

@import "file.css" as file;
@import "file2.css" as file2, "file3.css" as file3;

...are "namespaced" imports.

Personally I think namespaces should be the default as one of the main purposes of preprocessors is to allow modularization. As it is we need to create fake namespaces in mixin/variable names to avoid collisions. But I understand the concern about backwards compatibility and can live with it as optional. :)

I think !noinclude is unnecessary. If people want to selectively include selectors, mixins or variables, they can create a new file, import a namespace and include only what is wanted. No need for new syntax and rules.

@jleclanche
Copy link

This doesn't make sense. You say:
"I'd require the namespace to be always named in order to use namespaces"

and then you say:
"I think namespaces should be the default"

It's either one or the other :P I'm comparing to python/the first example in this thread here where @import "foo" would make everything available as "foo->x". It's a bad idea imho because you could be importing foo.sass, foo.scss and foo.css in the same file.

@moraes
Copy link
Author

moraes commented Apr 16, 2012

"I'd require the namespace to be always named in order to use namespaces"

^ This is a "let's make it fit in a compatible way" proposal.

"I think namespaces should be the default"

^ This is "how I really think things should be if I was implementing SASS from scratch". That said, I don't know Ruby so I can only give ideas. :)

@jleclanche
Copy link

@nex3 Any comments on the proposal?

@nex3
Copy link
Contributor

nex3 commented Apr 25, 2012

Haven't had time to look through it in-depth yet.

@zakdances
Copy link

It's been more than year. This isn't going to happen. Would you consider pull requests to add this feature?

@chriseppstein
Copy link

@zakdances Have patience. This is the main feature we have planned for Sass 4.0. We would like to see proposals for how this would work and get feedback on such proposals (Like @noprompt's #749). Just plowing forward and writing code without an agreement for what we are building seems likely to waste a lot of time.

@zakdances
Copy link

@chriseppstein Understood. Should new proposals be appended here or filed as a new issue?

Did you know that each time you use @import in an .scss file, Sass actually copies the contents of the linked stylesheet to the file after compilation? This means that if you use that same import statement more than once, even in different sass files, you get duplicate CSS? This creates a TON of duplicate CSS and creates incredible bloat.

I'm just surprised that functionality like @import, which is so core to the Sass experience, has this kind of major side effect. I hear what you're saying about reaching an agreement, but in the meantime shouldn't there be at least be a temporary workaround to limit the problems this issue causes?

Maybe I'm using @import wrong or I'm ignorant of work-arounds/fixes for this. Please let me know how you're dealing with this issue in your own projects.

@chriseppstein
Copy link

@zakdances A gist linked to this issue would be a good way to make a proposal. Yes, I'm very aware of how @import works -- I'm one of the core developers of Sass. They way you manage the behavior of the import directive effectively is to keep separate things that need to be re-used (mixins, functions, variable definitions, etc) from the things that should only appear once and then judiciously import the latter. Here's an article on best practices for project organization.

@zakdances
Copy link

Thank you @chriseppstein for that article link. I've been really struggling with CSS organization in larger projects lately so this is honestly helps a great deal.

For anyone else reading this, there's also a node.js library called clean-css which added a feature a few months ago to remove duplicate css. It can be used as a grunt task.

@jamadam
Copy link

jamadam commented Jan 14, 2015

Hi. Does this topic covers namespaced variable access?

Not a few modules encourage customizing their behavior by setting global variables.

$some-grid-grid-columns: 12;
@import "some-grid";

Instead of above, I wish if I could do something like..

@import "some-grid" as "grid";
grid.$grid-columns: 12; // or something like grid.set-grid-columns(12)
grid.initialize(); // maybe?

No matter how, in the future, can I expect ways to get rid of global variables?

@jamadam
Copy link

jamadam commented Jan 14, 2015

If the namespace feature comes out and can have own variables, I also wish multiple instance to work, like object-oriented class and instance variable.

@import "some-grid" as "grid1";
grid.$grid-columns: 12;

@import "some-grid" as "grid2";
grid.$grid-columns: 6;

@haydenc
Copy link

haydenc commented Oct 8, 2015

Is there an update on when import v2/Sass 4.0 will be released? Can't find a timeline or recent update anywhere online...

@simonbuerger
Copy link

Why not make the semantics of this very similar to how ES2015 module imports work?

@import $grid from './grid'; //Imports everything from ./grid as $grid. 
@import $utils from './utils'; //If we're namespacing you can assume no output css from these modules. So default behaviour when namespacing can be @import-once?

$grid.$settings: (
  columns: 12,
  gutter-width: 20px
); //Alter config variable - is this global now for anywhere the module is used (like commonjs import) or specific to the current file?

.test {
  @include $grid.columns(3); //Mixin
}

.item {
  @extend $utils.%clearfix; //Extend placeholder
  font-size: $utils.rem(18px); //Function
}

//Could also allow import of only what you need from a module
@import ( rem, %clearfix ) from './utils';
@import ( $positive, $negative, $neutral ) from './colors';
@import ( columns ) from './grid';
//Import and rename
@import ( columns as columns2 ) from './grid2';

//Perhaps as with commonjs you can even give control over what gets exported from a file to the file itself?

//Eg. in './utils' above

//Not exported
@function map-extend($map, $maps...) {
  /*...*/
}

//Exported
@export @function rem($value) {
  /*...*/
}

@export %clearfix {
  /*...*/
}

Just a thought, seems javascript modules semantics could fit quite well for Sass too.

@simonbuerger
Copy link

Alternatively if you don't want dot notation (Sass seems to be largely avoiding it) you could even decide that the importing an entire library under a variable eg. @import $grid from './grid' is not allowed and only permit named imports. As I mention above you could then named @import anything - placeholder, variable, mixin, function (or even selector? thought that could get very messy) and use it just as if you had defined it in that file. Except it would have access to effectively private variables and functions from the other file.

That would eliminate the need for a new dot-notation or some other way to access items in an imported module 'collection' - the usage of all those things could stay the same as it is currently.

@import ( rem, %clearfix ) from './utils';
@import ( $positive, $negative, $neutral ) from './colors';
@import ( columns ) from './grid';
//Import and rename
@import ( columns as columns2 ) from './grid2';

Exports:

//Exported
@export @function rem($value) {
  /*...*/
}
@export %clearfix {
  /*...*/
}

//re-export (useful for libraries/index files). Can optionally use rename syntax too
@export ( columns as grid-columns ) from './grid'

The no-output use-case is also covered, since you're only importing what you need in the first place.

@ilan-schemoul
Copy link

4 years since 2013. 4 years you've been saying that this or this feature is going to be add to Sass 4.0. When does this incredible 4.0 version is going to be released ? Has this version been abandoned ?

@devdoomari
Copy link

@NitroBAY yeah I'm hoping something would come out of this...
came here because CSS module's 'composes' doesn't actually 'compose' styles, but instead adds up classnames so overriding doesn't work.

was thinking about using sass import-extend, but sass import can't do 'selective' import & etc :(

@robertjk
Copy link

robertjk commented Apr 7, 2017

The biggest advantage of that for me is it would make the sharing of variables and mixins between files so much cleaner. Today, when I want to share a variable between 2 files, I need to put the variable in a partial and then import that partial in both files. There are cases though, when the variable logically belongs to one of the modules and putting it into external partial feels like a hack and fragments the code. It feels dirty to declare some very component specific variable in a globally shared partial, just to be able to use it in another file.

@ArmorDarks
Copy link

I support @simonbuerger proposal.

I was thinking about ES6-way too, and even wanted to describe proposal here, but @simonbuerger catched already whole thing.

ES6 imports and exports are nice, and from all I've seen so far, seems to be the most elegant and understandable. So I definitely vote for them.

I also think that @export directive, as described, is crucial. This way we will be able precisely control output and this is good thing. I think it's possible to live without @export, when everything will be dumped from file in some big variable, like $grid, but it will immediately introduce some problems.

For instance, if module A imports some mixins, and then you import module A in your module into $extModule, should $extModule receive mixins, that imported module A?

If we will have @export, this question won't even raise, because we will explicitly say what we're exporting. Thus, we can pass mixins down, or avoid re-exporting them. It's up to us.

The only questions remained untouched (or I missed it) seems to be CSS content of imported file.

I see few options:

  1. It's not possible to import CSS content this way. Bam. That's all. Want to use it? Use already existing @import 'filename.scss, and it will print content of CSS in place of import.

  2. Always export content as CSS mixin by default. Then you can import it as @import CSS from 'filename.scss, and call where you need as @include CSS(), or pass to another export as @export CSS

    This means that @import 'filename' will be just a shortcut for same thing followed by immediate CSS mixin invocation.

  3. Another, but similar to second option: CSS always should be exported from file explicitly. There are few ways on my mind, but most obvious is by wrapping content into @export directive.

Also it seems that named exports remained uncovered.

I think this can be done by adding name to @export directive, in same way as it's done with @media, like @export (MyVariable) $someVariable and then @import { MyVariable} from 'filename.scss'

@ArmorDarks
Copy link

Also worth mention, that this whole @import and @export also solves related issues #139 and #1094, and in fact much more superior.

The whole idea with @import-once, mentioned in #1094, won't work well, as already explained here #2268. @import and @export provides better solution to this problem.

In same issue also mentioned (cite of nex3):

Both selectively and wholesale. The importer may want to @extend the rules in the importee but not have them concretely present, or it may want to suppress rules matching a certain selector.

Issue provides solution in form of

// A.scss
.foo {...}
.bar {...}

// B.scss
@use "A";
.baz { @extend .foo; }

I'm not sure that it should be possible at all. It sounds like a black magic, when you extend something, which actually does not exist.

I think it should be made in more transparent way. Declare in your Sass file placeholder selectors, like %myClass.

Then import those placeholders as @import CSS from 'myFile', and call CSS(). This will "print" (make available) placeholders into your current file, after which you will be able to use them.

Most important part here is that

  1. Those placeholders won't be actually printed to output, because they are just a placeholders, after all
  2. This way makes it explicit that you want to use this CSS() and thus allow to extend placeholders of particularly this CSS().

One more reason why idea with @use "A"; is problematic: colliding of names.

If you use @use "A"; and then @use "B";, in case A and B contained .foo, which exactly .foo will you extend in result? Both? How to control it?

With imports and exports of CSS you make it explicit. Import A CSS as @import CSS as ACSS from 'A' and B CSS as @import BCSS from 'B'

Now we can be explicit:

@include ACSS()
.baz { @extend .foo; } // will extend A .foo

@include BCSS()
.bar { @extend .foo; } // will extend A and B .foo (because they both available)

// Now there will be a bit of slightly unusual for Sass stuff, but you can catch the idea
.baz2 { @extend ACSS.foo; } // will extend only A .foo
.bar2 { @extend BCSS.foo; } // will extend only B .foo

In worst case, if we don't want to limit users to placeholders-only (this can be troublesome), we can add option to imported CSS mixin. Something like:

@include ACSS({ print: false })
.baz { @extend .foo; } // will extend A .foo

This won't print CSS, but will make it available as context for extension.

@ArmorDarks
Copy link

ArmorDarks commented Apr 8, 2017

Lastly, what bothers me the most: will we be able to make such large amount of @import performant?

For instance, even right now it is possible to encapsulate every CSS in global mixin manually (see my brief example at #739 (comment)).

Just for clarity, here is how it can be done now: let's say, we add our button styles to myButton mixin and place it into myButton.scss file, which doesn't have anything except mixins. Then you can @import file with that mixin in any other Sass file when you want to use that button, and then call myButton mixin whenever you need. Brilliant.

And, in fact, it covers plenty of scenarios. It already brings encapsulation we need without the need to throw everything into the global scope, except mixin itself.

However, there is a trouble with it: despite you're calling @import to the same file (let's say, we have 20 components, which uses that buttons. Thus, we're declaring same @import in 20 files), and that file DOES NOT contain anything to print, it will affect performance greatly and will significantly increase compiling time of Sass, effectively making the whole approach very painful to use in large projects. Apparently, because each import results in file loading, even despite that files already been loaded 19 times in a row before.

So, won't we bash into same trouble here?

@ArmorDarks
Copy link

ArmorDarks commented Jan 28, 2018

@nex3 I wonder what will be with this issue in light of closing #739 with the resolution to add @use directive.

How @use will handle variables and mixins — are they still will be thrown into global space, as with regular @import?

@nex3
Copy link
Contributor

nex3 commented Jan 30, 2018

The plan is to get rid of the notion of a global namespace, and have only names from files explicit referenced with @use be visible. We'll also have a mechanism for forwarding names so that frameworks can expose their entire APIs as a single module.

@nex3
Copy link
Contributor

nex3 commented Dec 26, 2018

Closing this in favor of #1094.

@nex3 nex3 closed this as completed Dec 26, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests