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

Provide loader context #190

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,81 @@ webpack provides an [advanced mechanism to resolve files](http://webpack.github.

It's important to only prepend it with `~`, because `~/` resolves to the home-directory. webpack needs to distinguish between `bootstrap` and `~bootstrap` because CSS- and Sass-files have no special syntax for importing relative files. Writing `@import "file"` is the same as `@import "./file";`

#### Advanced: Doing wild things with a custom node-sass importer

node-sass's `importer` API allows you to define custom handlers for Sass's `@import` directive. While sass-loader defines its own importer callback to integrate `@import` with webpack's resolve mechanism, it will also pass along your own importer callback to node-sass. This allows your code a chance to "steal" the import and handle it, or pass on it and let sass-loader handle it as normal.

Why would you want to do this? Well, maybe you want your Sass to be able to `@import` something other than Sass or normal stylesheets... like Stylus, or LESS. You could write an importer that checks for the appropriate file extension, and invokes another compiler, replacing the `@import` with compiled CSS before it gets to node-sass. Or you could even transpile it to Sass.

Luckily, sass-loader won't get in your way if you want to do this. sass-loader will pass along your importer to node-sass and (as of 3.1.3) allow you to access webpack's loader API via `this.options.loaderContext`. For now, you'll have to handle the path resolution logic yourself, though, since path resolution and actual processing of imports are tightly coupled together.

Include your importer function in your sassLoader options, like this:

```javascript
var sass = require('node-sass');
module.exports = {
...
module: {
loaders: [{
test: /\.scss$/,
loaders: ['style', 'css', 'sass']
}]
},
sassLoader: {
importer: stylusImporter
}
};
```
And then, use loader-util's and webpack's resolve in your importer:

```javascript
var loaderUtils = require('loader-utils');
var sass = require('node-sass');

function specialImporter (url, fileContext, done) {
if (!shouldThisFileBeHandledByMyImporter(url)) {
// Return sass.NULL in order to declare you wish to "pass" on this url and
// let other importers handle it. Be careful, as this doesn't work correctly in
// an environment with multiple copies of node-sass (e.g., if your importer is
// inside of a symlinked package). This can manifest as a strange Sass error
// like "No such mixin foo", when `foo` is @import'd from another Sass file.
return sass.NULL;
}

// Let's run the URL through webpack's resolvers. Sass-loader includes the
// dirname of the entry point file (the initially require()'d scss file) in
// includePaths as the last entry.
var includePaths = this.options.includePaths.split(':');

// If we're given fileContext (and it's not 'stdin'), then fileContext is the
// path of the file doing the import--i.e., the import we're processing is the
// result of an @import from within Sass, rather than of a require() from
// within JS.
var workingDir = fileContext && fileContext !== 'stdin'
? path.dirname(fileContext)
: includePaths[includePaths.length - 1];
var filePath = loaderUtils.urlToRequest(url, workingDir);
if (filePath[0] === '.') {
filePath = path.resolve(workingDir, filePath);
}

var loaderContext = this.options.loaderContext;
var resolve = loaderContext.resolve.bind(loaderContext);

resolve(workingDir, filePath, function resolveCallback (err, filename) {
if (err) done(err);

// Tell Webpack about the file being part of the build.
this.options.loaderContext.addDependency(filename);

// Return the result or error via the `done` function:
done({ contents: '/* Resulting Sass code goes here */' });
// If there's an error, send an error object or the error you caught:
// done(new Error('Helpful error message about the file'));
}.bind(this));
}
```

### Problems with `url(...)`

Since Sass/[libsass](https://github.com/sass/libsass) does not provide [url rewriting](https://github.com/sass/libsass/issues/532), all linked assets must be relative to the output.
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,5 +381,5 @@ function getLoaderConfig(loaderContext) {

delete query.config;

return assign({}, config, query);
return assign({ loaderContext: loaderContext }, config, query);
}