From c136f4406d90c79bfdbfa3a19544781c599ed478 Mon Sep 17 00:00:00 2001 From: fabb <153960+fabb@users.noreply.github.com> Date: Tue, 6 Aug 2019 18:33:25 +0200 Subject: [PATCH] feat: `postTransformPublicPath` option (#334) --- README.md | 94 ++++++++++ src/index.js | 4 + src/options.json | 4 + ...ostTransformPublicPath-option.test.js.snap | 82 +++++++++ test/postTransformPublicPath-option.test.js | 173 ++++++++++++++++++ 5 files changed, 357 insertions(+) create mode 100644 test/__snapshots__/postTransformPublicPath-option.test.js.snap create mode 100644 test/postTransformPublicPath-option.test.js diff --git a/README.md b/README.md index c93f2f1..1a13de0 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,32 @@ module.exports = { }; ``` +### `postTransformPublicPath` + +Type: `Function` +Default: `undefined` + +Specifies a custom function to post-process the generated public path. This can be used to prepend or append dynamic global variables that are only available at runtime, like `__webpack_public_path__`. This would not be possible with just `publicPath`, since it stringifies the values. + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: /\.(png|jpg|gif)$/, + loader: 'file-loader', + options: { + publicPath: '/some/path/', + postTransformPublicPath: (p) => `__webpack_public_path__ + ${p}`, + }, + }, + ], + }, +}; +``` + ### `context` Type: `String` @@ -577,6 +603,74 @@ Result: path/to/file.png?e43b20c069c4a01867c31e98cbce33c9 ``` +### Dynamic public path depending on environment variable at run time + +An application might want to configure different CDN hosts depending on an environment variable that is only available when running the application. This can be an advantage, as only one build of the application is necessary, which behaves differntly depending on environment variables of the deployment environment. Since file-loader is applied when compiling the application, and not when running it, the environment variable cannot be used in the file-loader configuration. A way around this is setting the `__webpack_public_path__` to the desired CDN host depending on the environment variable at the entrypoint of the application. The option `postTransformPublicPath` can be used to configure a custom path depending on a variable like `__webpack_public_path__`. + +**main.js** + +```js +const namespace = process.env.NAMESPACE; +const assetPrefixForNamespace = (namespace) => { + switch (namespace) { + case 'prod': + return 'https://cache.myserver.net/web'; + case 'uat': + return 'https://cache-uat.myserver.net/web'; + case 'st': + return 'https://cache-st.myserver.net/web'; + case 'dev': + return 'https://cache-dev.myserver.net/web'; + default: + return ''; + } +}; +__webpack_public_path__ = `${assetPrefixForNamespace(namespace)}/`; +``` + +**file.js** + +```js +import png from './image.png'; +``` + +**webpack.config.js** + +```js +module.exports = { + module: { + rules: [ + { + test: /\.(png|jpg|gif)$/, + loader: 'file-loader', + options: { + context: '', + emitFile: true, + name: '[name].[hash].[ext]', + publicPath: 'static/assets/', + postTransformPublicPath: (p) => `__webpack_public_path__ + ${p}`, + outputPath: 'static/assets/', + }, + }, + ], + }, +}; +``` + +Result when run with `NAMESPACE=prod` env variable: + +```bash +# result +https://cache.myserver.net/web/static/assets/image.somehash.png +``` + +Result when run with `NAMESPACE=dev` env variable: + +```bash +# result +https://cache-dev.myserver.net/web/static/assets/image.somehash.png +``` + ## Contributing Please take a moment to read our contributing guidelines if you haven't yet done so. diff --git a/src/index.js b/src/index.js index 629a3cb..b9d8a32 100644 --- a/src/index.js +++ b/src/index.js @@ -47,6 +47,10 @@ export default function loader(content) { publicPath = JSON.stringify(publicPath); } + if (options.postTransformPublicPath) { + publicPath = options.postTransformPublicPath(publicPath); + } + if (typeof options.emitFile === 'undefined' || options.emitFile) { this.emitFile(outputPath, content); } diff --git a/src/options.json b/src/options.json index 262f16a..7ab6184 100644 --- a/src/options.json +++ b/src/options.json @@ -34,6 +34,10 @@ } ] }, + "postTransformPublicPath": { + "description": "A custom transformation function for post-processing the publicPath (https://github.com/webpack-contrib/file-loader#posttransformpublicpath).", + "instanceof": "Function" + }, "context": { "description": "A custom file context (https://github.com/webpack-contrib/file-loader#context).", "type": "string" diff --git a/test/__snapshots__/postTransformPublicPath-option.test.js.snap b/test/__snapshots__/postTransformPublicPath-option.test.js.snap new file mode 100644 index 0000000..17e1e99 --- /dev/null +++ b/test/__snapshots__/postTransformPublicPath-option.test.js.snap @@ -0,0 +1,82 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`when applied with \`postTransformPublicPath\` option matches snapshot for appending to input parameter value 1`] = ` +Object { + "assets": Array [ + "9c87cbf3ba33126ffd25ae7f2f6bbafb.png", + ], + "source": "module.exports = __webpack_public_path__ + \\"9c87cbf3ba33126ffd25ae7f2f6bbafb.png\\" + \\"/test\\";", +} +`; + +exports[`when applied with \`postTransformPublicPath\` option matches snapshot for returned input parameter value without modification 1`] = ` +Object { + "assets": Array [ + "9c87cbf3ba33126ffd25ae7f2f6bbafb.png", + ], + "source": "module.exports = __webpack_public_path__ + \\"9c87cbf3ba33126ffd25ae7f2f6bbafb.png\\";", +} +`; + +exports[`when applied with \`publicPath\` and \`postTransformPublicPath\` option \`{Function}\` value matches snapshot for appending to input parameter value 1`] = ` +Object { + "assets": Array [ + "9c87cbf3ba33126ffd25ae7f2f6bbafb.png", + ], + "source": "module.exports = \\"public_path/9c87cbf3ba33126ffd25ae7f2f6bbafb.png\\" + \\"?test=test\\";", +} +`; + +exports[`when applied with \`publicPath\` and \`postTransformPublicPath\` option \`{Function}\` value matches snapshot for prefixing with __webpack_public_path__ 1`] = ` +Object { + "assets": Array [ + "9c87cbf3ba33126ffd25ae7f2f6bbafb.png", + ], + "source": "module.exports = __webpack_public_path__ + \\"public_path/9c87cbf3ba33126ffd25ae7f2f6bbafb.png\\";", +} +`; + +exports[`when applied with \`publicPath\` and \`postTransformPublicPath\` option \`{Function}\` value matches snapshot for prefixing with string 1`] = ` +Object { + "assets": Array [ + "9c87cbf3ba33126ffd25ae7f2f6bbafb.png", + ], + "source": "module.exports = \\"path_prefix/\\" + \\"public_path/9c87cbf3ba33126ffd25ae7f2f6bbafb.png\\";", +} +`; + +exports[`when applied with \`publicPath\` and \`postTransformPublicPath\` option \`{Function}\` value matches snapshot for returned input parameter value without modification 1`] = ` +Object { + "assets": Array [ + "9c87cbf3ba33126ffd25ae7f2f6bbafb.png", + ], + "source": "module.exports = \\"public_path/9c87cbf3ba33126ffd25ae7f2f6bbafb.png\\";", +} +`; + +exports[`when applied with \`publicPath\` and \`postTransformPublicPath\` option \`{String}\` value matches snapshot for appending to input parameter value 1`] = ` +Object { + "assets": Array [ + "9c87cbf3ba33126ffd25ae7f2f6bbafb.png", + ], + "source": "module.exports = \\"public_path/9c87cbf3ba33126ffd25ae7f2f6bbafb.png\\" + \\"?test=test\\";", +} +`; + +exports[`when applied with \`publicPath\` and \`postTransformPublicPath\` option \`{String}\` value matches snapshot for prefixing with __webpack_public_path__ 1`] = ` +Object { + "assets": Array [ + "9c87cbf3ba33126ffd25ae7f2f6bbafb.png", + ], + "source": "module.exports = __webpack_public_path__ + \\"public_path/9c87cbf3ba33126ffd25ae7f2f6bbafb.png\\";", +} +`; + +exports[`when applied with \`publicPath\` and \`postTransformPublicPath\` option \`{String}\` value matches snapshot for returned input parameter value without modification 1`] = ` +Object { + "assets": Array [ + "9c87cbf3ba33126ffd25ae7f2f6bbafb.png", + ], + "source": "module.exports = \\"public_path/9c87cbf3ba33126ffd25ae7f2f6bbafb.png\\";", +} +`; diff --git a/test/postTransformPublicPath-option.test.js b/test/postTransformPublicPath-option.test.js new file mode 100644 index 0000000..d673e02 --- /dev/null +++ b/test/postTransformPublicPath-option.test.js @@ -0,0 +1,173 @@ +import webpack from './helpers/compiler'; + +describe('when applied with `postTransformPublicPath` option', () => { + it('matches snapshot for returned input parameter value without modification', async () => { + const config = { + loader: { + test: /(png|jpg|svg)/, + options: { + postTransformPublicPath: (p) => p, + }, + }, + }; + + const stats = await webpack('fixture.js', config); + const [module] = stats.toJson().modules; + const { assets, source } = module; + + expect({ assets, source }).toMatchSnapshot(); + }); + + it('matches snapshot for appending to input parameter value', async () => { + const config = { + loader: { + test: /(png|jpg|svg)/, + options: { + postTransformPublicPath: (p) => `${p} + "/test"`, + }, + }, + }; + + const stats = await webpack('fixture.js', config); + const [module] = stats.toJson().modules; + const { assets, source } = module; + + expect({ assets, source }).toMatchSnapshot(); + }); +}); + +describe('when applied with `publicPath` and `postTransformPublicPath` option', () => { + describe('`{String}` value', () => { + it('matches snapshot for returned input parameter value without modification', async () => { + const config = { + loader: { + test: /(png|jpg|svg)/, + options: { + publicPath: 'public_path/', + postTransformPublicPath: (p) => p, + }, + }, + }; + + const stats = await webpack('fixture.js', config); + const [module] = stats.toJson().modules; + const { assets, source } = module; + + expect({ assets, source }).toMatchSnapshot(); + }); + + it('matches snapshot for appending to input parameter value', async () => { + const config = { + loader: { + test: /(png|jpg|svg)/, + options: { + publicPath: 'public_path/', + postTransformPublicPath: (p) => `${p} + "?test=test"`, + }, + }, + }; + + const stats = await webpack('fixture.js', config); + const [module] = stats.toJson().modules; + const { assets, source } = module; + + expect({ assets, source }).toMatchSnapshot(); + }); + + it('matches snapshot for prefixing with __webpack_public_path__', async () => { + const config = { + loader: { + test: /(png|jpg|svg)/, + options: { + publicPath: 'public_path/', + postTransformPublicPath: (p) => `__webpack_public_path__ + ${p}`, + }, + }, + }; + + const stats = await webpack('fixture.js', config); + const [module] = stats.toJson().modules; + const { assets, source } = module; + + expect({ assets, source }).toMatchSnapshot(); + }); + }); + + describe('`{Function}` value', () => { + it('matches snapshot for returned input parameter value without modification', async () => { + const config = { + loader: { + test: /(png|jpg|svg)/, + options: { + publicPath(url) { + return `public_path/${url}`; + }, + postTransformPublicPath: (p) => p, + }, + }, + }; + + const stats = await webpack('fixture.js', config); + const [module] = stats.toJson().modules; + const { assets, source } = module; + + expect({ assets, source }).toMatchSnapshot(); + }); + + it('matches snapshot for appending to input parameter value', async () => { + const config = { + loader: { + test: /(png|jpg|svg)/, + options: { + publicPath(url) { + return `public_path/${url}`; + }, + postTransformPublicPath: (p) => `${p} + "?test=test"`, + }, + }, + }; + + const stats = await webpack('fixture.js', config); + const [module] = stats.toJson().modules; + const { assets, source } = module; + + expect({ assets, source }).toMatchSnapshot(); + }); + + it('matches snapshot for prefixing with string', async () => { + const config = { + loader: { + test: /(png|jpg|svg)/, + options: { + publicPath: 'public_path/', + postTransformPublicPath: (p) => `"path_prefix/" + ${p}`, + }, + }, + }; + + const stats = await webpack('fixture.js', config); + const [module] = stats.toJson().modules; + const { assets, source } = module; + + expect({ assets, source }).toMatchSnapshot(); + }); + + it('matches snapshot for prefixing with __webpack_public_path__', async () => { + const config = { + loader: { + test: /(png|jpg|svg)/, + options: { + publicPath: 'public_path/', + postTransformPublicPath: (p) => `__webpack_public_path__ + ${p}`, + }, + }, + }; + + const stats = await webpack('fixture.js', config); + const [module] = stats.toJson().modules; + const { assets, source } = module; + + expect({ assets, source }).toMatchSnapshot(); + }); + }); +});