├── .npmignore ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── package.json ├── LICENSE ├── index.js ├── README.md └── test └── test.js /.npmignore: -------------------------------------------------------------------------------- 1 | # Everything 2 | * 3 | 4 | !index.js 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - v*.*.* 9 | pull_request: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v1 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: '12.x' 19 | registry-url: 'https://registry.npmjs.org' 20 | - run: npm install 21 | - run: npm test 22 | - run: npm publish 23 | if: startsWith(github.ref, 'refs/tags/') 24 | env: 25 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "interpolate-loader", 3 | "version": "2.0.1", 4 | "description": "Webpack loader to interpolate require results.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "ava test/test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/erikdesjardins/interpolate-loader.git" 12 | }, 13 | "keywords": [ 14 | "webpack" 15 | ], 16 | "author": "Erik Desjardins", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/erikdesjardins/interpolate-loader/issues" 20 | }, 21 | "homepage": "https://github.com/erikdesjardins/interpolate-loader#readme", 22 | "peerDependencies": { 23 | "webpack": ">=3.0.0" 24 | }, 25 | "dependencies": { 26 | "escape-string-regexp": "^2.0.0", 27 | "loader-utils": "^1.1.0" 28 | }, 29 | "devDependencies": { 30 | "ava": "^2.4.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Erik Desjardins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Erik Desjardins 3 | * See LICENSE file in root directory for full license. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var loaderUtils = require('loader-utils'); 9 | var escapeStringRegexp = require('escape-string-regexp'); 10 | 11 | function validateQuery(query) { 12 | if ('prefix' in query) { 13 | if (typeof query.prefix !== 'string') { 14 | throw new TypeError('prefix must be a string'); 15 | } 16 | if (query.prefix === '') { 17 | throw new TypeError('prefix may not be empty'); 18 | } 19 | } 20 | if ('suffix' in query) { 21 | if (typeof query.suffix !== 'string') { 22 | throw new TypeError('suffix must be a string'); 23 | } 24 | if (query.suffix === '') { 25 | throw new TypeError('suffix may not be empty'); 26 | } 27 | } 28 | if (query.prefix && !query.suffix) { 29 | throw new Error('prefix was supplied without a corresponding suffix'); 30 | } 31 | if (query.suffix && !query.prefix) { 32 | throw new Error('suffix was supplied without a corresponding prefix'); 33 | } 34 | 35 | return query; 36 | } 37 | 38 | module.exports = function(source) { 39 | var query = Object.assign( 40 | { prefix: '{{', suffix: '}}' }, 41 | validateQuery(loaderUtils.getOptions(this) || {}) 42 | ); 43 | var prefix = escapeStringRegexp(query.prefix); 44 | var suffix = escapeStringRegexp(query.suffix); 45 | 46 | var requires = []; 47 | var placeholder = '__INTERPOLATE_LOADER_' + String(Math.random()).slice(2) + '__'; 48 | 49 | var content = source.replace(new RegExp(prefix + '([^\\r\\n]+?)' + suffix, 'g'), function(_, url) { 50 | requires.push(loaderUtils.stringifyRequest(this, url.trim())); 51 | return placeholder; 52 | }.bind(this)); 53 | 54 | var i = 0; 55 | var interpolated = JSON.stringify(content).replace(new RegExp(placeholder, 'g'), function() { 56 | return '" + require(' + requires[i++] + ') + "'; 57 | }); 58 | 59 | return 'module.exports = ' + interpolated + ';'; 60 | }; 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # interpolate-loader 2 | 3 | Webpack loader to interpolate require results. 4 | 5 | Useful if you need to use a manifest file as an entry point to your application. 6 | 7 | Pairs well with [`prop-loader`](https://github.com/erikdesjardins/prop-loader) and [`entry-loader`](https://github.com/eoin/entry-loader) / [`spawn-loader`](https://github.com/erikdesjardins/spawn-loader) (see below). 8 | 9 | Usually, you will want to pipe the output from this loader into [`extricate-loader`](https://github.com/erikdesjardins/extricate-loader) (to resolve the imports) and then [`file-loader`](https://github.com/webpack/file-loader) (to emit a non-js manifest file). 10 | 11 | ## Installation 12 | 13 | `npm install --save-dev interpolate-loader` 14 | 15 | ## Options 16 | 17 | ### Prefix/suffix 18 | 19 | If you supply a query, it must contain both `prefix` and `suffix`. 20 | 21 | #### Examples 22 | 23 | `interpolate-loader?prefix=(*&suffix=*)` 24 | 25 | `interpolate-loader?prefix=%3C%25&suffix=%25%3E` (lodash-style `<%` and `%>`) 26 | 27 | #### Defaults 28 | 29 | `prefix`: `{{` 30 | 31 | `suffix`: `}}` 32 | 33 | ## Example Usage 34 | 35 | **webpack.config.js:** 36 | 37 | ```js 38 | var InertEntryPlugin = require('inert-entry-webpack-plugin'); // maybe unnecessary, see below 39 | 40 | module.exports = { 41 | entry: 'extricate-loader!interpolate-loader!manifest.json', 42 | output: { filename: 'manifest.json' }, 43 | module: { 44 | rules: [{ 45 | test: /\.html$/, 46 | use: [ 47 | { loader: 'file-loader', options: { name: '[name].[ext]' } }, 48 | 'extricate-loader', 49 | 'html-loader' 50 | ] 51 | }] 52 | }, 53 | plugins: [ 54 | // This is required to use manifest.json as the entry point. 55 | // If the entry point is a .js file and this loader is only used 56 | // for subresources, then it is not necessary. 57 | new InertEntryPlugin() 58 | ], 59 | }; 60 | ``` 61 | 62 | **manifest.json:** 63 | 64 | ```json 65 | { 66 | "name": "{{prop-loader?name!package.json}}", 67 | "version": "{{prop-loader?name!package.json}}", 68 | "description": "{{prop-loader?description!package.json}}", 69 | "background": { 70 | "scripts": [ 71 | "{{entry-loader!main.js}}" // or {{spawn-loader!main.js}} 72 | ] 73 | }, 74 | "options_page": "{{options.html}}", 75 | "icons": { 76 | "48": "{{file-loader!images/icon48.png}}", 77 | "128": "{{file-loader!images/icon128.png}}" 78 | } 79 | } 80 | ``` 81 | 82 | ### Output 83 | 84 | **manifest.json:** 85 | ```json 86 | { 87 | "name": "my-package", 88 | "version": "3.2.1", 89 | "description": "This is my package.", 90 | "background": { 91 | "scripts": [ 92 | "main.109fa8.js" 93 | ] 94 | }, 95 | "options_page": "options.html", 96 | "icons": { 97 | "48": "e43b20c069c4a01867c31e98cbce33c9.png", 98 | "128": "0dcbbaa701328a3c262cfd45869e351f.png" 99 | } 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import loader from '../index.js'; 3 | 4 | test('basic usage', t => { 5 | t.is( 6 | loader.call({}, '{{anotherLoader!hi.js}}'), 7 | 'module.exports = "" + require("anotherLoader!hi.js") + "";' 8 | ); 9 | t.is( 10 | loader.call({}, 'stuff{{test.html}}>other stuff'), 11 | 'module.exports = "stuff" + require("test.html") + ">other stuff";', 12 | 'other words' 13 | ); 14 | }); 15 | 16 | test('escaping', t => { 17 | t.is( 18 | loader.call({}, 'prop: "{{test}}"'), 19 | 'module.exports = "prop: \\"" + require("test") + "\\"";' 20 | ); 21 | }); 22 | 23 | test('trimming', t => { 24 | t.is( 25 | loader.call({}, '{{ foo\t}}'), 26 | 'module.exports = "" + require("foo") + "";' 27 | ); 28 | }); 29 | 30 | test('custom prefix/suffix', t => { 31 | t.is( 32 | loader.call({ query: '?prefix=%3C%25&suffix=%25%3E' }, '<%foo%> {{hi}}'), 33 | 'module.exports = "" + require("foo") + " {{hi}}";' 34 | ); 35 | t.is( 36 | loader.call({ query: '?prefix=%7B.&suffix=.%7D' }, '{.foo.} {bar}'), 37 | 'module.exports = "" + require("foo") + " {bar}";', 38 | 'escaping regex characters' 39 | ); 40 | t.is( 41 | loader.call({ query: '?prefix=(&suffix=)&bar=baz' }, '(foo)'), 42 | 'module.exports = "" + require("foo") + "";', 43 | 'extra unrelated query param' 44 | ); 45 | t.is( 46 | loader.call({ query: '?{prefix:"(",suffix:")"}' }, '(foo)'), 47 | 'module.exports = "" + require("foo") + "";', 48 | 'json query' 49 | ); 50 | }); 51 | 52 | test('invalid queries', t => { 53 | t.throws( 54 | () => loader.call({ query: '?prefix=5' }), 55 | 'prefix was supplied without a corresponding suffix' 56 | ); 57 | t.throws( 58 | () => loader.call({ query: '?suffix=5' }), 59 | 'suffix was supplied without a corresponding prefix' 60 | ); 61 | t.throws( 62 | () => loader.call({ query: '?prefix' }), 63 | 'prefix must be a string' 64 | ); 65 | t.throws( 66 | () => loader.call({ query: '?suffix' }), 67 | 'suffix must be a string' 68 | ); 69 | t.throws( 70 | () => loader.call({ query: '?prefix=' }), 71 | 'prefix may not be empty' 72 | ); 73 | t.throws( 74 | () => loader.call({ query: '?suffix=' }), 75 | 'suffix may not be empty' 76 | ); 77 | }); 78 | 79 | test('no spanning lines', t => { 80 | t.is( 81 | loader.call({}, '{{\nfoo}}'), 82 | 'module.exports = "{{\\nfoo}}";' 83 | ); 84 | t.is( 85 | loader.call({}, '{{\rfoo}}'), 86 | 'module.exports = "{{\\rfoo}}";' 87 | ); 88 | t.is( 89 | loader.call({}, '{{foo\nbar}}'), 90 | 'module.exports = "{{foo\\nbar}}";' 91 | ); 92 | t.is( 93 | loader.call({}, '{{foo\rbar}}'), 94 | 'module.exports = "{{foo\\rbar}}";' 95 | ); 96 | }); 97 | 98 | test('multiple replacements', t => { 99 | t.is( 100 | loader.call({}, '{{foo}}\n{{bar}}'), 101 | 'module.exports = "" + require("foo") + "\\n" + require("bar") + "";' 102 | ); 103 | t.is( 104 | loader.call({}, '{{foo}}{{bar}}'), 105 | 'module.exports = "" + require("foo") + "" + require("bar") + "";', 106 | 'avoid merging multiple replacements on the same line' 107 | ); 108 | }); 109 | --------------------------------------------------------------------------------