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

Commit

Permalink
feat: add options validation (schema-utils) (#184)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-ciniawsky authored and joshwiens committed Sep 29, 2017
1 parent 39016b8 commit 696ca77
Show file tree
Hide file tree
Showing 8 changed files with 2,851 additions and 459 deletions.
3,120 changes: 2,726 additions & 394 deletions package-lock.json

Large diffs are not rendered by default.

21 changes: 11 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@
"version": "1.0.0",
"author": "Tobias Koppers @sokra",
"description": "file loader module for webpack",
"license": "MIT",
"engines": {
"node": ">= 4.3 < 5.0.0 || >= 5.10"
},
"main": "dist/cjs.js",
"files": [
"dist"
],
"directories": {
"test": "test"
},
"dependencies": {
"loader-utils": "^1.0.2"
"loader-utils": "^1.0.2",
"schema-utils": "^0.3.0"
},
"devDependencies": {
"babel-cli": "^6.24.1",
Expand All @@ -33,7 +42,7 @@
"scripts": {
"start": "npm run build -- -w",
"appveyor:test": "npm run test",
"build": "cross-env NODE_ENV=production babel src -d dist --ignore 'src/**/*.test.js'",
"build": "cross-env NODE_ENV=production babel src -d dist --ignore 'src/**/*.test.js' --copy-files",
"clean": "del-cli dist",
"lint": "eslint --cache src test",
"lint-staged": "lint-staged",
Expand All @@ -57,14 +66,6 @@
"url": "https://github.com/webpack/file-loader/issues"
},
"homepage": "https://github.com/webpack/file-loader",
"main": "dist/cjs.js",
"directories": {
"test": "test"
},
"license": "MIT",
"engines": {
"node": ">= 4.3 < 5.0.0 || >= 5.10"
},
"pre-commit": "lint-staged",
"lint-staged": {
"*.js": [
Expand Down
65 changes: 28 additions & 37 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,77 +1,68 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
import path from 'path';
import loaderUtils from 'loader-utils';
import validateOptions from 'schema-utils';
import schema from './options.json';

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

const query = loaderUtils.getOptions(this) || {};
const configKey = query.config || 'fileLoader';
const options = this.options[configKey] || {};
const options = loaderUtils.getOptions(this) || {};

const config = {
publicPath: undefined,
useRelativePath: false,
name: '[hash].[ext]',
};
validateOptions(schema, options, 'File Loader');

// options takes precedence over config
Object.keys(options).forEach((attr) => {
config[attr] = options[attr];
});

// query takes precedence over config and options
Object.keys(query).forEach((attr) => {
config[attr] = query[attr];
});
const context = options.context || this.options.context;

const context = config.context || this.options.context;
let url = loaderUtils.interpolateName(this, config.name, {
let url = loaderUtils.interpolateName(this, options.name, {
context,
content,
regExp: config.regExp,
regExp: options.regExp,
});

let outputPath = '';
if (config.outputPath) {

if (options.outputPath) {
// support functions as outputPath to generate them dynamically
outputPath = (
typeof config.outputPath === 'function' ? config.outputPath(url) : config.outputPath
typeof options.outputPath === 'function' ? options.outputPath(url) : options.outputPath
);
}

const filePath = this.resourcePath;
if (config.useRelativePath) {
const issuerContext = this._module && this._module.issuer
&& this._module.issuer.context || context; // eslint-disable-line no-mixed-operators

if (options.useRelativePath) {
const issuerContext = (this._module && this._module.issuer
&& this._module.issuer.context) || context;

const relativeUrl = issuerContext && path.relative(issuerContext, filePath).split(path.sep).join('/');

const relativePath = relativeUrl && `${path.dirname(relativeUrl)}/`;
if (~relativePath.indexOf('../')) { // eslint-disable-line no-bitwise
// eslint-disable-next-line no-bitwise
if (~relativePath.indexOf('../')) {
outputPath = path.posix.join(outputPath, relativePath, url);
} else {
outputPath = relativePath + url;
}

url = relativePath + url;
} else if (config.outputPath) {
} else if (options.outputPath) {
// support functions as outputPath to generate them dynamically
outputPath = (typeof config.outputPath === 'function' ? config.outputPath(url) : config.outputPath + url);
outputPath = typeof options.outputPath === 'function' ? options.outputPath(url) : options.outputPath + url;

url = outputPath;
} else {
outputPath = url;
}

let publicPath = `__webpack_public_path__ + ${JSON.stringify(url)}`;
if (config.publicPath !== undefined) {

if (options.publicPath !== undefined) {
// support functions as publicPath to generate them dynamically
publicPath = JSON.stringify(
typeof config.publicPath === 'function' ? config.publicPath(url) : config.publicPath + url,
typeof options.publicPath === 'function' ? options.publicPath(url) : options.publicPath + url,
);
}

if (query.emitFile === undefined || query.emitFile) {
if (options.emitFile === undefined || options.emitFile) {
this.emitFile(outputPath, content);
}

Expand Down
21 changes: 21 additions & 0 deletions src/options.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"regExp": {},
"context": {
"type": "string"
},
"publicPath": {},
"outputPath": {},
"useRelativePath": {
"type": "boolean"
},
"emitFile": {
"type": "boolean"
}
},
"additionalProperties": false
}
17 changes: 17 additions & 0 deletions test/Errors.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import loader from '../src';

describe('Errors', () => {
test('Validation Error', () => {
const err = () => loader.call({ query: { name: 1 }, emitFile: true });

expect(err).toThrow();
expect(err).toThrowErrorMatchingSnapshot();
});

test('Loader Error', () => {
const err = () => loader.call({ emitFile: false });

expect(err).toThrow();
expect(err).toThrowErrorMatchingSnapshot();
});
});
16 changes: 16 additions & 0 deletions test/__snapshots__/Errors.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Errors Loader Error 1`] = `
"File Loader
emitFile is required from module system"
`;

exports[`Errors Validation Error 1`] = `
"Validation Error
File Loader Invalid Options
options.name should be string
"
`;
9 changes: 6 additions & 3 deletions test/optional-file-emission.test.js → test/emitFile.test.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import fileLoader from '../src';
import loader from '../src';

const run = function run(resourcePath, query, content = new Buffer('1234')) {
let result = false;

const context = {
resourcePath,
query: `?${query}`,
query: `?${query || ''}`,
options: {
context: '/this/is/the/context',
},
emitFile() {
result = true;
},
};
fileLoader.call(context, content);

loader.call(context, content);

return result;
};

Expand Down
41 changes: 26 additions & 15 deletions test/correct-filename.test.js → test/loader.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* eslint-disable no-useless-escape, no-unused-vars */
import fileLoader from '../src';
import loader from '../src';

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

const context = {
resourcePath,
query: `?${query}`,
query: `?${query || ''}`,
options: {
context: '/this/is/the/context',
},
Expand All @@ -16,7 +16,7 @@ const run = function run(resourcePath, query, content = new Buffer('1234')) {
},
};

const result = fileLoader.call(context, content);
const result = loader.call(context, content);

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

const context = {
resourcePath,
query: options,
options: {
fileLoader: options,
context: '/this/is/the/context',
},
emitFile(url, content2) {
Expand All @@ -39,7 +39,7 @@ function runWithOptions(resourcePath, options, content = new Buffer('1234')) {
},
};

const result = fileLoader.call(context, content);
const result = loader.call(context, content);

return {
file,
Expand All @@ -57,6 +57,7 @@ describe('correct-filename', () => {
test('81dc9bdb52d04dc20036dbd8313ed055.txt', 'file.txt', '');
test('81dc9bdb52d04dc20036dbd8313ed055.bin', '', '');
});

it('should process name correctly', () => {
test('file.txt', '/file.txt', 'name=[name].[ext]');
test('file.png', '/file.png', 'name=[name].[ext]');
Expand All @@ -71,9 +72,11 @@ describe('correct-filename', () => {
test('_/_/dir/sub/file.txt', '/this/is/dir/sub/file.txt', 'name=[path][name].[ext]');
test('dir/sub/file.txt', '/this/is/dir/sub/file.txt', 'name=[path][name].[ext]&context=/this/is');
});

it('should process hash correctly', () => {
test('d93591bdf7860e1e4ee2fca799911215.txt', '/file.txt', '', new Buffer('4321'));
});

it('should process hash options correctly', () => {
test('81dc9.txt', '/file.txt', 'name=[hash:5].[ext]');
test('d4045.txt', '/file.txt', 'name=[sha512:hash:5].[ext]');
Expand All @@ -90,11 +93,13 @@ describe('publicPath option', () => {
'export default "http://cdn/81dc9bdb52d04dc20036dbd8313ed055.txt";',
);
});

it('should override public path when given empty string', () => {
expect(run('/file.txt', 'publicPath=').result).toEqual(
'export default "81dc9bdb52d04dc20036dbd8313ed055.txt";',
);
});

it('should use webpack public path when not set', () => {
expect(run('/file.txt').result).toEqual(
'export default __webpack_public_path__ + "81dc9bdb52d04dc20036dbd8313ed055.txt";',
Expand All @@ -107,12 +112,15 @@ describe('useRelativePath option', () => {
expect(run('/this/is/the/context/file.txt', 'useRelativePath=true').result).toEqual(
'export default __webpack_public_path__ + \"./81dc9bdb52d04dc20036dbd8313ed055.txt\";',
);

expect(run('/this/is/file.txt', 'useRelativePath=true').result).toEqual(
'export default __webpack_public_path__ + \"../../81dc9bdb52d04dc20036dbd8313ed055.txt\";',
);

expect(run('/this/file.txt', 'context=/this/is/the/&useRelativePath=true').result).toEqual(
'export default __webpack_public_path__ + \"../../81dc9bdb52d04dc20036dbd8313ed055.txt\";',
);

expect(run('/this/file.txt', 'context=/&useRelativePath=true').result).toEqual(
'export default __webpack_public_path__ + \"this/81dc9bdb52d04dc20036dbd8313ed055.txt\";',
);
Expand All @@ -121,20 +129,23 @@ describe('useRelativePath option', () => {

describe('outputPath function', () => {
it('should be supported', () => {
const outputFunc = value => '/path/set/by/func';
const options = {};
options.outputPath = outputFunc;
expect(runWithOptions('/this/is/the/context/file.txt', options).result).toEqual(
'export default __webpack_public_path__ + \"/path/set/by/func\";',
);
options.outputPath = value => '/path/set/by/func';

expect(runWithOptions('/this/is/the/context/file.txt', options).result)
.toEqual(
'export default __webpack_public_path__ + \"/path/set/by/func\";',
);
});

it('should be ignored if you set useRelativePath', () => {
const outputFunc = value => '/path/set/by/func';
const options = {};
options.outputPath = outputFunc;
options.outputPath = value => '/path/set/by/func';
options.useRelativePath = true;
expect(runWithOptions('/this/is/the/context/file.txt', options).result).toEqual(
'export default __webpack_public_path__ + \"./81dc9bdb52d04dc20036dbd8313ed055.txt\";',
);

expect(runWithOptions('/this/is/the/context/file.txt', options).result)
.toEqual(
'export default __webpack_public_path__ + \"./81dc9bdb52d04dc20036dbd8313ed055.txt\";',
);
});
});

0 comments on commit 696ca77

Please sign in to comment.