diff --git a/.eslintcache b/.eslintcache
new file mode 100644
index 0000000..e92bebb
--- /dev/null
+++ b/.eslintcache
@@ -0,0 +1 @@
+{"C:\\Users\\Adrian Miranda\\Sites\\ambox\\github\\adriancmiranda\\file-loader\\src\\cjs.js":{"size":45,"mtime":1496936755980,"hashOfConfig":"16xjw5j","results":{"filePath":"C:\\Users\\Adrian Miranda\\Sites\\ambox\\github\\adriancmiranda\\file-loader\\src\\cjs.js","messages":[],"errorCount":0,"warningCount":0}},"C:\\Users\\Adrian Miranda\\Sites\\ambox\\github\\adriancmiranda\\file-loader\\src\\helper.js":{"size":334,"mtime":1496934738398,"hashOfConfig":"16xjw5j","results":{"filePath":"C:\\Users\\Adrian Miranda\\Sites\\ambox\\github\\adriancmiranda\\file-loader\\src\\helper.js","messages":[],"errorCount":0,"warningCount":0}},"C:\\Users\\Adrian Miranda\\Sites\\ambox\\github\\adriancmiranda\\file-loader\\src\\index.js":{"size":3601,"mtime":1496941794969,"hashOfConfig":"16xjw5j","results":{"filePath":"C:\\Users\\Adrian Miranda\\Sites\\ambox\\github\\adriancmiranda\\file-loader\\src\\index.js","messages":[],"errorCount":0,"warningCount":0}},"C:\\Users\\Adrian Miranda\\Sites\\ambox\\github\\adriancmiranda\\file-loader\\test\\correct-filename.test.js":{"size":8332,"mtime":1496936927390,"hashOfConfig":"16xjw5j","results":{"filePath":"C:\\Users\\Adrian Miranda\\Sites\\ambox\\github\\adriancmiranda\\file-loader\\test\\correct-filename.test.js","messages":[],"errorCount":0,"warningCount":0}},"C:\\Users\\Adrian Miranda\\Sites\\ambox\\github\\adriancmiranda\\file-loader\\test\\optional-file-emission.test.js":{"size":628,"mtime":1496936290558,"hashOfConfig":"16xjw5j","results":{"filePath":"C:\\Users\\Adrian Miranda\\Sites\\ambox\\github\\adriancmiranda\\file-loader\\test\\optional-file-emission.test.js","messages":[],"errorCount":0,"warningCount":0}},"C:\\Users\\Adrian Miranda\\Sites\\ambox\\github\\adriancmiranda\\file-loader\\test\\helper.test.js":{"size":847,"mtime":1496949000783,"hashOfConfig":"16xjw5j","results":{"filePath":"C:\\Users\\Adrian Miranda\\Sites\\ambox\\github\\adriancmiranda\\file-loader\\test\\helper.test.js","messages":[],"errorCount":0,"warningCount":0}}}
\ No newline at end of file
diff --git a/.eslintignore b/.eslintignore
index b2d59d1..8225baa 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,2 +1,2 @@
/node_modules
-/dist
\ No newline at end of file
+/dist
diff --git a/.gitignore b/.gitignore
index c6c08c9..836cf4b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,18 +1,28 @@
+# samples
+examples/*/dist/*
+
# development
-/node_modules
+node_modules/
/coverage
-
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-.eslintcache
/dist
/local
/reports
-.DS_Store
+logs
+*.log
+*.log*
Thumbs.db
-.idea
-.vscode
*.sublime-project
-*.sublime-workspace
\ No newline at end of file
+*.sublime-workspace
+
+# dotfiles
+.*
+!.babelrc
+!.editorconfig
+!.eslintcache
+!.eslintignore
+!.eslintrc
+!.gitattributes
+!.gitignore
+!.travis.yml
+*~
+*#*
diff --git a/README.md b/README.md
index c4a6ed1..dc5fe2a 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,8 @@ use: "file-loader?name=[name].[ext]&publicPath=assets/foo/&outputPath=app/images
}
```
+`cssOutputPath` also should be used (together with `useRelativePath` property) to define the output path context.
+
#### Filename template placeholders
* `[ext]` the extension of the resource
diff --git a/examples/relative-path-with-multiple-css-outputs/package.json b/examples/relative-path-with-multiple-css-outputs/package.json
new file mode 100644
index 0000000..0a94b47
--- /dev/null
+++ b/examples/relative-path-with-multiple-css-outputs/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "use-relative-path",
+ "author": "@adriancmiranda",
+ "scripts": {
+ "dev": "webpack-dev-server --open --env.dev",
+ "prebuild": "rimraf dist/",
+ "build": "webpack -p"
+ },
+ "optionalDependencies": {
+ "css-loader": "0.28.0",
+ "extract-text-webpack-plugin": "2.1.0",
+ "html-webpack-plugin": "2.28.0",
+ "ip": "1.1.5",
+ "rimraf": "2.6.1",
+ "style-loader": "0.16.1",
+ "webpack-dev-server": "2.4.2"
+ },
+ "devDependencies": {
+ "webpack": "2.4.1"
+ }
+}
diff --git a/examples/relative-path-with-multiple-css-outputs/source/scripts/desktop.js b/examples/relative-path-with-multiple-css-outputs/source/scripts/desktop.js
new file mode 100644
index 0000000..eb28b71
--- /dev/null
+++ b/examples/relative-path-with-multiple-css-outputs/source/scripts/desktop.js
@@ -0,0 +1 @@
+console.log('Desktop');
diff --git a/examples/relative-path-with-multiple-css-outputs/source/scripts/mobile.js b/examples/relative-path-with-multiple-css-outputs/source/scripts/mobile.js
new file mode 100644
index 0000000..14dedd7
--- /dev/null
+++ b/examples/relative-path-with-multiple-css-outputs/source/scripts/mobile.js
@@ -0,0 +1 @@
+console.log('Mobile');
diff --git a/examples/relative-path-with-multiple-css-outputs/source/styles/desktop.css b/examples/relative-path-with-multiple-css-outputs/source/styles/desktop.css
new file mode 100644
index 0000000..b0b6180
--- /dev/null
+++ b/examples/relative-path-with-multiple-css-outputs/source/styles/desktop.css
@@ -0,0 +1,50 @@
+/* Desktop styles */
+*,
+*:after,
+*:before {
+ -webkit-tap-highlight-color: transparent;
+ tap-highlight-color: transparent;
+ box-sizing: border-box;
+ outline-style: none;
+}
+
+html,
+body {
+ width: 100%;
+ height: 100%;
+}
+
+body {
+ font-family: Helvetica, sans-serif;
+ font-size: 16px;
+ line-height: 10px;
+ margin: 0;
+}
+
+div,
+canvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: rgb(77, 155, 216);
+}
+
+div {
+ background: rgba(0,0,0,0.2) url("../../../../.github/assets/file_loader_icon.svg") no-repeat 50% 50%;
+}
+
+img {
+ position: absolute;
+ right: 10px;
+ bottom: 10px;
+ display: block;
+}
+
+div:before {
+ position: absolute;
+ display: block;
+ margin-top: 10px;
+ margin-left: 10px;
+ color: #fff;
+ content: "file-loader / examples / relative-path-with-multiple-output / desktop";
+}
diff --git a/examples/relative-path-with-multiple-css-outputs/source/styles/mobile.css b/examples/relative-path-with-multiple-css-outputs/source/styles/mobile.css
new file mode 100644
index 0000000..58fb63d
--- /dev/null
+++ b/examples/relative-path-with-multiple-css-outputs/source/styles/mobile.css
@@ -0,0 +1,50 @@
+/* Desktop styles */
+*,
+*:after,
+*:before {
+ -webkit-tap-highlight-color: transparent;
+ tap-highlight-color: transparent;
+ box-sizing: border-box;
+ outline-style: none;
+}
+
+html,
+body {
+ width: 100%;
+ height: 100%;
+}
+
+body {
+ font-family: Helvetica, sans-serif;
+ font-size: 16px;
+ line-height: 10px;
+ margin: 0;
+}
+
+div,
+canvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: rgb(142, 214, 251);
+}
+
+div {
+ background: rgba(0,0,0,0.2) url("../../../../.github/assets/file_loader_icon.svg") no-repeat 50% 50%;
+}
+
+img {
+ position: absolute;
+ right: 10px;
+ bottom: 10px;
+ display: block;
+}
+
+div:before {
+ position: absolute;
+ display: block;
+ margin-top: 10px;
+ margin-left: 10px;
+ color: #fff;
+ content: "file-loader / examples / relative-path-with-multiple-output / mobile";
+}
diff --git a/examples/relative-path-with-multiple-css-outputs/source/views/desktop.html b/examples/relative-path-with-multiple-css-outputs/source/views/desktop.html
new file mode 100644
index 0000000..42c8565
--- /dev/null
+++ b/examples/relative-path-with-multiple-css-outputs/source/views/desktop.html
@@ -0,0 +1,13 @@
+
+
+
+
+ <%= htmlWebpackPlugin.options.title %>
+
+
+
+
+
})
+
+
+
diff --git a/examples/relative-path-with-multiple-css-outputs/source/views/mobile.html b/examples/relative-path-with-multiple-css-outputs/source/views/mobile.html
new file mode 100644
index 0000000..42c8565
--- /dev/null
+++ b/examples/relative-path-with-multiple-css-outputs/source/views/mobile.html
@@ -0,0 +1,13 @@
+
+
+
+
+ <%= htmlWebpackPlugin.options.title %>
+
+
+
+
+
})
+
+
+
diff --git a/examples/relative-path-with-multiple-css-outputs/webpack.config.js b/examples/relative-path-with-multiple-css-outputs/webpack.config.js
new file mode 100644
index 0000000..592d4a3
--- /dev/null
+++ b/examples/relative-path-with-multiple-css-outputs/webpack.config.js
@@ -0,0 +1,109 @@
+const ip = require('ip');
+const path = require('path');
+const Text = require('extract-text-webpack-plugin');
+const Html = require('html-webpack-plugin');
+const webpack = require('webpack');
+const fileLoader = require.resolve('../../src/cjs');
+
+const resolve = (...args) => path.resolve(__dirname, ...args);
+
+const OUTPUT = {
+ bundle: 'dist/',
+ img: 'media/images/',
+ css: 'styles/',
+ js: 'scripts/',
+};
+
+module.exports = (argv = {}) => {
+ const config = {
+ devtool: argv.dev ? '#cheap-module-eval-source-map' : '#source-map',
+ entry: {
+ desktop: ['./source/scripts/desktop.js', './source/styles/desktop.css'],
+ mobile: ['./source/scripts/mobile.js', './source/styles/mobile.css'],
+ },
+ output: {
+ path: resolve(OUTPUT.bundle),
+ filename: `${OUTPUT.js}[name].js`,
+ },
+ devServer: {
+ contentBase: resolve(OUTPUT.bundle),
+ historyApiFallback: true,
+ stats: 'errors-only',
+ host: ip.address(),
+ port: 3000,
+ },
+ module: {
+ rules: [
+ {
+ test: /\.css$/,
+ use: Text.extract({
+ publicPath: argv.dev && OUTPUT.bundle,
+ fallback: 'style-loader',
+ use: [{
+ loader: 'css-loader',
+ query: {
+ minimize: false,
+ },
+ }],
+ }),
+ },
+ {
+ test: /\.(jpe?g|png|gif|svg)(\?v=\d+\.\d+\.\d+)?$/,
+ loader: fileLoader,
+ options: {
+ useRelativePath: true,
+
+ /*
+ |* If you need a multiple output path for any reason
+ |* @see https://github.com/webpack-contrib/file-loader/issues/149#issuecomment-294290509
+ `*/
+ cssOutputPath: OUTPUT.css,
+
+ outputPath: OUTPUT.img,
+ name: '[name].[hash:7].[ext]',
+ },
+ },
+ ],
+ },
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: `'${argv.dev ? 'development' : 'production'}'`,
+ },
+ }),
+ new Text({
+ filename: `${OUTPUT.css}[name]${argv.dev ? '' : '.[chunkhash]'}.css`,
+ disable: !!argv.dev,
+ allChunks: true,
+ }),
+ new Html({
+ title: 'file-loader // useRelativePath // mobile',
+ template: './source/views/mobile.html',
+ filename: 'mobile.html',
+ chunks: ['mobile'],
+ }),
+ new Html({
+ title: 'file-loader // useRelativePath // desktop',
+ template: './source/views/desktop.html',
+ filename: 'index.html',
+ chunks: ['desktop'],
+ }),
+ new webpack.HotModuleReplacementPlugin({ quiet: true }),
+ new webpack.NoEmitOnErrorsPlugin(),
+ new webpack.NamedModulesPlugin(),
+ ],
+ };
+
+ if (argv.dev) {
+ const host = config.devServer.host;
+ const port = config.devServer.port;
+ Object.keys(config.entry).forEach((entry) => {
+ config.entry[entry] = [
+ `webpack-dev-server/client?http://${host}:${port}`,
+ 'webpack/hot/only-dev-server',
+ ].concat(config.entry[entry]);
+ });
+ }
+
+ return config;
+};
diff --git a/examples/relative-path-with-single-css-output/package.json b/examples/relative-path-with-single-css-output/package.json
new file mode 100644
index 0000000..7db44af
--- /dev/null
+++ b/examples/relative-path-with-single-css-output/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "use-relative-path",
+ "author": "@adriancmiranda",
+ "scripts": {
+ "dev": "webpack-dev-server --open --env.dev",
+ "prebuild": "rimraf dist/",
+ "build": "webpack -p"
+ },
+ "optionalDependencies": {
+ "css-loader": "0.28.0",
+ "extract-text-webpack-plugin": "2.1.0",
+ "html-webpack-plugin": "2.28.0",
+ "rimraf": "2.6.1",
+ "style-loader": "0.16.1",
+ "webpack-dev-server": "2.4.2"
+ },
+ "devDependencies": {
+ "webpack": "2.4.1"
+ }
+}
diff --git a/examples/relative-path-with-single-css-output/source/index.css b/examples/relative-path-with-single-css-output/source/index.css
new file mode 100644
index 0000000..d0fc058
--- /dev/null
+++ b/examples/relative-path-with-single-css-output/source/index.css
@@ -0,0 +1,49 @@
+*,
+*:after,
+*:before {
+ -webkit-tap-highlight-color: transparent;
+ tap-highlight-color: transparent;
+ box-sizing: border-box;
+ outline-style: none;
+}
+
+html,
+body {
+ width: 100%;
+ height: 100%;
+}
+
+body {
+ font-family: Helvetica, sans-serif;
+ font-size: 16px;
+ line-height: 10px;
+ margin: 0;
+}
+
+div,
+canvas {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: purple;
+}
+
+div {
+ background: rgba(0,0,0,0.5) url("../../../.github/assets/file_loader_icon.svg") no-repeat 50% 50%;
+}
+
+img {
+ position: absolute;
+ right: 10px;
+ bottom: 10px;
+ display: block;
+}
+
+div:before {
+ position: absolute;
+ display: block;
+ margin-top: 10px;
+ margin-left: 10px;
+ color: #fff;
+ content: "file-loader / examples / relative-path-with-single-output";
+}
diff --git a/examples/relative-path-with-single-css-output/source/index.html b/examples/relative-path-with-single-css-output/source/index.html
new file mode 100644
index 0000000..539b6b0
--- /dev/null
+++ b/examples/relative-path-with-single-css-output/source/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+ <%= htmlWebpackPlugin.options.title %>
+
+
+
+
+
})
+
+
+
diff --git a/examples/relative-path-with-single-css-output/source/index.js b/examples/relative-path-with-single-css-output/source/index.js
new file mode 100644
index 0000000..bb6b422
--- /dev/null
+++ b/examples/relative-path-with-single-css-output/source/index.js
@@ -0,0 +1,146 @@
+const Utils = {
+ randomRange(min, max) {
+ return Math.random() * (max - min) + min;
+ },
+ distance(x1, y1, x2, y2) {
+ return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
+ },
+};
+
+function FileIcon(context, scale) {
+ this.context = context;
+ this.canvas = this.context.canvas;
+ this.image = new Image();
+ this.image.src = require('../../../.github/assets/file_loader_icon.svg');
+ this.scale = scale;
+ this.x = Math.random() * this.canvas.clientWidth;
+ this.y = Math.random() * this.canvas.clientHeight;
+ this.point = {
+ x: Math.cos(Math.floor(Math.random() * 360)) * 1.1,
+ y: Math.sin(Math.floor(Math.random() * 360)) * 1.1,
+ };
+}
+
+FileIcon.prototype = {
+ constructor: FileIcon,
+
+ update() {
+ this.bounds();
+ this.x += this.point.x;
+ this.y += this.point.y;
+ },
+
+ bounds() {
+ const registrationPointX = this.image.width * this.scale * 0.5;
+ const registrationPointY = this.image.height * this.scale * 0.5;
+ const boundsX = this.canvas.clientWidth - registrationPointX;
+ const boundsY = this.canvas.clientHeight - registrationPointY;
+ if (this.x >= boundsX || this.x <= registrationPointX) {
+ this.point.x *= -1;
+ }
+ if (this.y >= boundsY || this.y <= registrationPointY) {
+ this.point.y *= -1;
+ }
+ if (this.x > boundsX) {
+ this.x = boundsX;
+ }
+ if (this.y > boundsY) {
+ this.y = boundsY;
+ }
+ if (this.x < registrationPointX) {
+ this.x = registrationPointX;
+ }
+ if (this.y < registrationPointY) {
+ this.y = registrationPointY;
+ }
+ },
+
+ draw() {
+ const imageX = this.image.width * this.scale;
+ const imageY = this.image.height * this.scale;
+ this.context.drawImage(this.image,
+ 0, 0, 128, 128, // source
+ this.x - imageX * 0.5,
+ this.y - imageY * 0.5,
+ imageX,
+ imageY,
+ );
+ },
+};
+
+function FilesMesh(selector, options) {
+ this.options = Object.assign(FilesMesh.defaults, options);
+ this.canvas = document.querySelector(selector);
+ this.context = this.canvas.getContext('2d');
+ this.rgb = this.options.lineColor.match(/\d+/g);
+}
+
+FilesMesh.defaults = {
+ lineColor: 'rgb(234, 239, 240)',
+ bondDistance: 300,
+ numFiles: 25,
+ minScale: 0.05,
+ maxScale: 0.4,
+};
+
+FilesMesh.prototype = {
+ constructor: FilesMesh,
+
+ start() {
+ this.arrange = this.arrange.bind(this);
+ window.addEventListener('resize', this.arrange);
+ this.arrange();
+
+ this.files = [];
+ this.draw = this.draw.bind(this);
+ for (var id = 0, scale; id < this.options.numFiles; id++) {
+ scale = Utils.randomRange(this.options.minScale, this.options.maxScale);
+ this.files.push(new FileIcon(this.context, scale));
+ }
+ this.drawFrameID = window.requestAnimationFrame(this.draw);
+ },
+
+ stop(flush) {
+ window.removeEventListener('resize', this.arrange);
+ window.cancelAnimationFrame(this.drawFrameID);
+ delete this.drawFrameID;
+ },
+
+ bindFiles(file, dependencies) {
+ for (let id = 0; id < dependencies.length; id++) {
+ const dependency = dependencies[id];
+ const distance = Utils.distance(file.x, file.y, dependency.x, dependency.y);
+ const alpha = 1 - distance / this.options.bondDistance;
+ if (alpha) {
+ this.context.lineWidth = 0.5;
+ this.context.strokeStyle = `rgba(${this.rgb[0]},${this.rgb[1]},${this.rgb[2]},${alpha})`;
+ this.context.beginPath();
+ this.context.moveTo(file.x, file.y);
+ this.context.lineTo(dependency.x, dependency.y);
+ this.context.closePath();
+ this.context.stroke();
+ }
+ }
+ },
+
+ draw() {
+ let id;
+ this.drawFrameID = window.requestAnimationFrame(this.draw);
+ this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
+ for (id = 0; id < this.files.length; id++) {
+ this.bindFiles(this.files[id], this.files);
+ }
+ for (id = 0; id < this.files.length; id++) {
+ this.files[id].update();
+ this.files[id].draw();
+ }
+ },
+
+ arrange() {
+ this.canvas.width = window.innerWidth;
+ this.canvas.height = window.innerHeight;
+ },
+};
+
+const mesh = new FilesMesh('#background');
+mesh.start();
diff --git a/examples/relative-path-with-single-css-output/webpack.config.js b/examples/relative-path-with-single-css-output/webpack.config.js
new file mode 100644
index 0000000..f10f64f
--- /dev/null
+++ b/examples/relative-path-with-single-css-output/webpack.config.js
@@ -0,0 +1,82 @@
+const path = require('path');
+const Text = require('extract-text-webpack-plugin');
+const Html = require('html-webpack-plugin');
+const webpack = require('webpack');
+const fileLoader = require.resolve('../../src/cjs');
+
+const resolve = (...args) => path.resolve(__dirname, ...args);
+
+const OUTPUT = {
+ bundle: 'dist/',
+ img: 'media/images/',
+ css: 'styles/',
+ js: 'scripts/',
+};
+
+module.exports = (argv = {}) => {
+ return ({
+ devtool: argv.dev ? '#eval-source-map' : '#source-map',
+ entry: (argv.dev ? [
+ 'webpack-dev-server/client?http://localhost:8080',
+ 'webpack/hot/only-dev-server',
+ ] : []).concat([
+ './source/index.css',
+ './source/index.js',
+ ]),
+ output: {
+ path: resolve(OUTPUT.bundle),
+ filename: `${OUTPUT.js}[name].js`,
+ },
+ devServer: {
+ contentBase: resolve(OUTPUT.bundle),
+ historyApiFallback: true,
+ stats: 'errors-only',
+ },
+ module: {
+ rules: [
+ {
+ test: /\.css$/,
+ use: Text.extract({
+ publicPath: argv.dev && OUTPUT.bundle,
+ fallback: 'style-loader',
+ use: [{
+ loader: 'css-loader',
+ query: {
+ minimize: false,
+ },
+ }],
+ }),
+ },
+ {
+ test: /\.(jpe?g|png|gif|svg)(\?v=\d+\.\d+\.\d+)?$/,
+ loader: fileLoader,
+ options: {
+ useRelativePath: true,
+ cssOutputPath: OUTPUT.css,
+ outputPath: OUTPUT.img,
+ name: '[name].[hash:7].[ext]',
+ },
+ },
+ ],
+ },
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: `'${argv.dev ? 'development' : 'production'}'`,
+ },
+ }),
+ new Text({
+ filename: `${OUTPUT.css}theme.css`,
+ disable: !!argv.dev,
+ allChunks: true,
+ }),
+ new Html({
+ title: 'file-loader // useRelativePath',
+ template: './source/index.html',
+ }),
+ new webpack.HotModuleReplacementPlugin({ quiet: true }),
+ new webpack.NoEmitOnErrorsPlugin(),
+ new webpack.NamedModulesPlugin(),
+ ],
+ });
+};
diff --git a/src/helper.js b/src/helper.js
new file mode 100644
index 0000000..e4eee2e
--- /dev/null
+++ b/src/helper.js
@@ -0,0 +1,12 @@
+export const is = function is(expected, value) {
+ return new RegExp(`(${expected})`).test(Object.prototype.toString.call(value));
+};
+
+export const parsePath = function parsePath(property, slug) {
+ if (!property) {
+ return slug;
+ } else if (is('Function', property)) {
+ return property(slug);
+ }
+ return property + slug;
+};
diff --git a/src/index.js b/src/index.js
index 0fa18ee..fb65d01 100644
--- a/src/index.js
+++ b/src/index.js
@@ -4,6 +4,7 @@
*/
import path from 'path';
import loaderUtils from 'loader-utils';
+import { is, parsePath } from './helper';
export default function fileLoader(content) {
if (!this.emitFile) throw new Error('emitFile is required from module system');
@@ -11,71 +12,78 @@ export default function fileLoader(content) {
const query = loaderUtils.getOptions(this) || {};
const configKey = query.config || 'fileLoader';
const options = this.options[configKey] || {};
-
- const config = {
- publicPath: undefined,
+ const config = Object.assign({
+ regExp: undefined,
+ context: undefined,
useRelativePath: false,
+ publicPath: undefined,
+ cssOutputPath: '',
+ outputPath: '',
name: '[hash].[ext]',
- };
-
- // 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];
- });
+ }, options, query);
- const context = config.context || this.options.context;
+ const context = config.context || this.options.context || process.cwd();
+ const issuer = (this._module && this._module.issuer) || {}; // eslint-disable-line no-underscore-dangle
let url = loaderUtils.interpolateName(this, config.name, {
+ regExp: config.regExp,
context,
content,
- regExp: config.regExp,
});
- let outputPath = '';
if (config.outputPath) {
// support functions as outputPath to generate them dynamically
- outputPath = (
- typeof config.outputPath === 'function' ? config.outputPath(url) : config.outputPath
- );
+ config.outputPath = parsePath(config.outputPath, url);
}
- 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
- 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
- outputPath = path.posix.join(outputPath, relativePath, url);
- } else {
- outputPath = relativePath + url;
+ // Only the dirname is needed in this case.
+ config.outputPath = config.outputPath.replace(url, '');
+
+ // We have access only to entry point relationships. So we work with this relations.
+ issuer.context = issuer.context || context;
+ const relation = { path: issuer.context && path.relative(issuer.context, this.resourcePath) };
+ relation.path = relation.path ? path.dirname(relation.path) : config.outputPath;
+
+ // Output path
+ // If the `output.dirname` is pointing to up in relation to the `config.outputPath`.
+ // We forced him to the webpack output path config. Even though it is empty.
+ const output = this.options.output || {};
+ output.dirname = relation.path.replace(/^(\.\.(\/|\\))+/g, '').split(path.sep).join('/');
+ if (output.dirname.indexOf(config.outputPath) !== 0) output.dirname = config.outputPath;
+ config.outputPath = path.join(output.dirname, url).split(path.sep).join('/');
+
+ // Public path
+ // Entry files doesn't pass through the `file-loader`.
+ // So we haven't access to the files context to compare with your assets context
+ // then we need to create and the same way, force the `relation.path` to bundled files
+ // on the webpack output path config folder and manually the same with CSS file.
+ if (output.filename && path.extname(output.filename)) {
+ relation.path = output.dirname;
+ } else if (output.path && is('String', config.cssOutputPath)) {
+ output.bundle = output.path.replace(this.options.context + path.sep, '');
+ output.issuer = path.join(context, output.bundle, config.cssOutputPath);
+ output.asset = path.join(context, output.bundle, output.dirname);
+ relation.path = path.relative(output.issuer, output.asset);
}
- url = relativePath + url;
+ url = path.join(relation.path, url).split(path.sep).join('/');
} else if (config.outputPath) {
- // support functions as outputPath to generate them dynamically
- outputPath = (typeof config.outputPath === 'function' ? config.outputPath(url) : config.outputPath + url);
- url = outputPath;
+ url = config.outputPath;
} else {
- outputPath = url;
+ config.outputPath = url;
}
- let publicPath = `__webpack_public_path__ + ${JSON.stringify(url)}`;
- if (config.publicPath !== undefined) {
+ if (is('String|Function', config.publicPath)) {
// support functions as publicPath to generate them dynamically
- publicPath = JSON.stringify(
- typeof config.publicPath === 'function' ? config.publicPath(url) : config.publicPath + url,
- );
+ config.publicPath = JSON.stringify(parsePath(config.publicPath, url));
+ } else {
+ config.publicPath = `__webpack_public_path__ + ${JSON.stringify(url)}`;
}
if (query.emitFile === undefined || query.emitFile) {
- this.emitFile(outputPath, content);
+ this.emitFile(config.outputPath, content);
}
- return `export default = ${publicPath};`;
+ return `export default = ${config.publicPath};`;
}
export const raw = true;
diff --git a/test/correct-filename.test.js b/test/correct-filename.test.js
index 5b90331..0541299 100644
--- a/test/correct-filename.test.js
+++ b/test/correct-filename.test.js
@@ -1,14 +1,25 @@
/* eslint-disable no-useless-escape, no-unused-vars */
+import queryString from 'querystring';
import fileLoader from '../src';
-const run = function run(resourcePath, query, content = new Buffer('1234')) {
+function run(resourcePath, query, content = new Buffer('1234')) {
let file = null;
+ const queryObject = queryString.parse(query);
+ const webpackConfig = Object.assign({}, queryObject.webpackConfig);
+ delete queryObject.webpackConfig;
+
const context = {
resourcePath,
query: `?${query}`,
options: {
context: '/this/is/the/context',
+ output: webpackConfig.output,
+ },
+ _module: {
+ issuer: {
+ context: webpackConfig._moduleIssuerContext, // eslint-disable-line no-underscore-dangle
+ },
},
emitFile(url, content2) {
expect(content2).toEqual(content);
@@ -17,21 +28,27 @@ const run = function run(resourcePath, query, content = new Buffer('1234')) {
};
const result = fileLoader.call(context, content);
+ return { file, result };
+}
- return {
- file,
- result,
- };
-};
-
-function runWithOptions(resourcePath, options, content = new Buffer('1234')) {
+function runWithOptions(resourcePath, config, content = new Buffer('1234')) {
let file = null;
+ const options = Object.assign({}, config);
+ const webpackConfig = Object.assign({}, options.webpackConfig);
+ delete options.webpackConfig;
+
const context = {
resourcePath,
options: {
fileLoader: options,
context: '/this/is/the/context',
+ output: webpackConfig.output,
+ },
+ _module: {
+ issuer: {
+ context: webpackConfig._moduleIssuerContext, // eslint-disable-line no-underscore-dangle
+ },
},
emitFile(url, content2) {
expect(content2).toEqual(content);
@@ -40,15 +57,12 @@ function runWithOptions(resourcePath, options, content = new Buffer('1234')) {
};
const result = fileLoader.call(context, content);
-
- return {
- file,
- result,
- };
+ return { file, result };
}
-const test = function test(excepted, resourcePath, query, content) {
+
+function test(excepted, resourcePath, query, content) {
expect(run(resourcePath, query, content).file).toEqual(excepted);
-};
+}
describe('correct-filename', () => {
it('should process defaults correctly', () => {
@@ -104,17 +118,106 @@ describe('publicPath option', () => {
describe('useRelativePath option', () => {
it('should be supported', () => {
- expect(run('/this/is/the/context/file.txt', 'useRelativePath=true').result).toEqual(
- 'export default = __webpack_public_path__ + \"./81dc9bdb52d04dc20036dbd8313ed055.txt\";',
+ expect(runWithOptions('/this/is/the/context/file.txt', {
+ useRelativePath: true,
+ webpackConfig: {
+ _moduleIssuerContext: '/this/is/the/context',
+ output: {
+ filename: '[name].js',
+ },
+ },
+ }).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(runWithOptions('/this/is/file.txt', {
+ useRelativePath: true,
+ webpackConfig: {
+ _moduleIssuerContext: '/this/is/the/context',
+ output: {
+ filename: '[name].js',
+ },
+ },
+ }).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(runWithOptions('/this/file.txt', {
+ useRelativePath: true,
+ context: '/this/is/the/',
+ webpackConfig: {
+ _moduleIssuerContext: '/this/is/the/',
+ output: {
+ filename: '[name].js',
+ },
+ },
+ }).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\";',
+ expect(runWithOptions('/this/file.txt', {
+ useRelativePath: true,
+ context: '/',
+ webpackConfig: {
+ _moduleIssuerContext: '/',
+ output: {
+ filename: '[name].js',
+ },
+ },
+ }).result).toEqual(
+ 'export default = __webpack_public_path__ + "this/81dc9bdb52d04dc20036dbd8313ed055.txt";',
+ );
+ });
+});
+
+describe('cssOutputPath option', () => {
+ it('should be supported', () => {
+ expect(runWithOptions('/this/is/the/context/dist/file.txt', {
+ useRelativePath: true,
+ cssOutputPath: 'style',
+ webpackConfig: {
+ _moduleIssuerContext: '/this/is/the/context/source',
+ output: {
+ path: '/this/is/the/context/dist',
+ },
+ },
+ }).result).toEqual(
+ 'export default = __webpack_public_path__ + "../dist/81dc9bdb52d04dc20036dbd8313ed055.txt";',
+ );
+ expect(runWithOptions('/this/is/file.txt', {
+ useRelativePath: true,
+ cssOutputPath: '',
+ webpackConfig: {
+ _moduleIssuerContext: '/this/is',
+ output: {
+ path: '/this/is/the/context',
+ },
+ },
+ }).result).toEqual(
+ 'export default = __webpack_public_path__ + "81dc9bdb52d04dc20036dbd8313ed055.txt";',
+ );
+ expect(runWithOptions('/this/file.txt', {
+ useRelativePath: true,
+ cssOutputPath: '/this/is/the/style',
+ context: '/this/is/the/',
+ webpackConfig: {
+ _moduleIssuerContext: '/this/is/the/',
+ output: {
+ path: '/this/is/the/context/dist',
+ },
+ },
+ }).result).toEqual(
+ 'export default = __webpack_public_path__ + "../../../../../81dc9bdb52d04dc20036dbd8313ed055.txt";',
+ );
+ expect(runWithOptions('/this/file.txt', {
+ useRelativePath: true,
+ cssOutputPath: '/style',
+ context: '/',
+ webpackConfig: {
+ _moduleIssuerContext: '/',
+ output: {
+ path: '/this/is/the/context/dist',
+ },
+ },
+ }).result).toEqual(
+ 'export default = __webpack_public_path__ + "../this/81dc9bdb52d04dc20036dbd8313ed055.txt";',
);
});
});
@@ -125,7 +228,7 @@ describe('outputPath function', () => {
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\";',
+ 'export default = __webpack_public_path__ + "/path/set/by/func";',
);
});
it('should be ignored if you set useRelativePath', () => {
@@ -134,7 +237,7 @@ describe('outputPath function', () => {
options.outputPath = outputFunc;
options.useRelativePath = true;
expect(runWithOptions('/this/is/the/context/file.txt', options).result).toEqual(
- 'export default = __webpack_public_path__ + \"./81dc9bdb52d04dc20036dbd8313ed055.txt\";',
+ 'export default = __webpack_public_path__ + "81dc9bdb52d04dc20036dbd8313ed055.txt";',
);
});
});
diff --git a/test/helper.test.js b/test/helper.test.js
new file mode 100644
index 0000000..7c39338
--- /dev/null
+++ b/test/helper.test.js
@@ -0,0 +1,26 @@
+import { is, parsePath } from '../src/helper';
+
+describe('is', () => {
+ expect(is('String|Function', '/path/to/file.txt')).toBe(true);
+ expect(is('String|Function', () => '/path/to/file.txt')).toBe(true);
+ expect(is('Function', () => '/path/to/file.txt')).toBe(true);
+ expect(is('String', () => '/path/to/file.txt')).toBe(false);
+});
+
+describe('parsePath', () => {
+ const outputPath = filePath => `/path/to/${filePath}`;
+ const publicPath = '/public/path/';
+ const url = 'file.txt';
+
+ it('should process function value correctly', () => {
+ expect(parsePath(outputPath, url)).toBe(`/path/to/${url}`);
+ });
+
+ it('should process string value correctly', () => {
+ expect(parsePath(publicPath, url)).toBe(publicPath + url);
+ });
+
+ it('should process void correctly', () => {
+ expect(parsePath(undefined, url)).toBe(url);
+ });
+});