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

feat: add options validation (schema-utils) #184

Merged
merged 1 commit into from
Sep 29, 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
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\";',
);
});
});