Skip to content
This repository was archived by the owner on Mar 17, 2021. It is now read-only.

Commit 696ca77

Browse files
michael-ciniawskyjoshwiens
authored andcommitted
feat: add options validation (schema-utils) (#184)
1 parent 39016b8 commit 696ca77

File tree

8 files changed

+2851
-459
lines changed

8 files changed

+2851
-459
lines changed

package-lock.json

Lines changed: 2726 additions & 394 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,20 @@
33
"version": "1.0.0",
44
"author": "Tobias Koppers @sokra",
55
"description": "file loader module for webpack",
6+
"license": "MIT",
7+
"engines": {
8+
"node": ">= 4.3 < 5.0.0 || >= 5.10"
9+
},
10+
"main": "dist/cjs.js",
611
"files": [
712
"dist"
813
],
14+
"directories": {
15+
"test": "test"
16+
},
917
"dependencies": {
10-
"loader-utils": "^1.0.2"
18+
"loader-utils": "^1.0.2",
19+
"schema-utils": "^0.3.0"
1120
},
1221
"devDependencies": {
1322
"babel-cli": "^6.24.1",
@@ -33,7 +42,7 @@
3342
"scripts": {
3443
"start": "npm run build -- -w",
3544
"appveyor:test": "npm run test",
36-
"build": "cross-env NODE_ENV=production babel src -d dist --ignore 'src/**/*.test.js'",
45+
"build": "cross-env NODE_ENV=production babel src -d dist --ignore 'src/**/*.test.js' --copy-files",
3746
"clean": "del-cli dist",
3847
"lint": "eslint --cache src test",
3948
"lint-staged": "lint-staged",
@@ -57,14 +66,6 @@
5766
"url": "https://github.com/webpack/file-loader/issues"
5867
},
5968
"homepage": "https://github.com/webpack/file-loader",
60-
"main": "dist/cjs.js",
61-
"directories": {
62-
"test": "test"
63-
},
64-
"license": "MIT",
65-
"engines": {
66-
"node": ">= 4.3 < 5.0.0 || >= 5.10"
67-
},
6869
"pre-commit": "lint-staged",
6970
"lint-staged": {
7071
"*.js": [

src/index.js

Lines changed: 28 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,68 @@
1-
/*
2-
MIT License http://www.opensource.org/licenses/mit-license.php
3-
Author Tobias Koppers @sokra
4-
*/
51
import path from 'path';
62
import loaderUtils from 'loader-utils';
3+
import validateOptions from 'schema-utils';
4+
import schema from './options.json';
75

8-
export default function fileLoader(content) {
9-
if (!this.emitFile) throw new Error('emitFile is required from module system');
6+
export default function loader(content) {
7+
if (!this.emitFile) throw new Error('File Loader\n\nemitFile is required from module system');
108

11-
const query = loaderUtils.getOptions(this) || {};
12-
const configKey = query.config || 'fileLoader';
13-
const options = this.options[configKey] || {};
9+
const options = loaderUtils.getOptions(this) || {};
1410

15-
const config = {
16-
publicPath: undefined,
17-
useRelativePath: false,
18-
name: '[hash].[ext]',
19-
};
11+
validateOptions(schema, options, 'File Loader');
2012

21-
// options takes precedence over config
22-
Object.keys(options).forEach((attr) => {
23-
config[attr] = options[attr];
24-
});
25-
26-
// query takes precedence over config and options
27-
Object.keys(query).forEach((attr) => {
28-
config[attr] = query[attr];
29-
});
13+
const context = options.context || this.options.context;
3014

31-
const context = config.context || this.options.context;
32-
let url = loaderUtils.interpolateName(this, config.name, {
15+
let url = loaderUtils.interpolateName(this, options.name, {
3316
context,
3417
content,
35-
regExp: config.regExp,
18+
regExp: options.regExp,
3619
});
3720

3821
let outputPath = '';
39-
if (config.outputPath) {
22+
23+
if (options.outputPath) {
4024
// support functions as outputPath to generate them dynamically
4125
outputPath = (
42-
typeof config.outputPath === 'function' ? config.outputPath(url) : config.outputPath
26+
typeof options.outputPath === 'function' ? options.outputPath(url) : options.outputPath
4327
);
4428
}
4529

4630
const filePath = this.resourcePath;
47-
if (config.useRelativePath) {
48-
const issuerContext = this._module && this._module.issuer
49-
&& this._module.issuer.context || context; // eslint-disable-line no-mixed-operators
31+
32+
if (options.useRelativePath) {
33+
const issuerContext = (this._module && this._module.issuer
34+
&& this._module.issuer.context) || context;
35+
5036
const relativeUrl = issuerContext && path.relative(issuerContext, filePath).split(path.sep).join('/');
37+
5138
const relativePath = relativeUrl && `${path.dirname(relativeUrl)}/`;
52-
if (~relativePath.indexOf('../')) { // eslint-disable-line no-bitwise
39+
// eslint-disable-next-line no-bitwise
40+
if (~relativePath.indexOf('../')) {
5341
outputPath = path.posix.join(outputPath, relativePath, url);
5442
} else {
5543
outputPath = relativePath + url;
5644
}
45+
5746
url = relativePath + url;
58-
} else if (config.outputPath) {
47+
} else if (options.outputPath) {
5948
// support functions as outputPath to generate them dynamically
60-
outputPath = (typeof config.outputPath === 'function' ? config.outputPath(url) : config.outputPath + url);
49+
outputPath = typeof options.outputPath === 'function' ? options.outputPath(url) : options.outputPath + url;
50+
6151
url = outputPath;
6252
} else {
6353
outputPath = url;
6454
}
6555

6656
let publicPath = `__webpack_public_path__ + ${JSON.stringify(url)}`;
67-
if (config.publicPath !== undefined) {
57+
58+
if (options.publicPath !== undefined) {
6859
// support functions as publicPath to generate them dynamically
6960
publicPath = JSON.stringify(
70-
typeof config.publicPath === 'function' ? config.publicPath(url) : config.publicPath + url,
61+
typeof options.publicPath === 'function' ? options.publicPath(url) : options.publicPath + url,
7162
);
7263
}
7364

74-
if (query.emitFile === undefined || query.emitFile) {
65+
if (options.emitFile === undefined || options.emitFile) {
7566
this.emitFile(outputPath, content);
7667
}
7768

src/options.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"name": {
5+
"type": "string"
6+
},
7+
"regExp": {},
8+
"context": {
9+
"type": "string"
10+
},
11+
"publicPath": {},
12+
"outputPath": {},
13+
"useRelativePath": {
14+
"type": "boolean"
15+
},
16+
"emitFile": {
17+
"type": "boolean"
18+
}
19+
},
20+
"additionalProperties": false
21+
}

test/Errors.test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import loader from '../src';
2+
3+
describe('Errors', () => {
4+
test('Validation Error', () => {
5+
const err = () => loader.call({ query: { name: 1 }, emitFile: true });
6+
7+
expect(err).toThrow();
8+
expect(err).toThrowErrorMatchingSnapshot();
9+
});
10+
11+
test('Loader Error', () => {
12+
const err = () => loader.call({ emitFile: false });
13+
14+
expect(err).toThrow();
15+
expect(err).toThrowErrorMatchingSnapshot();
16+
});
17+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Errors Loader Error 1`] = `
4+
"File Loader
5+
6+
emitFile is required from module system"
7+
`;
8+
9+
exports[`Errors Validation Error 1`] = `
10+
"Validation Error
11+
12+
File Loader Invalid Options
13+
14+
options.name should be string
15+
"
16+
`;

test/optional-file-emission.test.js renamed to test/emitFile.test.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
import fileLoader from '../src';
1+
import loader from '../src';
22

33
const run = function run(resourcePath, query, content = new Buffer('1234')) {
44
let result = false;
5+
56
const context = {
67
resourcePath,
7-
query: `?${query}`,
8+
query: `?${query || ''}`,
89
options: {
910
context: '/this/is/the/context',
1011
},
1112
emitFile() {
1213
result = true;
1314
},
1415
};
15-
fileLoader.call(context, content);
16+
17+
loader.call(context, content);
18+
1619
return result;
1720
};
1821

test/correct-filename.test.js renamed to test/loader.test.js

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/* eslint-disable no-useless-escape, no-unused-vars */
2-
import fileLoader from '../src';
2+
import loader from '../src';
33

44
const run = function run(resourcePath, query, content = new Buffer('1234')) {
55
let file = null;
66

77
const context = {
88
resourcePath,
9-
query: `?${query}`,
9+
query: `?${query || ''}`,
1010
options: {
1111
context: '/this/is/the/context',
1212
},
@@ -16,7 +16,7 @@ const run = function run(resourcePath, query, content = new Buffer('1234')) {
1616
},
1717
};
1818

19-
const result = fileLoader.call(context, content);
19+
const result = loader.call(context, content);
2020

2121
return {
2222
file,
@@ -29,8 +29,8 @@ function runWithOptions(resourcePath, options, content = new Buffer('1234')) {
2929

3030
const context = {
3131
resourcePath,
32+
query: options,
3233
options: {
33-
fileLoader: options,
3434
context: '/this/is/the/context',
3535
},
3636
emitFile(url, content2) {
@@ -39,7 +39,7 @@ function runWithOptions(resourcePath, options, content = new Buffer('1234')) {
3939
},
4040
};
4141

42-
const result = fileLoader.call(context, content);
42+
const result = loader.call(context, content);
4343

4444
return {
4545
file,
@@ -57,6 +57,7 @@ describe('correct-filename', () => {
5757
test('81dc9bdb52d04dc20036dbd8313ed055.txt', 'file.txt', '');
5858
test('81dc9bdb52d04dc20036dbd8313ed055.bin', '', '');
5959
});
60+
6061
it('should process name correctly', () => {
6162
test('file.txt', '/file.txt', 'name=[name].[ext]');
6263
test('file.png', '/file.png', 'name=[name].[ext]');
@@ -71,9 +72,11 @@ describe('correct-filename', () => {
7172
test('_/_/dir/sub/file.txt', '/this/is/dir/sub/file.txt', 'name=[path][name].[ext]');
7273
test('dir/sub/file.txt', '/this/is/dir/sub/file.txt', 'name=[path][name].[ext]&context=/this/is');
7374
});
75+
7476
it('should process hash correctly', () => {
7577
test('d93591bdf7860e1e4ee2fca799911215.txt', '/file.txt', '', new Buffer('4321'));
7678
});
79+
7780
it('should process hash options correctly', () => {
7881
test('81dc9.txt', '/file.txt', 'name=[hash:5].[ext]');
7982
test('d4045.txt', '/file.txt', 'name=[sha512:hash:5].[ext]');
@@ -90,11 +93,13 @@ describe('publicPath option', () => {
9093
'export default "http://cdn/81dc9bdb52d04dc20036dbd8313ed055.txt";',
9194
);
9295
});
96+
9397
it('should override public path when given empty string', () => {
9498
expect(run('/file.txt', 'publicPath=').result).toEqual(
9599
'export default "81dc9bdb52d04dc20036dbd8313ed055.txt";',
96100
);
97101
});
102+
98103
it('should use webpack public path when not set', () => {
99104
expect(run('/file.txt').result).toEqual(
100105
'export default __webpack_public_path__ + "81dc9bdb52d04dc20036dbd8313ed055.txt";',
@@ -107,12 +112,15 @@ describe('useRelativePath option', () => {
107112
expect(run('/this/is/the/context/file.txt', 'useRelativePath=true').result).toEqual(
108113
'export default __webpack_public_path__ + \"./81dc9bdb52d04dc20036dbd8313ed055.txt\";',
109114
);
115+
110116
expect(run('/this/is/file.txt', 'useRelativePath=true').result).toEqual(
111117
'export default __webpack_public_path__ + \"../../81dc9bdb52d04dc20036dbd8313ed055.txt\";',
112118
);
119+
113120
expect(run('/this/file.txt', 'context=/this/is/the/&useRelativePath=true').result).toEqual(
114121
'export default __webpack_public_path__ + \"../../81dc9bdb52d04dc20036dbd8313ed055.txt\";',
115122
);
123+
116124
expect(run('/this/file.txt', 'context=/&useRelativePath=true').result).toEqual(
117125
'export default __webpack_public_path__ + \"this/81dc9bdb52d04dc20036dbd8313ed055.txt\";',
118126
);
@@ -121,20 +129,23 @@ describe('useRelativePath option', () => {
121129

122130
describe('outputPath function', () => {
123131
it('should be supported', () => {
124-
const outputFunc = value => '/path/set/by/func';
125132
const options = {};
126-
options.outputPath = outputFunc;
127-
expect(runWithOptions('/this/is/the/context/file.txt', options).result).toEqual(
128-
'export default __webpack_public_path__ + \"/path/set/by/func\";',
129-
);
133+
options.outputPath = value => '/path/set/by/func';
134+
135+
expect(runWithOptions('/this/is/the/context/file.txt', options).result)
136+
.toEqual(
137+
'export default __webpack_public_path__ + \"/path/set/by/func\";',
138+
);
130139
});
140+
131141
it('should be ignored if you set useRelativePath', () => {
132-
const outputFunc = value => '/path/set/by/func';
133142
const options = {};
134-
options.outputPath = outputFunc;
143+
options.outputPath = value => '/path/set/by/func';
135144
options.useRelativePath = true;
136-
expect(runWithOptions('/this/is/the/context/file.txt', options).result).toEqual(
137-
'export default __webpack_public_path__ + \"./81dc9bdb52d04dc20036dbd8313ed055.txt\";',
138-
);
145+
146+
expect(runWithOptions('/this/is/the/context/file.txt', options).result)
147+
.toEqual(
148+
'export default __webpack_public_path__ + \"./81dc9bdb52d04dc20036dbd8313ed055.txt\";',
149+
);
139150
});
140151
});

0 commit comments

Comments
 (0)