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

New loader parameter syntax, loader support. #12

Merged
merged 3 commits into from
Mar 3, 2017
Merged
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
70 changes: 62 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ Automatically require any resources related to the required one. See example bel

[Documentation: Using loaders](https://webpack.github.io/docs/using-loaders.html).

### Install
## Install

```sh
$ npm i -S baggage-loader
```

### Usage
## Example

Imagine that you have project structure like this and you're using webpack:

Expand All @@ -43,19 +43,18 @@ require('./styles.css');
var html = template({ foo: 'bar' });
```

Now you have to stop and give it to `baggage-loader`, so:
Now you can stop and let `baggage-loader` handle those `require`s, like so:

```javascript
module: {
preLoaders: [ {
loaders: [ {
test: /\/components\/.+script\.js$/,
// baggage?file=var&file-without-var&…
loader: 'baggage?template.html=template&styles.css'
loader: 'baggage?{"template.html":{"varName":"template"},"styles.css":{}}'
} ]
}
```

will become the necessary requires with variables declarations if corresponding files exists:
The example above will become the necessary requires, with variable declarations, if the corresponding files exist:

```javascript
// injected by preloader at the top of script.js
Expand All @@ -66,7 +65,46 @@ require('./styles.css');
var html = template({ foo: 'bar' };
```

Even more, there are placeholders `[dir]`, `[Dir]`, `[file]` and `[File]`, so you can use them in various tricky ways both with `file` and `var`:
## Usage
The 'baggage' -- the additional `require`s you want `baggage-loader` to insert -- is specified via the loader's query string. This query string must be written as a JSON string (see below for deprecated url-style query string syntax).

## Format
### Basic require (no options):
`?{"filename.ext":{}}`

This will insert `require('./filename.ext');` into each module to which the loader is applied

### Require with variable name:
`?{"filename.ext":{"varName":"foo"}}`

This will insert `var foo = require('./filename.ext');`

### Require with 'inline' loaders:
`?{"filename.ext":{"loaders":"style*css*sass"}}`

This will insert `require('style!css!sass!./filename.ext');`. Note that asterisks are replaced with exclamation points; the loader will append the final exclamation point between your loaders and the file path. If you are overriding existing loader config, you will need to prefix your loader string with `*` so that the loader string begins with `!` (the leading exclamation point is webpack's syntax for overriding loaders).

### Combined
Any of the above can be combined, for example:

`?{"filename.ext":{"varName":"foo","loaders":"style*css*sass}}`

will insert `var foo = require('style!css!sass!./filename.ext');`. You can also have more than one baggage file in your params:

`?{"filename.js":{},"filename.scss":{}}`

The above will insert

```javascript
require('./filename.js');
require('./filename.scss');
```

When defining a large amount of loader parameters, you may find it easier to define the JSON object and then stringify it for use in your loader config.

### Supported placeholders

The placeholder strings `[dir]`, `[Dir]`, `[file]` and `[File]` can be used in the keys of the loader params object (the file path) or in the `varName` value. The values for file and directory are taken from the module being loaded. For example:

```
alert/
Expand All @@ -83,3 +121,19 @@ loader: 'baggage?template[Dir].html=[file]Template&[dir][File]Styles.css'
var viewTemplate = require('./templateAlert.html');
require('./alertViewStyles.css');
```

## Pre-1.0 Usage
Before version 1.0, the loader supported both JSON-style query strings and url-style query strings, and the syntax was different. The breaking change for the loader's parameters was made to support a greater range of parmaters to control the loader's behavior. The older syntax is still supported, but **only** when params are specfied as a url-style query string. (In other words, all url-style params will be treated as the old syntax, all JSON-style params will be treated as the 1.x+ syntax.) In the future, support for the older styntax will likely be removed; users are encouraged to update to the 1.x syntax.

`?template.html=template&styles.css`

The above would insert

```
var template = require('./template.html');
require('./styles.css');
```

Note that the argument 'name' in this syntax is the file path, and if you assign a 'value', that value becomes the variable name. The file and directory placeholders may be used in this syntax just as in the 1.x+ syntax.

Note that the above example demonstrates all of the functionality of the legacy syntax. Newer features, such as specifying loaders, are not supported.
101 changes: 101 additions & 0 deletions compat/legacy-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* Legacy version of baggage-loader (to 0.2.4). Does not support advanced config via JSON.
* Deprecated; support will likely be removed at some point in the future. Use v1+ JSON-based
* config instead.
*/

'use strict';

var path = require('path');
var fs = require('fs');
var loaderUtils = require('loader-utils');
var SourceMap = require('source-map');

var util = require('../lib/util');

module.exports = function(source, sourceMap) {
var query = loaderUtils.parseQuery(this.query);

// /foo/bar/file.js
var srcFilepath = this.resourcePath;
// /foo/bar/file.js -> file
var srcFilename = path.basename(srcFilepath, path.extname(srcFilepath));
// /foo/bar/file.js -> /foo/bar
var srcDirpath = path.dirname(srcFilepath);
// /foo/bar -> bar
var srcDirname = srcDirpath.split(path.sep).pop();

if (this.cacheable) {
this.cacheable();
}

if (Object.keys(query).length) {
var inject = '\n/* injects from baggage-loader */\n';

Object.keys(query).forEach(function(baggageFile) {
var baggageVar = query[baggageFile];

// TODO: not so quick and dirty validation
if (typeof baggageVar === 'string' || baggageVar === true) {
// apply filename placeholders
baggageFile = util.applyPlaceholders(baggageFile, srcDirname, srcFilename);

// apply var placeholders
if (baggageVar.length) {
baggageVar = util.applyPlaceholders(baggageVar, srcDirname, srcFilename);
}

try {
// check if absoluted from srcDirpath + baggageFile path exists
var stats = fs.statSync(path.resolve(srcDirpath, baggageFile));

if (stats.isFile()) {
// assign it to variable
if (baggageVar.length) {
inject += 'var ' + baggageVar + ' = ';
}

// and require
inject += 'require(\'./' + baggageFile + '\');\n';
}
} catch (e) {}
}
});

inject += '\n';

// support existing SourceMap
// https://github.com/mozilla/source-map#sourcenode
// https://github.com/webpack/imports-loader/blob/master/index.js#L34-L44
// https://webpack.github.io/docs/loaders.html#writing-a-loader
if (sourceMap) {
var currentRequest = loaderUtils.getCurrentRequest(this);
var SourceNode = SourceMap.SourceNode;
var SourceMapConsumer = SourceMap.SourceMapConsumer;
var sourceMapConsumer = new SourceMapConsumer(sourceMap);
var node = SourceNode.fromStringWithSourceMap(source, sourceMapConsumer);

node.prepend(inject);

var result = node.toStringWithSourceMap({
file: currentRequest
});

this.callback(null, result.code, result.map.toJSON());

return;
}

// prepend collected inject at the top of file
return inject + source;
}

// return the original source and sourceMap
if (sourceMap) {
this.callback(null, source, sourceMap);
return;
}

// return the original source
return source;
};
114 changes: 71 additions & 43 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
/* eslint-disable consistent-return */
'use strict';

var path = require('path');
var fs = require('fs');
var loaderUtils = require('loader-utils');
var SourceMap = require('source-map');

var legacyLoader = require('./compat/legacy-loader.js');
var util = require('./lib/util');

var injectBanner = '\n/* injects from baggage-loader */\n';

module.exports = function(source, sourceMap) {
// parseQuery will always give us an object, for back-compat we
// want to know if we're working with JSON query or query string
if (!util.isJSONString(this.query.replace('?', ''))) {
return legacyLoader.call(this, source, sourceMap);
}

var query = loaderUtils.parseQuery(this.query);

// /foo/bar/file.js
Expand All @@ -22,65 +33,82 @@ module.exports = function(source, sourceMap) {
this.cacheable();
}

if (Object.keys(query).length) {
var inject = '\n/* injects from baggage-loader */\n';

Object.keys(query).forEach(function(baggageFile) {
var baggageVar = query[baggageFile];
var filePaths = Object.keys(query);
var injections = [];
if (filePaths.length) {
injections = filePaths.map(function(filePath) {

// TODO: not so quick and dirty validation
if (typeof baggageVar === 'string' || baggageVar === true) {
// apply filename placeholders
baggageFile = util.applyPlaceholders(baggageFile, srcDirname, srcFilename);
var varName;
var loadersForFile = '';
var inject = null;

// apply var placeholders
if (baggageVar.length) {
baggageVar = util.applyPlaceholders(baggageVar, srcDirname, srcFilename);
if (typeof query[filePath] === 'object') {
var fileConfig = query[filePath];
var loaderStringForFile = fileConfig.loaders || '';
if (loaderStringForFile) {
loadersForFile = loaderStringForFile.replace(/\*/g, '!') + '!';
}

try {
// check if absoluted from srcDirpath + baggageFile path exists
var stats = fs.statSync(path.resolve(srcDirpath, baggageFile));
varName = fileConfig.varName;
}

filePath = util.applyPlaceholders(filePath, srcDirname, srcFilename);
if (varName) {
varName = util.applyPlaceholders(varName, srcDirname, srcFilename);
}

// @todo support mandatory/optional requires via config
try {

if (stats.isFile()) {
// assign it to variable
if (baggageVar.length) {
inject += 'var ' + baggageVar + ' = ';
}
// check if absoluted from srcDirpath + baggageFile path exists
var stats = fs.statSync(path.resolve(srcDirpath, filePath));

// and require
inject += 'require(\'./' + baggageFile + '\');\n';
if (stats.isFile()) {
inject = '';
if (varName) {
inject = 'var ' + varName + ' = ';
}
} catch (e) {}
}

inject += 'require(\'' + loadersForFile + './' + filePath + '\');\n';
}
} catch (e) {}

return inject;
});

inject += '\n';
injections.filter(function(inject) {
return typeof inject === 'string';
});

if (injections.length) {
var srcInjection = injectBanner + injections.join('\n');

// support existing SourceMap
// https://github.com/mozilla/source-map#sourcenode
// https://github.com/webpack/imports-loader/blob/master/index.js#L34-L44
// https://webpack.github.io/docs/loaders.html#writing-a-loader
if (sourceMap) {
var currentRequest = loaderUtils.getCurrentRequest(this);
var SourceNode = SourceMap.SourceNode;
var SourceMapConsumer = SourceMap.SourceMapConsumer;
var sourceMapConsumer = new SourceMapConsumer(sourceMap);
var node = SourceNode.fromStringWithSourceMap(source, sourceMapConsumer);
// support existing SourceMap
// https://github.com/mozilla/source-map#sourcenode
// https://github.com/webpack/imports-loader/blob/master/index.js#L34-L44
// https://webpack.github.io/docs/loaders.html#writing-a-loader
if (sourceMap) {
var currentRequest = loaderUtils.getCurrentRequest(this);
var SourceNode = SourceMap.SourceNode;
var SourceMapConsumer = SourceMap.SourceMapConsumer;
var sourceMapConsumer = new SourceMapConsumer(sourceMap);
var node = SourceNode.fromStringWithSourceMap(source, sourceMapConsumer);

node.prepend(inject);
node.prepend(srcInjection);

var result = node.toStringWithSourceMap({
file: currentRequest
});
var result = node.toStringWithSourceMap({
file: currentRequest
});

this.callback(null, result.code, result.map.toJSON());
this.callback(null, result.code, result.map.toJSON());

return;
}

return;
// prepend collected inject at the top of file
return srcInjection + source;
}

// prepend collected inject at the top of file
return inject + source;
}

// return the original source and sourceMap
Expand Down
23 changes: 18 additions & 5 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,27 @@ var capitalize = exports.capitalize = function(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
};

exports.applyPlaceholders = function(str, dirname, filename) {
if (!str.length) {
return str;
}
var util = {
applyPlaceholders: function(str, dirname, filename) {
if (!str.length) {
return str;
}

return str
return str
.split('[dir]').join(dirname)
.split('[Dir]').join(capitalize(dirname))
.split('[file]').join(filename)
.split('[File]').join(capitalize(filename));
},

isJSONString: function(str) {
try {
JSON.parse(str);
return true;
} catch (err) {
return false;
}
}
};

module.exports = util;
Loading