├── .gitignore ├── .jshintrc ├── .nvmrc ├── LICENSE ├── codec ├── absolute.js ├── bower-component.js ├── index.js ├── npm-module.js ├── output-relative.js ├── output-root-relative.js ├── project-relative.js ├── project-root-relative.js ├── source-relative.js ├── source-root-relative.js ├── utility │ ├── enhanced-relative.js │ ├── get-context-directory.js │ └── get-output-directory.js ├── webpack-bootstrap.js └── webpack-protocol.js ├── index.js ├── lib ├── loader.js ├── module-filename-template.js └── process │ ├── debug-message.js │ ├── decode-sources-with.js │ ├── encode-sources-with.js │ ├── get-error.js │ ├── get-field-as-fn.js │ ├── index.js │ ├── locate-root-with.js │ ├── test-codec.js │ ├── throw-errors.js │ └── to-reg-exp.js ├── package-lock.json ├── package.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /node_modules 3 | /npm-debug.log -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "forin": false, 7 | "freeze": false, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": "nofunc", 11 | "newcap": true, 12 | "noarg": true, 13 | "noempty": true, 14 | "nonbsp": true, 15 | "nonew": true, 16 | "plusplus": false, 17 | "quotmark": "single", 18 | "undef": true, 19 | "unused": true, 20 | "strict": true, 21 | "maxparams": 20, 22 | "maxdepth": 5, 23 | "maxlen": 120, 24 | "scripturl": true, 25 | "node": true, 26 | "esnext": true, 27 | "jasmine": true 28 | } -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8.9 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Ben Holloway 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 | -------------------------------------------------------------------------------- /codec/absolute.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | fs = require('fs'); 5 | 6 | /** 7 | * Codec for absolute paths. 8 | * @type {{name:string, decode: function, encode: function, root: function}} 9 | */ 10 | module.exports = { 11 | name : 'absolute', 12 | decode: decode, 13 | encode: encode, 14 | root : root 15 | }; 16 | 17 | /** 18 | * Decode the given uri. 19 | * Any path with leading slash is tested in an absolute sense. 20 | * @this {{options: object}} A loader or compilation 21 | * @param {string} uri A source uri to decode 22 | * @returns {boolean|string} False where unmatched else the decoded path 23 | */ 24 | function decode(uri) { 25 | return path.isAbsolute(uri) && fs.existsSync(uri) && fs.statSync(uri).isFile() && uri; 26 | } 27 | 28 | /** 29 | * Encode the given file path. 30 | * @this {{options: object}} A loader or compilation 31 | * @returns {string} A uri 32 | */ 33 | function encode(absolute) { 34 | return absolute; 35 | } 36 | 37 | /** 38 | * The source-map root where relevant. 39 | * @this {{options: object}} A loader or compilation 40 | * @returns {string|undefined} The source-map root applicable to any encoded uri 41 | */ 42 | function root() { 43 | } 44 | -------------------------------------------------------------------------------- /codec/bower-component.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Codec for code generated by the Bower plugin. 5 | * @type {{name:string, decode:function, abstract:boolean}} 6 | */ 7 | module.exports = { 8 | name : 'bowerComponent', 9 | decode : decode, 10 | abstract: true 11 | }; 12 | 13 | /** 14 | * Validate the given uri (abstract). 15 | * @this {{options: object}} A loader or compilation 16 | * @param {string} uri A source uri to decode 17 | * @returns {boolean|string} False where unmatched else True 18 | */ 19 | function decode(uri) { 20 | return /^\/?([\w-]+)\s+\(bower component\)$/.test(uri); 21 | } 22 | -------------------------------------------------------------------------------- /codec/index.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | require('./webpack-protocol'), 3 | require('./webpack-bootstrap'), 4 | require('./bower-component'), 5 | require('./npm-module'), 6 | /* insert here any additional special character CODECs */ 7 | require('./output-relative'), 8 | require('./output-root-relative'), 9 | require('./project-relative'), 10 | require('./project-root-relative'), 11 | require('./source-relative'), 12 | require('./source-root-relative'), 13 | require('./absolute') 14 | ]; 15 | -------------------------------------------------------------------------------- /codec/npm-module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | fs = require('fs'); 5 | 6 | var loaderUtils = require('loader-utils'); 7 | 8 | var getContextDirectory = require('./utility/get-context-directory'); 9 | 10 | /** 11 | * Codec for relative paths with respect to the context directory. 12 | * @type {{name:string, decode: function}} 13 | */ 14 | module.exports = { 15 | name : 'npmModule', 16 | decode: decode 17 | }; 18 | 19 | /** 20 | * Decode the given uri. 21 | * Include only module paths containing `~`. 22 | * @this {{options: object}} A loader or compilation 23 | * @param {string} uri A source uri to decode 24 | * @returns {boolean|string} False where unmatched else the decoded path 25 | */ 26 | function decode(uri) { 27 | /* jshint validthis:true */ 28 | if (/~/.test(uri)) { 29 | var relative = loaderUtils.urlToRequest(uri), 30 | base = getContextDirectory.call(this), 31 | absFile = path.normalize(path.join(base, 'node_modules', relative)), 32 | isValid = !!absFile && fs.existsSync(absFile) && fs.statSync(absFile).isFile(); 33 | return isValid && absFile; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /codec/output-relative.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | fs = require('fs'); 5 | 6 | var getOutputDirectory = require('./utility/get-output-directory'); 7 | 8 | /** 9 | * Codec for relative paths with respect to the output directory. 10 | * @type {{name:string, decode: function, encode: function, root: function}} 11 | */ 12 | module.exports = { 13 | name : 'outputRelative', 14 | decode: decode, 15 | encode: encode, 16 | root : getOutputDirectory 17 | }; 18 | 19 | /** 20 | * Decode the given uri. 21 | * Any path with without leading slash is tested against output directory. 22 | * @this {{options: object}} A loader or compilation 23 | * @param {string} uri A source uri to decode 24 | * @returns {boolean|string} False where unmatched else the decoded path 25 | */ 26 | function decode(uri) { 27 | /* jshint validthis:true */ 28 | var base = !uri.startsWith('/') && getOutputDirectory.call(this), 29 | absFile = !!base && path.normalize(path.join(base, uri)), 30 | isValid = !!absFile && fs.existsSync(absFile) && fs.statSync(absFile).isFile(); 31 | return isValid && absFile; 32 | } 33 | 34 | /** 35 | * Encode the given file path. 36 | * @this {{options: object}} A loader or compilation 37 | * @param {string} absolute An absolute file path to encode 38 | * @returns {string} A uri without leading slash 39 | */ 40 | function encode(absolute) { 41 | /* jshint validthis:true */ 42 | var base = getOutputDirectory.call(this); 43 | if (!base) { 44 | throw new Error('Cannot locate the Webpack output directory'); 45 | } 46 | else { 47 | return path.relative(base, absolute); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /codec/output-root-relative.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var relative = require('./output-relative'); 4 | 5 | /** 6 | * Codec for relative paths with respect to the output directory. 7 | * @type {{name:string, decode: function, encode: function, root: function}} 8 | */ 9 | module.exports = { 10 | name : 'outputRootRelative', 11 | decode: decode, 12 | encode: encode, 13 | root : relative.root 14 | }; 15 | 16 | /** 17 | * Decode the given uri. 18 | * Any path with leading slash is tested against output directory. 19 | * @this {{options: object}} A loader or compilation 20 | * @param {string} uri A source uri to decode 21 | * @returns {boolean|string} False where unmatched else the decoded path 22 | */ 23 | function decode(uri) { 24 | /* jshint validthis:true */ 25 | return uri.startsWith('/') && relative.decode.call(this, uri.slice(1)); 26 | } 27 | 28 | /** 29 | * Encode the given file path. 30 | * @this {{options: object}} A loader or compilation 31 | * @param {string} absolute An absolute file path to encode 32 | * @returns {string} A uri with leading slash 33 | */ 34 | function encode(absolute) { 35 | /* jshint validthis:true */ 36 | return '/' + relative.encode.call(this, absolute); 37 | } 38 | -------------------------------------------------------------------------------- /codec/project-relative.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | fs = require('fs'); 5 | 6 | var getContextDirectory = require('./utility/get-context-directory'), 7 | enhancedRelative = require('./utility/enhanced-relative'); 8 | 9 | /** 10 | * Codec for relative paths with respect to the project directory. 11 | * @type {{name:string, decode: function, encode: function, root: function}} 12 | */ 13 | module.exports = { 14 | name : 'projectRelative', 15 | decode: decode, 16 | encode: encode, 17 | root : getContextDirectory 18 | }; 19 | 20 | /** 21 | * Decode the given uri. 22 | * Any path with without leading slash is tested against project directory. 23 | * @this {{options: object}} A loader or compilation 24 | * @param {string} uri A source uri to decode 25 | * @returns {boolean|string} False where unmatched else the decoded path 26 | */ 27 | function decode(uri) { 28 | /* jshint validthis:true */ 29 | var base = !uri.startsWith('/') && getContextDirectory.call(this), 30 | absFile = !!base && path.normalize(path.join(base, uri)), 31 | isValid = !!absFile && fs.existsSync(absFile) && fs.statSync(absFile).isFile(); 32 | return isValid && absFile; 33 | } 34 | 35 | /** 36 | * Encode the given file path. 37 | * @this {{options: object}} A loader or compilation 38 | * @param {string} absolute An absolute file path to encode 39 | * @returns {string} A uri without leading slash 40 | */ 41 | function encode(absolute) { 42 | /* jshint validthis:true */ 43 | var base = getContextDirectory.call(this); 44 | if (!base) { 45 | throw new Error('Cannot locate the Webpack project directory'); 46 | } 47 | else { 48 | return enhancedRelative(base, absolute); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /codec/project-root-relative.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var relative = require('./project-relative'); 4 | 5 | /** 6 | * Codec for relative paths with respect to the project directory. 7 | * @type {{name:string, decode: function, encode: function, root: function}} 8 | */ 9 | module.exports = { 10 | name : 'projectRootRelative', 11 | decode: decode, 12 | encode: encode, 13 | root : relative.root 14 | }; 15 | 16 | /** 17 | * Decode the given uri. 18 | * Any path with leading slash is tested against project directory. 19 | * @this {{options: object}} A loader or compilation 20 | * @param {string} uri A source uri to decode 21 | * @returns {boolean|string} False where unmatched else the decoded path 22 | */ 23 | function decode(uri) { 24 | /* jshint validthis:true */ 25 | return uri.startsWith('/') && relative.decode.call(this, uri.slice(1)); 26 | } 27 | 28 | /** 29 | * Encode the given file path. 30 | * @this {{options: object}} A loader or compilation 31 | * @param {string} absolute An absolute file path to encode 32 | * @returns {string} A uri with leading slash 33 | */ 34 | function encode(absolute) { 35 | /* jshint validthis:true */ 36 | return '/' + relative.encode.call(this, absolute); 37 | } 38 | -------------------------------------------------------------------------------- /codec/source-relative.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | fs = require('fs'); 5 | 6 | /** 7 | * Codec for relative paths with respect to the source directory. 8 | * @type {{name:string, decode: function, encode: function, root: function}} 9 | */ 10 | module.exports = { 11 | name : 'sourceRelative', 12 | decode: decode, 13 | encode: encode, 14 | root : root 15 | }; 16 | 17 | /** 18 | * Decode the given uri. 19 | * Any path without leading slash is tested against source directory. 20 | * @this {{options: object}} A loader or compilation 21 | * @param {string} uri A source uri to decode 22 | * @returns {boolean|string} False where unmatched else the decoded path 23 | */ 24 | function decode(uri) { 25 | /* jshint validthis:true */ 26 | var base = !uri.startsWith('/') && this.context, 27 | absFile = !!base && path.normalize(path.join(base, uri)), 28 | isValid = !!absFile && fs.existsSync(absFile) && fs.statSync(absFile).isFile(); 29 | return isValid && absFile; 30 | } 31 | 32 | /** 33 | * Encode the given file path. 34 | * @this {{options: object}} A loader or compilation 35 | * @param {string} absolute An absolute file path to encode 36 | * @returns {string} A uri without leading slash 37 | */ 38 | function encode(absolute) { 39 | /* jshint validthis:true */ 40 | return path.relative(this.context, absolute); 41 | } 42 | 43 | /** 44 | * The source-map root where relevant. 45 | * @this {{options: object}} A loader or compilation 46 | * @returns {string|undefined} The source-map root applicable to any encoded uri 47 | */ 48 | function root() { 49 | /* jshint validthis:true */ 50 | return this.context; 51 | } 52 | -------------------------------------------------------------------------------- /codec/source-root-relative.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var relative = require('./source-relative'); 4 | 5 | /** 6 | * Codec for relative paths with respect to the source directory. 7 | * @type {{name:string, decode: function, encode: function, root: function}} 8 | */ 9 | module.exports = { 10 | name : 'sourceRootRelative', 11 | decode: decode, 12 | encode: encode, 13 | root : relative.root 14 | }; 15 | 16 | /** 17 | * Decode the given uri. 18 | * Any path with leading slash is tested against source directory. 19 | * @this {{options: object}} A loader or compilation 20 | * @param {string} uri A source uri to decode 21 | * @returns {boolean|string} False where unmatched else the decoded path 22 | */ 23 | function decode(uri) { 24 | /* jshint validthis:true */ 25 | return uri.startsWith('/') && relative.decode.call(this, uri.slice(1)); 26 | } 27 | 28 | /** 29 | * Encode the given file path. 30 | * @this {{options: object}} A loader or compilation 31 | * @param {string} absolute An absolute file path to encode 32 | * @returns {string} A uri with leading slash 33 | */ 34 | function encode(absolute) { 35 | /* jshint validthis:true */ 36 | return '/' + relative.encode.call(this, absolute); 37 | } 38 | -------------------------------------------------------------------------------- /codec/utility/enhanced-relative.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'), 4 | path = require('path'); 5 | 6 | var cache; 7 | 8 | /** 9 | * Perform path.relative() but try to detect and correct sym-linked node modules. 10 | * @param {string} from The base path 11 | * @param {string} to The full path 12 | */ 13 | function enhancedRelative(from, to) { 14 | 15 | // relative path 16 | var relative = path.relative(from, to); 17 | 18 | // trailing is the relative path portion without any '../' 19 | var trailing = relative.replace(/^\.{2}[\\\/]/, ''), 20 | leading = to.replace(trailing, ''); 21 | 22 | // within project is what we want 23 | var isInProject = (relative === trailing); 24 | if (isInProject) { 25 | return relative; 26 | } 27 | // otherwise look at symbolic linked modules 28 | else { 29 | var splitTrailing = trailing.split(/[\\\/]/); 30 | 31 | // ensure failures can retry with fresh cache 32 | for (var i = cache ? 2 : 1, foundPath = false; (i > 0) && !foundPath; i--) { 33 | 34 | // ensure cache 35 | cache = cache || indexLinkedModules(from); 36 | 37 | // take elements from the trailing path and append them the the leading path in an attempt to find a package.json 38 | for (var j = 0; (j < splitTrailing.length) && !foundPath; j++) { 39 | 40 | // find the name of packages in the actual file location 41 | // start at the lowest concrete directory that appears in the relative path 42 | var packagePath = path.join.apply(path, [leading].concat(splitTrailing.slice(0, j + 1))), 43 | packageJsonPath = path.join(packagePath, 'package.json'), 44 | packageName = fs.existsSync(packageJsonPath) && require(packageJsonPath).name; 45 | 46 | // lookup any package name in the cache 47 | var linkedPackagePath = !!packageName && cache[packageName]; 48 | if (linkedPackagePath) { 49 | 50 | // the remaining portion of the trailing path, not including the package path 51 | var remainingPath = path.join.apply(path, splitTrailing.slice(j + 1)); 52 | 53 | // validate the remaining path in the linked location 54 | // failure implies we will keep trying nested sym-linked packages 55 | var linkedFilePath = path.join(linkedPackagePath, remainingPath), 56 | isValid = !!linkedFilePath && fs.existsSync(linkedFilePath) && 57 | fs.statSync(linkedFilePath).isFile(); 58 | 59 | // path is found where valid 60 | foundPath = isValid && linkedFilePath; 61 | } 62 | } 63 | 64 | // cache cannot be trusted if a file can't be found 65 | // set the cache to false to trigger its rebuild 66 | cache = !!foundPath && cache; 67 | } 68 | 69 | // the relative path should now be within the project 70 | return foundPath ? path.relative(from, foundPath) : relative; 71 | } 72 | } 73 | 74 | module.exports = enhancedRelative; 75 | 76 | /** 77 | * Make a hash of linked modules within the given directory by breadth-first search. 78 | * @param {string} directory A path to start searching 79 | * @returns {object} A collection of sym-linked paths within the project keyed by their package name 80 | */ 81 | function indexLinkedModules(directory) { 82 | var buffer = listSymLinkedModules(directory), 83 | hash = {}; 84 | 85 | // while there are items in the buffer 86 | while (buffer.length > 0) { 87 | var modulePath = buffer.shift(), 88 | packageJsonPath = path.join(modulePath, 'package.json'), 89 | packageName = fs.existsSync(packageJsonPath) && require(packageJsonPath).name; 90 | if (packageName) { 91 | 92 | // add this path keyed by package name, so long as it doesn't exist at a lower level 93 | hash[packageName] = hash[packageName] || modulePath; 94 | 95 | // detect nested module and push to the buffer (breadth-first) 96 | buffer.push.apply(buffer, listSymLinkedModules(modulePath)); 97 | } 98 | } 99 | return hash; 100 | 101 | function listSymLinkedModules(directory) { 102 | var modulesPath = path.join(directory, 'node_modules'), 103 | hasNodeModules = fs.existsSync(modulesPath) && fs.statSync(modulesPath).isDirectory(), 104 | subdirectories = !!hasNodeModules && fs.readdirSync(modulesPath) || []; 105 | 106 | return subdirectories 107 | .map(joinDirectory) 108 | .filter(testIsSymLink); 109 | 110 | function joinDirectory(subdirectory) { 111 | return path.join(modulesPath, subdirectory); 112 | } 113 | 114 | function testIsSymLink(directory) { 115 | return fs.lstatSync(directory).isSymbolicLink(); // must use lstatSync not statSync 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /codec/utility/get-context-directory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | 5 | /** 6 | * Infer the compilation context directory from options. 7 | * Relative paths are resolved against process.cwd(). 8 | * @this {{options: object}} A loader or compilation 9 | * @returns {string} process.cwd() where not defined else the output path string 10 | */ 11 | function getContextDirectory() { 12 | /* jshint validthis:true */ 13 | var context = this.options ? this.options.context : null; 14 | return !!context && path.resolve(context) || process.cwd(); 15 | } 16 | 17 | module.exports = getContextDirectory; 18 | -------------------------------------------------------------------------------- /codec/utility/get-output-directory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | fs = require('fs'); 5 | 6 | var getContextDirectory = require('./get-context-directory'); 7 | 8 | /** 9 | * Infer the compilation output directory from options. 10 | * Relative paths are resolved against the compilation context (or process.cwd() where not specified). 11 | * @this {{options: object}} A loader or compilation 12 | * @returns {undefined|string} The output path string, where defined 13 | */ 14 | function getOutputDirectory() { 15 | /* jshint validthis:true */ 16 | var base = this.options && this.options.output ? this.options.output.directory : null, 17 | absBase = !!base && path.resolve(getContextDirectory.call(this), base), 18 | isValid = !!absBase && fs.existsSync(absBase) && fs.statSync(absBase).isDirectory(); 19 | return isValid ? absBase : undefined; 20 | } 21 | 22 | module.exports = getOutputDirectory; 23 | -------------------------------------------------------------------------------- /codec/webpack-bootstrap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Codec for webpack generated bootstrap code. 5 | * @type {{name:string, decode:function, abstract:boolean}} 6 | */ 7 | module.exports = { 8 | name : 'webpackBootstrap', 9 | decode : decode, 10 | abstract: true 11 | }; 12 | 13 | /** 14 | * Validate the given uri (abstract). 15 | * @this {{options: object}} A loader or compilation 16 | * @param {string} uri A source uri to decode 17 | * @returns {boolean|string} False where unmatched else True 18 | */ 19 | function decode(uri) { 20 | return /^webpack\/bootstrap\s+\w{20}$/.test(uri); 21 | } 22 | -------------------------------------------------------------------------------- /codec/webpack-protocol.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var projectRelative = require('./project-relative'); 4 | 5 | /** 6 | * Codec for relative paths with respect to the context directory, preceded by a webpack:// protocol. 7 | * @type {{name:string, decode: function, encode: function, root: function}} 8 | */ 9 | module.exports = { 10 | name : 'webpackProtocol', 11 | decode: decode, 12 | encode: encode, 13 | root : root 14 | }; 15 | 16 | /** 17 | * Decode the given uri. 18 | * @this {{options: object}} A loader or compilation 19 | * @param {string} uri A source uri to decode 20 | * @returns {boolean|string} False where unmatched else the decoded path 21 | */ 22 | function decode(uri) { 23 | /* jshint validthis:true */ 24 | var analysis = /^webpack:\/{2}(.*)$/.exec(uri); 25 | return !!analysis && projectRelative.decode.call(this, analysis[1]); 26 | } 27 | 28 | /** 29 | * Encode the given file path. 30 | * @this {{options: object}} A loader or compilation 31 | * @param {string} absolute An absolute file path to encode 32 | * @returns {string} A uri 33 | */ 34 | function encode(absolute) { 35 | /* jshint validthis:true */ 36 | return 'webpack://' + projectRelative.encode.call(this, absolute); 37 | } 38 | 39 | /** 40 | * The source-map root where relevant. 41 | * @this {{options: object}} A loader or compilation 42 | * @returns {string|undefined} The source-map root applicable to any encoded uri 43 | */ 44 | function root() { 45 | } 46 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License http://opensource.org/licenses/MIT 3 | * Author: Ben Holloway @bholloway 4 | */ 5 | 'use strict'; 6 | 7 | module.exports = Object.assign(require('./lib/loader'), { 8 | moduleFilenameTemplate: require('./lib/module-filename-template'), 9 | codec : require('./codec') 10 | }); -------------------------------------------------------------------------------- /lib/loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | 5 | var loaderUtils = require('loader-utils'); 6 | 7 | var process = require('./process'); 8 | 9 | /** 10 | * Webpack loader that manipulates the source-map of a preceding loader. 11 | * @this {object} The loader context 12 | * @param {string} content The content 13 | * @param {object} sourceMap The source-map 14 | * @returns {string|String} 15 | */ 16 | function loader(content, sourceMap) { 17 | /* jshint validthis:true */ 18 | 19 | // loader result is cacheable 20 | this.cacheable(); 21 | 22 | // webpack 1: prefer loader query, else options object 23 | // webpack 2: prefer loader options 24 | // webpack 3: deprecate loader.options object 25 | // webpack 4: loader.options no longer defined 26 | var options = Object.assign( 27 | {}, 28 | this.options && this.options.adjustSourcemapLoader, 29 | loaderUtils.getOptions(this), 30 | {sep: path.sep} 31 | ); 32 | 33 | // process the source-map 34 | var outputMap = process(this, options, sourceMap); 35 | 36 | // need to use callback when there are multiple arguments 37 | this.callback(null, content, outputMap); 38 | } 39 | 40 | module.exports = loader; 41 | -------------------------------------------------------------------------------- /lib/module-filename-template.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var process = require('./process'); 4 | 5 | function moduleFilenameTemplate(options) { 6 | return function templateFn(parameters) { 7 | return process(parameters, options, parameters.resourcePath); 8 | }; 9 | } 10 | 11 | module.exports = moduleFilenameTemplate; -------------------------------------------------------------------------------- /lib/process/debug-message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PACKAGE_NAME = require('../../package.json').name, 4 | PADDING = (new Array(11)).join(' '); 5 | 6 | /** 7 | * Format a debug message 8 | * @param {{resourcePath:string, loaders:Array, loaderIndex:number}} context A loader or compilation 9 | * @param {{input:Array., absolute:Array., output:Array., root:string}} info Source-map info 10 | * @returns {string} An encoded debug string 11 | */ 12 | function debugMessage(context, info) { 13 | return [ 14 | ' ', 15 | PACKAGE_NAME + ':', 16 | ' ' + context.resourcePath, 17 | formatField('@', precedingRequest(context)), 18 | formatField('INPUT', info.input || '(source-map absent)'), 19 | formatField('ABSOLUTE', info.absolute), 20 | formatField('OUTPUT', info.output), 21 | formatField('ROOT', info.root) 22 | ] 23 | .filter(Boolean) 24 | .join('\n'); 25 | } 26 | 27 | module.exports = debugMessage; 28 | 29 | /** 30 | * Find the request that precedes this loader in the loader chain 31 | * @param {{loaders:Array, loaderIndex:number}} loader The loader context 32 | * @returns {string} The request of the preceding loader 33 | */ 34 | function precedingRequest(loader) { 35 | var isLoader = ('loaderIndex' in loader) && ('loaders' in loader) && Array.isArray(loader.loaders); 36 | if (isLoader) { 37 | var index = loader.loaderIndex + 1; 38 | return (index in loader.loaders) ? loader.loaders[index].request : '(no preceding loader)'; 39 | } 40 | } 41 | 42 | /** 43 | * Where the data is truthy then format it with a right-aligned title. 44 | * @param {string} title 45 | * @param {*} data The data to display 46 | * @returns {boolean|string} False where data is falsey, else formatted message 47 | */ 48 | function formatField(title, data) { 49 | return !!data && (rightAlign(title) + formatData(data)); 50 | 51 | function rightAlign(text) { 52 | return (PADDING + text + ' ').slice(-PADDING.length); 53 | } 54 | 55 | function formatData(data) { 56 | return Array.isArray(data) ? data.join('\n' + PADDING) : data; 57 | } 58 | } -------------------------------------------------------------------------------- /lib/process/decode-sources-with.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var getFieldAsFn = require('./get-field-as-fn'); 4 | 5 | /** 6 | * Create a decoder for input sources using the given codec hash 7 | * @this {object} A loader or compilation 8 | * @param {Array.} codecs A list of codecs, each with a `decode` function 9 | * @param {boolean} mustDecode Return an error for a source that is not decoded 10 | * @returns {function(string):string|Error} A decode function that returns an absolute path or else an Error 11 | */ 12 | function decodeSourcesWith(codecs, mustDecode) { 13 | /* jshint validthis:true */ 14 | var context = this; 15 | 16 | // get a list of valid decoders 17 | var candidates = [].concat(codecs) 18 | .reduce(reduceValidDecoder.bind(null, codecs), []); 19 | 20 | /** 21 | * Attempt to decode the given source path using the previously supplied codecs 22 | * @param {string} inputSource A source path from a source map 23 | * @returns {Error|string|undefined} An absolute path if decoded else an error if encountered else undefined 24 | */ 25 | return function decode(inputSource) { 26 | 27 | // attempt all candidates until a match 28 | for (var i = 0, decoded = null; i < candidates.length && !decoded; i++) { 29 | 30 | // call the decoder 31 | try { 32 | decoded = candidates[i].decode.call(context, inputSource); 33 | } 34 | catch (exception) { 35 | return getNamedError(exception); 36 | } 37 | 38 | // match implies a return value 39 | if (decoded) { 40 | 41 | // abstract sources cannot be decoded, only validated 42 | if (candidates[i].abstract) { 43 | return undefined; 44 | } 45 | // non-string implies error 46 | if (typeof decoded !== 'string') { 47 | return getNamedError('Decoder returned a truthy value but it is not a string:\n' + decoded); 48 | } 49 | // otherwise success 50 | else { 51 | return decoded; 52 | } 53 | } 54 | } 55 | 56 | // default is undefined or error 57 | return mustDecode ? new Error('No viable decoder for source: ' + inputSource) : undefined; 58 | 59 | function getNamedError(details) { 60 | var name = candidates[i].name || '(unnamed)', 61 | message = [ 62 | 'Decoding with codec: ' + name, 63 | 'Incoming source: ' + inputSource, 64 | details && (details.stack ? details.stack : details) 65 | ] 66 | .filter(Boolean) 67 | .join('\n'); 68 | return new Error(message); 69 | } 70 | }; 71 | } 72 | 73 | module.exports = decodeSourcesWith; 74 | 75 | function reduceValidDecoder(reduced, codec) { 76 | var decoder = getFieldAsFn('decode')(codec); 77 | return decoder ? reduced.concat(codec) : reduced; 78 | } -------------------------------------------------------------------------------- /lib/process/encode-sources-with.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var getFieldAsFn = require('./get-field-as-fn'), 4 | CustomError = require('./get-error'); 5 | 6 | /** 7 | * Create an encoder for output sources using the given codec hash 8 | * @throws Error Where the given codec is missing an encode function 9 | * @this {object} A loader or compilation 10 | * @param {{encode:function}} codec A single codec with an `encode` function 11 | * @returns {function(string):string|Error|false} An encode function that takes an absolute path 12 | */ 13 | function encodeSourcesWith(codec) { 14 | /* jshint validthis:true */ 15 | var context = this, 16 | encoder = getFieldAsFn('encode')(codec); 17 | if (!encoder) { 18 | return new CustomError('Specified format does not support encoding (it lacks an "encoder" function)'); 19 | } 20 | else { 21 | return function encode(absoluteSource) { 22 | 23 | // call the encoder 24 | var encoded; 25 | try { 26 | encoded = absoluteSource && encoder.call(context, absoluteSource); 27 | } 28 | catch (exception) { 29 | return getNamedError(exception); 30 | } 31 | return encoded; 32 | 33 | function getNamedError(details) { 34 | var name = codec.name || '(unnamed)', 35 | message = [ 36 | 'Encoding with codec: ' + name, 37 | 'Absolute source: ' + absoluteSource, 38 | details && (details.stack ? details.stack : details) 39 | ] 40 | .filter(Boolean) 41 | .join('\n'); 42 | return new Error(message); 43 | } 44 | }; 45 | } 46 | } 47 | 48 | module.exports = encodeSourcesWith; 49 | -------------------------------------------------------------------------------- /lib/process/get-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PACKAGE_NAME = require('../../package.json').name; 4 | 5 | /** 6 | * Get an Error instance for the given message 7 | * @param {...*} message Any number of message arguments 8 | * @returns {Error} 9 | */ 10 | function getError() { 11 | var message = (PACKAGE_NAME + ':\n' + Array.prototype.slice.call(arguments).join(' ')) 12 | .split(/\s*\n\s*/) 13 | .join('\n '); 14 | return new Error(message); 15 | } 16 | 17 | module.exports = getError; -------------------------------------------------------------------------------- /lib/process/get-field-as-fn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Create a method that will retrieve the given field from an object where that field has a function value 5 | * @param {string} field The field to consider 6 | * @returns {function(object):function} A method that gets functions from the given field 7 | */ 8 | function getFieldAsFn(field) { 9 | return function getFromValue(value) { 10 | return !!value && (typeof value === 'object') && (typeof value[field] === 'function') && value[field]; 11 | }; 12 | } 13 | 14 | module.exports = getFieldAsFn; -------------------------------------------------------------------------------- /lib/process/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var debugMessage = require('./debug-message'), 4 | toRegExp = require('./to-reg-exp'), 5 | throwErrors = require('./throw-errors'), 6 | decodeSourcesWith = require('./decode-sources-with'), 7 | locateRootWith = require('./locate-root-with'), 8 | encodeSourcesWith = require('./encode-sources-with'), 9 | testCodec = require('./test-codec'); 10 | 11 | var CODECS = require('../../codec'); 12 | 13 | /** 14 | * Process the given source-map per the given options. 15 | * @param {{resourcePath:string, context:string, output:{path:string}}} context A loader or compilation 16 | * @param {{debug:boolean, fail:boolean, format:string|boolean, root:string, codecs:object}} opt Options hash 17 | * @param {object|string} sourceMapOrSource An incoming source-map or single source path 18 | * @returns {undefined|object|string} An amended source-map or source path else undefined 19 | */ 20 | function process(context, opt, sourceMapOrSource) { 21 | 22 | // default options 23 | var options = Object.assign({ 24 | sep : '/', 25 | debug : false, 26 | fail : false, 27 | format: false, 28 | root : false, 29 | codecs: CODECS 30 | }, opt); 31 | 32 | // validate codecs 33 | var codecs = options.codecs 34 | .filter(testCodec); 35 | 36 | // determine what is present 37 | var inputMap = !!sourceMapOrSource && (typeof sourceMapOrSource === 'object') && sourceMapOrSource, 38 | inputPath = (typeof sourceMapOrSource === 'string') && sourceMapOrSource, 39 | inputSources = inputMap && inputMap.sources || inputPath && [inputPath]; 40 | 41 | // what we need to produce 42 | var absoluteSources, 43 | outputSources, 44 | outputRoot, 45 | outputMap; 46 | 47 | if (inputSources) { 48 | 49 | // decode each source with the first valid codec 50 | absoluteSources = inputSources 51 | .map(decodeSourcesWith.call(context, codecs, options.fail)); 52 | 53 | // check for decode errors 54 | throwErrors(context.resourcePath, absoluteSources); 55 | 56 | // output map is a copy unless absent or we are removing 57 | outputMap = (!inputMap || (options.format === 'remove')) ? undefined : Object.assign({}, inputMap); 58 | 59 | // some change in format 60 | if (options.format) { 61 | 62 | // find the specified codec in the codecs list 63 | var codec = codecs 64 | .filter(testNamedCodec) 65 | .pop(); 66 | 67 | if (!codec) { 68 | throw new Error('Specified format "' + options.format + '" does not match any available codec.'); 69 | } 70 | 71 | // use the encoder where specified in 'format' 72 | outputSources = absoluteSources 73 | .map(encodeSourcesWith.call(context, codec)) 74 | .map(insertAbstractSources) 75 | .map(convertPathSep); 76 | 77 | outputRoot = !!options.root && locateRootWith.call(context, codec)() || undefined; 78 | 79 | // check for encode errors 80 | throwErrors(context.resourcePath, outputSources.concat(outputRoot)); 81 | 82 | // commit the change 83 | if (outputMap) { 84 | outputMap.sources = outputSources; 85 | outputMap.sourceRoot = outputRoot; 86 | } 87 | } 88 | } 89 | 90 | // debugging information 91 | var isDebug = toRegExp(options.debug).test(context.resourcePath); 92 | if (isDebug) { 93 | console.log(debugMessage(context, { 94 | input : inputSources, 95 | absolute: absoluteSources, 96 | output : outputSources, 97 | root : outputRoot 98 | })); 99 | } 100 | 101 | // complete 102 | return inputMap ? outputMap : outputSources ? outputSources[0] : undefined; 103 | 104 | function testNamedCodec(value) { 105 | return (value.name === options.format); 106 | } 107 | 108 | function insertAbstractSources(value, i) { 109 | return value || inputSources[i]; 110 | } 111 | 112 | function convertPathSep(value) { 113 | return (value instanceof Error) ? value : value.replace(/[\\\/]/g, options.sep); 114 | } 115 | } 116 | 117 | module.exports = process; 118 | -------------------------------------------------------------------------------- /lib/process/locate-root-with.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var getFieldAsFn = require('./get-field-as-fn'), 4 | CustomError = require('./get-error'); 5 | 6 | /** 7 | * Locate the root for input sources using the given codec hash 8 | * @throws Error Where the given codec is missing an encode function 9 | * @this {object} A loader or compilation 10 | * @param {{encode:function}} codec A single codec with an `encode` function 11 | * @returns {function(string):string|Error} An encode function that takes an absolute path 12 | */ 13 | function locateRootWith(codec) { 14 | /* jshint validthis:true */ 15 | var context = this, 16 | root = getFieldAsFn('root')(codec); 17 | if (!root) { 18 | return new CustomError('Specified format does not support encoding (it lacks a "root" function)'); 19 | } 20 | else { 21 | return function locate() { 22 | 23 | // call the root 24 | var located; 25 | try { 26 | located = root.call(context); 27 | } 28 | catch (exception) { 29 | return getNamedError(exception); 30 | } 31 | return located; 32 | 33 | function getNamedError(details) { 34 | var name = codec.name || '(unnamed)', 35 | message = [ 36 | 'Locating root with codec: ' + name, 37 | details && (details.stack ? details.stack : details) 38 | ] 39 | .filter(Boolean) 40 | .join('\n'); 41 | return new Error(message); 42 | } 43 | }; 44 | } 45 | } 46 | 47 | module.exports = locateRootWith; 48 | -------------------------------------------------------------------------------- /lib/process/test-codec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | /** 6 | * Reducer function that converts a codec list to a hash. 7 | * @throws Error on bad codec 8 | * @param {{name:string, decode:function, encode:function, root:function}} candidate A possible codec 9 | * @returns True where an error is not thrown 10 | */ 11 | function testCodec(candidate) { 12 | assert( 13 | !!candidate && (typeof candidate === 'object'), 14 | 'Codec must be an object' 15 | ); 16 | assert( 17 | (typeof candidate.name === 'string') && /^[\w-]+$/.test(candidate.name), 18 | 'Codec.name must be a kebab-case string' 19 | ); 20 | assert( 21 | (typeof candidate.decode === 'function') && (candidate.decode.length === 1), 22 | 'Codec.decode must be a function that accepts a single source string' 23 | ); 24 | assert( 25 | (typeof candidate.encode === 'undefined') || 26 | ((typeof candidate.encode === 'function') && (candidate.encode.length === 1)), 27 | 'Codec.encode must be a function that accepts a single absolute path string, or else be omitted' 28 | ); 29 | assert( 30 | (typeof candidate.root === 'undefined') || 31 | (typeof candidate.root === 'function') && (candidate.root.length === 0), 32 | 'Codec.root must be a function that accepts no arguments, or else be omitted' 33 | ); 34 | return true; 35 | } 36 | 37 | module.exports = testCodec; -------------------------------------------------------------------------------- /lib/process/throw-errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var getError = require('./get-error'); 4 | 5 | /** 6 | * Where the given list is non-null and contains error instances then consolidate and throw 7 | * @throws Error 8 | * @param {string} resourcePath The path to the resource being processed 9 | * @param {null|Array} candidates A possible Array with possible error elements 10 | */ 11 | function throwErrors(resourcePath, candidates) { 12 | var errors = !!candidates && candidates 13 | .filter(testIsError) 14 | .map(getMessage); 15 | 16 | var hasError = !!errors && errors.length; 17 | if (hasError) { 18 | throw getError(['For resource: ' + resourcePath].concat(errors).join('\n')); 19 | } 20 | 21 | function testIsError(candidate) { 22 | return !!candidate && (typeof candidate === 'object') && (candidate instanceof Error); 23 | } 24 | 25 | function getMessage(error) { 26 | return error.message; 27 | } 28 | } 29 | 30 | module.exports = throwErrors; -------------------------------------------------------------------------------- /lib/process/to-reg-exp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var regexParser = require('regex-parser'); 4 | 5 | var REGEXP = /(\/?)(.+)\1([a-z]*)/i; 6 | 7 | /** 8 | * Parse the give value as a regular expression or give a pass-none expression where it is invalid 9 | * @param {RegExp|string|*} value An existing expression, or its string representation, or degenerate value 10 | * @returns {RegExp} The given expression or one matching the RegExp string else a pass-none expression 11 | */ 12 | function toRegExp(value) { 13 | return ((typeof value === 'object') && (typeof value.test === 'function') && value) || 14 | ((typeof value === 'string') && REGEXP.test(value) && regexParser(value)) || 15 | (/^true$|^$/.test(value) && /.*/) || 16 | /matchnone^/; 17 | } 18 | 19 | module.exports = toRegExp; -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adjust-sourcemap-loader", 3 | "version": "5.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balanced-match": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 11 | "dev": true 12 | }, 13 | "brace-expansion": { 14 | "version": "1.1.11", 15 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 16 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 17 | "dev": true, 18 | "requires": { 19 | "balanced-match": "1.0.0", 20 | "concat-map": "0.0.1" 21 | } 22 | }, 23 | "cli": { 24 | "version": "1.0.1", 25 | "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", 26 | "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", 27 | "dev": true, 28 | "requires": { 29 | "exit": "0.1.2", 30 | "glob": "7.1.6" 31 | } 32 | }, 33 | "concat-map": { 34 | "version": "0.0.1", 35 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 36 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 37 | "dev": true 38 | }, 39 | "console-browserify": { 40 | "version": "1.1.0", 41 | "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", 42 | "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", 43 | "dev": true, 44 | "requires": { 45 | "date-now": "0.1.4" 46 | } 47 | }, 48 | "core-util-is": { 49 | "version": "1.0.2", 50 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 51 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 52 | "dev": true 53 | }, 54 | "date-now": { 55 | "version": "0.1.4", 56 | "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", 57 | "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", 58 | "dev": true 59 | }, 60 | "dom-serializer": { 61 | "version": "0.2.2", 62 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", 63 | "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", 64 | "dev": true, 65 | "requires": { 66 | "domelementtype": "2.0.2", 67 | "entities": "2.1.0" 68 | }, 69 | "dependencies": { 70 | "domelementtype": { 71 | "version": "2.0.2", 72 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz", 73 | "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==", 74 | "dev": true 75 | }, 76 | "entities": { 77 | "version": "2.1.0", 78 | "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", 79 | "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", 80 | "dev": true 81 | } 82 | } 83 | }, 84 | "domelementtype": { 85 | "version": "1.3.1", 86 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", 87 | "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", 88 | "dev": true 89 | }, 90 | "domhandler": { 91 | "version": "2.3.0", 92 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", 93 | "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", 94 | "dev": true, 95 | "requires": { 96 | "domelementtype": "1.3.1" 97 | } 98 | }, 99 | "domutils": { 100 | "version": "1.5.1", 101 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", 102 | "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", 103 | "dev": true, 104 | "requires": { 105 | "dom-serializer": "0.2.2", 106 | "domelementtype": "1.3.1" 107 | } 108 | }, 109 | "entities": { 110 | "version": "1.0.0", 111 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", 112 | "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", 113 | "dev": true 114 | }, 115 | "exit": { 116 | "version": "0.1.2", 117 | "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", 118 | "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", 119 | "dev": true 120 | }, 121 | "fs.realpath": { 122 | "version": "1.0.0", 123 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 124 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 125 | "dev": true 126 | }, 127 | "glob": { 128 | "version": "7.1.6", 129 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 130 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 131 | "dev": true, 132 | "requires": { 133 | "fs.realpath": "1.0.0", 134 | "inflight": "1.0.6", 135 | "inherits": "2.0.4", 136 | "minimatch": "3.0.4", 137 | "once": "1.4.0", 138 | "path-is-absolute": "1.0.1" 139 | } 140 | }, 141 | "htmlparser2": { 142 | "version": "3.8.3", 143 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", 144 | "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", 145 | "dev": true, 146 | "requires": { 147 | "domelementtype": "1.3.1", 148 | "domhandler": "2.3.0", 149 | "domutils": "1.5.1", 150 | "entities": "1.0.0", 151 | "readable-stream": "1.1.14" 152 | } 153 | }, 154 | "inflight": { 155 | "version": "1.0.6", 156 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 157 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 158 | "dev": true, 159 | "requires": { 160 | "once": "1.4.0", 161 | "wrappy": "1.0.2" 162 | } 163 | }, 164 | "inherits": { 165 | "version": "2.0.4", 166 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 167 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 168 | "dev": true 169 | }, 170 | "isarray": { 171 | "version": "0.0.1", 172 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 173 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 174 | "dev": true 175 | }, 176 | "jshint": { 177 | "version": "2.13.5", 178 | "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.5.tgz", 179 | "integrity": "sha512-dB2n1w3OaQ35PLcBGIWXlszjbPZwsgZoxsg6G8PtNf2cFMC1l0fObkYLUuXqTTdi6tKw4sAjfUseTdmDMHQRcg==", 180 | "dev": true, 181 | "requires": { 182 | "cli": "1.0.1", 183 | "console-browserify": "1.1.0", 184 | "exit": "0.1.2", 185 | "htmlparser2": "3.8.3", 186 | "lodash": "4.17.21", 187 | "minimatch": "3.0.4", 188 | "strip-json-comments": "1.0.4" 189 | }, 190 | "dependencies": { 191 | "lodash": { 192 | "version": "4.17.21", 193 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 194 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 195 | "dev": true 196 | } 197 | } 198 | }, 199 | "loader-utils": { 200 | "version": "3.2.0", 201 | "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.0.tgz", 202 | "integrity": "sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ==" 203 | }, 204 | "minimatch": { 205 | "version": "3.0.4", 206 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 207 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 208 | "dev": true, 209 | "requires": { 210 | "brace-expansion": "1.1.11" 211 | } 212 | }, 213 | "once": { 214 | "version": "1.4.0", 215 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 216 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 217 | "dev": true, 218 | "requires": { 219 | "wrappy": "1.0.2" 220 | } 221 | }, 222 | "path-is-absolute": { 223 | "version": "1.0.1", 224 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 225 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 226 | "dev": true 227 | }, 228 | "readable-stream": { 229 | "version": "1.1.14", 230 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 231 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 232 | "dev": true, 233 | "requires": { 234 | "core-util-is": "1.0.2", 235 | "inherits": "2.0.4", 236 | "isarray": "0.0.1", 237 | "string_decoder": "0.10.31" 238 | } 239 | }, 240 | "regex-parser": { 241 | "version": "2.2.11", 242 | "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", 243 | "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" 244 | }, 245 | "string_decoder": { 246 | "version": "0.10.31", 247 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 248 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", 249 | "dev": true 250 | }, 251 | "strip-json-comments": { 252 | "version": "1.0.4", 253 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", 254 | "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", 255 | "dev": true 256 | }, 257 | "wrappy": { 258 | "version": "1.0.2", 259 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 260 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 261 | "dev": true 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adjust-sourcemap-loader", 3 | "version": "5.0.0", 4 | "description": "Webpack loader that adjusts source maps", 5 | "main": "index.js", 6 | "engines": { 7 | "node": ">=12" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/bholloway/adjust-sourcemap-loader.git" 12 | }, 13 | "keywords": [ 14 | "webpack", 15 | "loader", 16 | "source-map", 17 | "sourcemap", 18 | "sources", 19 | "resolve", 20 | "adjust" 21 | ], 22 | "author": "bholloway", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/bholloway/adjust-sourcemap-loader/issues" 26 | }, 27 | "homepage": "https://github.com/bholloway/adjust-sourcemap-loader", 28 | "dependencies": { 29 | "loader-utils": "^3.2.0", 30 | "regex-parser": "^2.2.11" 31 | }, 32 | "devDependencies": { 33 | "jshint": "^2.13.5" 34 | }, 35 | "scripts": { 36 | "lint": "jshint index.js lib codec" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Adjust Source-map Loader 2 | 3 | [![NPM](https://nodei.co/npm/adjust-sourcemap-loader.png)](http://github.com/bholloway/adjust-sourcemap-loader) 4 | 5 | Webpack loader that adjusts source maps. 6 | 7 | Use as a **loader** to debug source-maps or to adjust source-maps between other loaders. 8 | 9 | Use as a **module filename template** to ensure the final source-map are to your liking. 10 | 11 | ## Usage : Loader 12 | 13 | ``` javascript 14 | require('adjust-sourcemap?format=absolute!babel?sourceMap'); 15 | ``` 16 | 17 | ### Source maps required 18 | 19 | Note that **source maps** must be enabled on any preceding loader. In the above example we use `babel?sourceMap`. 20 | 21 | ### Apply via webpack config 22 | 23 | It is preferable to adjust your `webpack.config` so to avoid having to prefix every `require()` statement: 24 | 25 | ``` javascript 26 | module.exports = { 27 | module: { 28 | loaders: [ 29 | { 30 | test : /\.js/, 31 | loaders: ['adjust-sourcemap?format=absolute', 'babel?sourceMap'] 32 | } 33 | ] 34 | } 35 | }; 36 | ``` 37 | 38 | ## Usage : Module filename template 39 | 40 | Specifying a certain format as the final step in a loader chain will **not** influence the final source format that Webpack will output. Instead the format is determined by the **module filename template**. 41 | 42 | There are limitations to the filename templating that Webpack provides. This package may also operate as a custom template function that will convert output source-map sources to the desired `format`. 43 | 44 | In the following example we ensure project-relative source-map sources are output. 45 | 46 | ```javascript 47 | var templateFn = require('adjust-sourcemap-loader') 48 | .moduleFilenameTemplate({ 49 | format: 'projectRelative' 50 | }); 51 | 52 | module.exports = { 53 | output: { 54 | ... 55 | devtoolModuleFilenameTemplate : templateFn, 56 | devtoolFallbackModuleFilenameTemplate: templateFn 57 | } 58 | }; 59 | ``` 60 | 61 | ## Options 62 | 63 | As a loader, options may be set using [query parameters](https://webpack.github.io/docs/using-loaders.html#query-parameters) or by using [programmatic parameters](https://webpack.github.io/docs/how-to-write-a-loader.html#programmable-objects-as-query-option). Programmatic means the following in your `webpack.config`. 64 | 65 | ```javascript 66 | module.exports = { 67 | adjustSourcemapLoader: { 68 | ... 69 | } 70 | } 71 | ``` 72 | 73 | Where `...` is a hash of any of the following options. 74 | 75 | * **`debug`** : `boolean|RegExp` May be used alone (boolean) or with a `RegExp` to match the resource(s) you are interested in debugging. 76 | 77 | * **`fail`** : `boolean` Implies an **Error** if a source-map source cannot be decoded. 78 | 79 | * **`format`** : `string` Optional output format for source-map `sources`. Must be the name of one of the available `codecs`. Omitting the format will result in **no change** and the outgoing source-map will match the incomming one. 80 | 81 | * **`root`** : `boolean` A boolean flag that indices that a `sourceRoot` path sould be included in the output map. This is contingent on a `format` being specified. 82 | 83 | * **`codecs`** : `Array.<{name:string, decode:function, encode:function, root:function}>` Optional Array of codecs. There are a number of built-in codecs available. If you specify you own codecs you will loose those that are built-in. However you can include them from the `codec/` directory. 84 | 85 | Note that **query** parameters take precedence over **programmatic** parameters. 86 | 87 | ### Changing the format 88 | 89 | Built-in codecs that may be specified as a `format` include: 90 | 91 | * `absolute` 92 | * `outputRelative` 93 | * `projectRelative` 94 | * `webpackProtocol` 95 | * `sourceRelative` (works for loader only, **not** Module filename template) 96 | 97 | ### Specifying codecs 98 | 99 | There are additional built-in codecs that do not support encoding. These are still necessary to decode source-map sources. If you specify your own `options.codecs` then you should **also include the built-in codecs**. Otherwise you will find that some sources cannot be decoded. 100 | 101 | The existing codecs may be found in `/codec`, or on the loader itself: 102 | 103 | ```javascript 104 | var inBuiltCodecs = require('adjust-sourcemap-loader').codecs, 105 | myCodecs = [ 106 | { 107 | name : 'foo', 108 | decode: function(uri) {...}, 109 | encode: function(absolute) {...}, 110 | root : function() {...} 111 | }, 112 | ... 113 | ]; 114 | 115 | module.exports = { 116 | adjustSourcemapLoader: { 117 | codecs: inBuiltCodecs.concat(myCodecs) 118 | } 119 | } 120 | ``` 121 | 122 | The codec **order is important**. Those that come first have precedence. Any codec that detects a distinct URI should be foremost so that illegal paths are not encountered by successive codecs. 123 | 124 | ### Abstract codecs 125 | 126 | A codec that detects generated code and cannot `decode()` a URI to an absolute file path. 127 | 128 | Instead of implementing `encode()` or `root()` it should instead specify `abstract:true`. Its `decode()` function then may return `boolean` where it detects such generated sources. 129 | 130 | For example, a built-in abstract codec will match the **Webpack bootstrap** code and ensure that its illegal source uri is not encountered by later coders. 131 | 132 | ## How it works 133 | 134 | The loader will receive a source map as its second parameter, so long as the preceding loader was using source-maps. 135 | 136 | The exception is the **css-loader** where the source-map is in the content, which is **not currently supported** . 137 | 138 | The source-map `sources` are parsed by applying **codec.decode()** functions until one of them returns an absolute path to a file that exists. The exception is abstract codecs, where the source with remain unchanged. 139 | 140 | If a format is specified then the source-map `sources` are recreated by applying the **codec.encode()** function for the stated `format` and (where the `root` option is specified) the **codec.root()** function will set the source-map `sourceRoot`. 141 | 142 | If a codec does not specify **codec.encode()** or **codec.root()** then it may **not** be used as the `format`. 143 | 144 | --------------------------------------------------------------------------------