├── .editorconfig
├── .eslintrc.json
├── .gitattributes
├── .github
├── FUNDING.yml
└── workflows
│ └── nodejs.yml
├── .gitignore
├── .prettierignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── SECURITY.md
├── lib
├── getHashDigest.js
├── hash
│ ├── BatchedHash.js
│ ├── BulkUpdateDecorator.js
│ ├── md4.js
│ ├── wasm-hash.js
│ └── xxhash64.js
├── index.js
├── interpolateName.js
├── isUrlRequest.js
└── urlToRequest.js
├── package.json
├── test
├── .eslintrc.json
├── getHashDigest.test.js
├── interpolateName.test.js
├── isUrlRequest.test.js
└── urlToRequest.test.js
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [test/cases/parsing/bom/bomfile.{css,js}]
12 | charset = utf-8-bom
13 |
14 | [*.md]
15 | insert_final_newline = true
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "plugins": ["node"],
4 | "extends": ["eslint:recommended", "plugin:node/recommended"],
5 | "env": {
6 | "node": true
7 | },
8 | "rules": {
9 | "no-template-curly-in-string": "error",
10 | "no-caller": "error",
11 | "yoda": "error",
12 | "eqeqeq": "error",
13 | "no-extra-bind": "error",
14 | "no-process-exit": "error",
15 | "no-loop-func": "error",
16 | "no-console": "off",
17 | "valid-jsdoc": "error",
18 | "no-var": "error",
19 | "prefer-const": "error",
20 | "prefer-arrow-callback": "error",
21 | "object-shorthand": "error"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | open_collective: webpack
2 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: loader-utils
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - next
8 | pull_request:
9 | branches:
10 | - master
11 | - next
12 |
13 | permissions:
14 | contents: read # to fetch code (actions/checkout)
15 |
16 | jobs:
17 | lint:
18 | permissions:
19 | contents: read # to fetch code (actions/checkout)
20 | pull-requests: read # to get commits in PR (wagoid/commitlint-github-action)
21 |
22 | name: Lint - ${{ matrix.os }} - Node v${{ matrix.node-version }}
23 |
24 | env:
25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26 |
27 | strategy:
28 | matrix:
29 | os: [ubuntu-latest]
30 | node-version: [12.x]
31 |
32 | runs-on: ${{ matrix.os }}
33 |
34 | steps:
35 | - uses: actions/checkout@v2
36 | with:
37 | fetch-depth: 0
38 |
39 | - name: Use Node.js ${{ matrix.node-version }}
40 | uses: actions/setup-node@v2
41 | with:
42 | node-version: ${{ matrix.node-version }}
43 | cache: "yarn"
44 |
45 | - name: Install dependencies
46 | run: yarn
47 |
48 | - name: Lint
49 | run: yarn lint
50 |
51 | - name: Security audit
52 | run: yarn audit --groups dependencies
53 |
54 | - name: Check commit message
55 | uses: wagoid/commitlint-github-action@v4
56 |
57 | test:
58 | name: Test - ${{ matrix.os }} - Node v${{ matrix.node-version }}
59 |
60 | strategy:
61 | matrix:
62 | os: [ubuntu-latest, windows-latest, macos-latest]
63 | node-version: [12.x, 14.x, 16.x, 17.x]
64 |
65 | runs-on: ${{ matrix.os }}
66 |
67 | steps:
68 | - name: Setup Git
69 | if: matrix.os == 'windows-latest'
70 | run: git config --global core.autocrlf input
71 |
72 | - uses: actions/checkout@v2
73 | - uses: actions/github-script@v7
74 | id: calculate_architecture
75 | with:
76 | result-encoding: string
77 | script: |
78 | if ('${{ matrix.os }}' === 'macos-latest' && ('${{ matrix['node-version'] }}' === '12.x' || '${{ matrix['node-version'] }}' === '14.x')) {
79 | return "x64"
80 | } else {
81 | return ''
82 | }
83 | - name: Use Node.js ${{ matrix.node-version }}
84 | uses: actions/setup-node@v2
85 | with:
86 | node-version: ${{ matrix.node-version }}
87 | architecture: ${{ steps.calculate_architecture.outputs.result }}
88 | cache: "yarn"
89 |
90 | - name: Install dependencies
91 | run: yarn
92 |
93 | - name: Run tests
94 | run: yarn test:only
95 |
96 | - name: Submit coverage data to codecov
97 | uses: codecov/codecov-action@v2
98 | with:
99 | token: ${{ secrets.CODECOV_TOKEN }}
100 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | coverage
4 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /dist
3 | /node_modules
4 | /test/fixtures
5 | CHANGELOG.md
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 | ### [3.3.1](https://github.com/webpack/loader-utils/compare/v3.3.0...v3.3.1) (2024-06-05)
6 |
7 |
8 | ### Bug Fixes
9 |
10 | * base64safe regex ([3b2d3b1](https://github.com/webpack/loader-utils/commit/3b2d3b1379b644aeefbbe859c0baeadd0b732710))
11 |
12 | ## [3.3.0](https://github.com/webpack/loader-utils/compare/v3.2.2...v3.3.0) (2024-06-04)
13 |
14 |
15 | ### Features
16 |
17 | * add digestType 'base64safe' ([#259](https://github.com/webpack/loader-utils/issues/259)) ([af15793](https://github.com/webpack/loader-utils/commit/af157934abb1ee172cffd015acbabb065f0e1dbf))
18 |
19 | ### [3.2.2](https://github.com/webpack/loader-utils/compare/v3.2.1...v3.2.2) (2024-05-29)
20 |
21 |
22 | ### Bug Fixes
23 |
24 | * unreachable code for directories ([128f945](https://github.com/webpack/loader-utils/commit/128f945e8f66d0ad7d69cdf568a8aa8bce40633a))
25 |
26 | ### [3.2.1](https://github.com/webpack/loader-utils/compare/v3.2.0...v3.2.1) (2022-11-11)
27 |
28 |
29 | ### Bug Fixes
30 |
31 | * ReDoS problem ([#224](https://github.com/webpack/loader-utils/issues/224)) ([d2d752d](https://github.com/webpack/loader-utils/commit/d2d752d59629daee38f34b24307221349c490eb1))
32 |
33 | ## [3.2.0](https://github.com/webpack/loader-utils/compare/v3.1.3...v3.2.0) (2021-11-11)
34 |
35 |
36 | ### Features
37 |
38 | * hash uniformity for base digests ([451858b](https://github.com/webpack/loader-utils/commit/451858b0bb33911d52d2f03a6470fd2b86493b84))
39 |
40 | ### [3.1.3](https://github.com/webpack/loader-utils/compare/v3.1.2...v3.1.3) (2021-11-04)
41 |
42 |
43 | ### Bug Fixes
44 |
45 | * crash with md4 hash ([#198](https://github.com/webpack/loader-utils/issues/198)) ([ef084d4](https://github.com/webpack/loader-utils/commit/ef084d43ba29ebf3c3c0ea0939a5c58adad0bba2))
46 |
47 | ### [3.1.2](https://github.com/webpack/loader-utils/compare/v3.1.1...v3.1.2) (2021-11-04)
48 |
49 |
50 | ### Bug Fixes
51 |
52 | * bug with unicode characters ([#196](https://github.com/webpack/loader-utils/issues/196)) ([0426405](https://github.com/webpack/loader-utils/commit/04264056f951514955af7302510631f942276eec))
53 |
54 | ### [3.1.1](https://github.com/webpack/loader-utils/compare/v3.1.0...v3.1.1) (2021-11-04)
55 |
56 |
57 | ### Bug Fixes
58 |
59 | * base64 and unicode characters ([02b1f3f](https://github.com/webpack/loader-utils/commit/02b1f3fe6d718870b5ee7abc64519a1b2b5b8531))
60 |
61 | ## [3.1.0](https://github.com/webpack/loader-utils/compare/v3.0.0...v3.1.0) (2021-10-29)
62 |
63 |
64 | ### Features
65 |
66 | * added `md4` (wasm version) and `md4-native` (`crypto` module version) algorithms ([cbf9d1d](https://github.com/webpack/loader-utils/commit/cbf9d1dac866be50971d294c3baacda45527fb8e))
67 |
68 | ## [3.0.0](https://github.com/webpack/loader-utils/compare/v2.0.0...v3.0.0) (2021-10-20)
69 |
70 |
71 | ### ⚠ BREAKING CHANGES
72 |
73 | * minimum supported Node.js version is `12.13.0` ([93a87ce](https://github.com/webpack/loader-utils/commit/93a87cefd41cc69de0bc1f9099f7d753ed8cd557))
74 | * use `xxhash64` by default for `[hash]`/`[contenthash]` and `getHashDigest` API
75 | * `[emoji]` was removed without replacements, please use custom function if you need this
76 | * removed `getOptions` in favor `loaderContext.getOptions` (`loaderContext` is `this` inside loader function), note - special query parameters like `?something=true` is not supported anymore, if you need this please do it on `loader` side, but we strongly recommend avoid it, as alternative you can use `?something=1` and handle `1` as `true`
77 | * removed `getRemainingRequest` in favor `loaderContext.remainingRequest` (`loaderContext` is `this` inside loader function)
78 | * removed `getCurrentRequest` in favor `loaderContext.currentRequest` (`loaderContext` is `this` inside loader function)
79 | * removed `parseString` in favor `JSON.parse`
80 | * removed `parseQuery` in favor `new URLSearchParams(loaderContext.resourceQuery.slice(1))` where `loaderContext` is `this` in loader function
81 | * removed `stringifyRequest` in favor `JSON.stringify(loaderContext.utils.contextify(loaderContext.context || loaderContext.rootContext, request))` (`loaderContext` is `this` inside loader function), also it will be cachable and faster
82 | * `isUrlRequest` ignores only absolute URLs and `#hash` requests, `data URI` and root relative request are handled as requestable due webpack v5 support them
83 |
84 | ### Bug Fixes
85 |
86 | * allowed the `interpolateName` API works without options ([862ea7d](https://github.com/webpack/loader-utils/commit/862ea7d1d0226558f2750bec36da02492d1e516d))
87 |
88 | ## [2.0.0](https://github.com/webpack/loader-utils/compare/v1.4.0...v2.0.0) (2020-03-17)
89 |
90 |
91 | ### ⚠ BREAKING CHANGES
92 |
93 | * minimum required `Node.js` version is `8.9.0` ([#166](https://github.com/webpack/loader-utils/issues/166)) ([c937e8c](https://github.com/webpack/loader-utils/commit/c937e8c77231b42018be616b784a6b45eac86f8a))
94 | * the `getOptions` method returns empty object on empty query ([#167](https://github.com/webpack/loader-utils/issues/167)) ([b595cfb](https://github.com/webpack/loader-utils/commit/b595cfba022d3f04f3d310dd570b0253e461605b))
95 | * Use `md4` by default
96 |
97 |
98 | # [1.4.0](https://github.com/webpack/loader-utils/compare/v1.3.0...v1.4.0) (2020-02-19)
99 |
100 |
101 | ### Features
102 |
103 | * the `resourceQuery` is passed to the `interpolateName` method ([#163](https://github.com/webpack/loader-utils/issues/163)) ([cd0e428](https://github.com/webpack/loader-utils/commit/cd0e428))
104 |
105 |
106 |
107 |
108 | # [1.3.0](https://github.com/webpack/loader-utils/compare/v1.2.3...v1.3.0) (2020-02-19)
109 |
110 |
111 | ### Features
112 |
113 | * support the `[query]` template for the `interpolatedName` method ([#162](https://github.com/webpack/loader-utils/issues/162)) ([469eeba](https://github.com/webpack/loader-utils/commit/469eeba))
114 |
115 |
116 |
117 |
118 | ## [1.2.3](https://github.com/webpack/loader-utils/compare/v1.2.2...v1.2.3) (2018-12-27)
119 |
120 |
121 | ### Bug Fixes
122 |
123 | * **interpolateName:** don't interpolated `hashType` without `hash` or `contenthash` ([#140](https://github.com/webpack/loader-utils/issues/140)) ([3528fd9](https://github.com/webpack/loader-utils/commit/3528fd9))
124 |
125 |
126 |
127 |
128 | ## [1.2.2](https://github.com/webpack/loader-utils/compare/v1.2.1...v1.2.2) (2018-12-27)
129 |
130 |
131 | ### Bug Fixes
132 |
133 | * fixed a hash type extracting in interpolateName ([#137](https://github.com/webpack/loader-utils/issues/137)) ([f8a71f4](https://github.com/webpack/loader-utils/commit/f8a71f4))
134 |
135 |
136 |
137 |
138 | ## [1.2.1](https://github.com/webpack/loader-utils/compare/v1.2.0...v1.2.1) (2018-12-25)
139 |
140 |
141 | ### Bug Fixes
142 |
143 | * **isUrlRequest:** better handle absolute urls and non standards ([#134](https://github.com/webpack/loader-utils/issues/134)) ([aca43da](https://github.com/webpack/loader-utils/commit/aca43da))
144 |
145 |
146 | ### Reverts
147 |
148 | * PR [#79](https://github.com/webpack/loader-utils/issues/79) ([#135](https://github.com/webpack/loader-utils/issues/135)) ([73d350a](https://github.com/webpack/loader-utils/commit/73d350a))
149 |
150 |
151 |
152 |
153 | # [1.2.0](https://github.com/webpack/loader-utils/compare/v1.1.0...v1.2.0) (2018-12-24)
154 |
155 |
156 | ### Features
157 |
158 | * **interpolateName:** support `[contenthash]`
159 |
160 | ### Fixes
161 |
162 | * **urlToRequest:** empty urls are not rewritten to relative requests
163 | * **urlToRequest:** don't rewrite absolute urls
164 | * **isUrlRequest:** ignore all url with `extension` (like `moz-extension:`, `ms-browser-extension:` and etc)
165 | * **isUrlRequest:** ignore `about:blank`
166 | * **interpolateName:** failing explicitly when ran out of emoji
167 | * **interpolateName:** `[hash]` token regex in interpolate string to capture any hash algorithm name
168 | * **interpolateName:** parse string for emoji count before use
169 |
170 |
171 |
172 |
173 | # [1.1.0](https://github.com/webpack/loader-utils/compare/v1.0.4...v1.1.0) (2017-03-16)
174 |
175 |
176 | ### Features
177 |
178 | * **automatic-release:** Generation of automatic release ([7484d13](https://github.com/webpack/loader-utils/commit/7484d13))
179 | * **parseQuery:** export parseQuery ([ddf64e4](https://github.com/webpack/loader-utils/commit/ddf64e4))
180 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright JS Foundation and other contributors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | 'Software'), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # loader-utils
2 |
3 | ## Methods
4 |
5 | ### `urlToRequest`
6 |
7 | Converts some resource URL to a webpack module request.
8 |
9 | > i Before call `urlToRequest` you need call `isUrlRequest` to ensure it is requestable url
10 |
11 | ```javascript
12 | const url = "path/to/module.js";
13 |
14 | if (loaderUtils.isUrlRequest(url)) {
15 | // Logic for requestable url
16 | const request = loaderUtils.urlToRequest(url);
17 | } else {
18 | // Logic for not requestable url
19 | }
20 | ```
21 |
22 | Simple example:
23 |
24 | ```javascript
25 | const url = "path/to/module.js";
26 | const request = loaderUtils.urlToRequest(url); // "./path/to/module.js"
27 | ```
28 |
29 | #### Module URLs
30 |
31 | Any URL containing a `~` will be interpreted as a module request. Anything after the `~` will be considered the request path.
32 |
33 | ```javascript
34 | const url = "~path/to/module.js";
35 | const request = loaderUtils.urlToRequest(url); // "path/to/module.js"
36 | ```
37 |
38 | #### Root-relative URLs
39 |
40 | URLs that are root-relative (start with `/`) can be resolved relative to some arbitrary path by using the `root` parameter:
41 |
42 | ```javascript
43 | const url = "/path/to/module.js";
44 | const root = "./root";
45 | const request = loaderUtils.urlToRequest(url, root); // "./root/path/to/module.js"
46 | ```
47 |
48 | To convert a root-relative URL into a module URL, specify a `root` value that starts with `~`:
49 |
50 | ```javascript
51 | const url = "/path/to/module.js";
52 | const root = "~";
53 | const request = loaderUtils.urlToRequest(url, root); // "path/to/module.js"
54 | ```
55 |
56 | ### `interpolateName`
57 |
58 | Interpolates a filename template using multiple placeholders and/or a regular expression.
59 | The template and regular expression are set as query params called `name` and `regExp` on the current loader's context.
60 |
61 | ```javascript
62 | const interpolatedName = loaderUtils.interpolateName(
63 | loaderContext,
64 | name,
65 | options
66 | );
67 | ```
68 |
69 | The following tokens are replaced in the `name` parameter:
70 |
71 | - `[ext]` the extension of the resource
72 | - `[name]` the basename of the resource
73 | - `[path]` the path of the resource relative to the `context` query parameter or option.
74 | - `[folder]` the folder the resource is in
75 | - `[query]` the queryof the resource, i.e. `?foo=bar`
76 | - `[contenthash]` the hash of `options.content` (Buffer) (by default it's the hex digest of the `xxhash64` hash)
77 | - `[:contenthash::]` optionally one can configure
78 | - other `hashType`s, i. e. `xxhash64`, `sha1`, `md4` (wasm version), `native-md4` (`crypto` module version), `md5`, `sha256`, `sha512`
79 | - other `digestType`s, i. e. `hex`, `base26`, `base32`, `base36`, `base49`, `base52`, `base58`, `base62`, `base64`, `base64safe`
80 | - and `length` the length in chars
81 | - `[hash]` the hash of `options.content` (Buffer) (by default it's the hex digest of the `xxhash64` hash)
82 | - `[:hash::]` optionally one can configure
83 | - other `hashType`s, i. e. `xxhash64`, `sha1`, `md4` (wasm version), `native-md4` (`crypto` module version), `md5`, `sha256`, `sha512`
84 | - other `digestType`s, i. e. `hex`, `base26`, `base32`, `base36`, `base49`, `base52`, `base58`, `base62`, `base64`, `base64safe`
85 | - and `length` the length in chars
86 | - `[N]` the N-th match obtained from matching the current file name against `options.regExp`
87 |
88 | In loader context `[hash]` and `[contenthash]` are the same, but we recommend using `[contenthash]` for avoid misleading.
89 |
90 | `digestType` with `base64safe` don't contain `/`, `+` and `=` symbols.
91 |
92 | Examples
93 |
94 | ```javascript
95 | // loaderContext.resourcePath = "/absolute/path/to/app/js/javascript.js"
96 | loaderUtils.interpolateName(loaderContext, "js/[hash].script.[ext]", { content: ... });
97 | // => js/9473fdd0d880a43c21b7778d34872157.script.js
98 |
99 | // loaderContext.resourcePath = "/absolute/path/to/app/js/javascript.js"
100 | // loaderContext.resourceQuery = "?foo=bar"
101 | loaderUtils.interpolateName(loaderContext, "js/[hash].script.[ext][query]", { content: ... });
102 | // => js/9473fdd0d880a43c21b7778d34872157.script.js?foo=bar
103 |
104 | // loaderContext.resourcePath = "/absolute/path/to/app/js/javascript.js"
105 | loaderUtils.interpolateName(loaderContext, "js/[contenthash].script.[ext]", { content: ... });
106 | // => js/9473fdd0d880a43c21b7778d34872157.script.js
107 |
108 | // loaderContext.resourcePath = "/absolute/path/to/app/page.html"
109 | loaderUtils.interpolateName(loaderContext, "html-[hash:6].html", { content: ... });
110 | // => html-9473fd.html
111 |
112 | // loaderContext.resourcePath = "/absolute/path/to/app/flash.txt"
113 | loaderUtils.interpolateName(loaderContext, "[hash]", { content: ... });
114 | // => c31e9820c001c9c4a86bce33ce43b679
115 |
116 | // loaderContext.resourcePath = "/absolute/path/to/app/img/image.png"
117 | loaderUtils.interpolateName(loaderContext, "[sha512:hash:base64:7].[ext]", { content: ... });
118 | // => 2BKDTjl.png
119 | // use sha512 hash instead of xxhash64 and with only 7 chars of base64
120 |
121 | // loaderContext.resourcePath = "/absolute/path/to/app/img/myself.png"
122 | // loaderContext.query.name =
123 | loaderUtils.interpolateName(loaderContext, "picture.png");
124 | // => picture.png
125 |
126 | // loaderContext.resourcePath = "/absolute/path/to/app/dir/file.png"
127 | loaderUtils.interpolateName(loaderContext, "[path][name].[ext]?[hash]", { content: ... });
128 | // => /app/dir/file.png?9473fdd0d880a43c21b7778d34872157
129 |
130 | // loaderContext.resourcePath = "/absolute/path/to/app/js/page-home.js"
131 | loaderUtils.interpolateName(loaderContext, "script-[1].[ext]", { regExp: "page-(.*)\\.js", content: ... });
132 | // => script-home.js
133 |
134 | // loaderContext.resourcePath = "/absolute/path/to/app/js/javascript.js"
135 | // loaderContext.resourceQuery = "?foo=bar"
136 | loaderUtils.interpolateName(
137 | loaderContext,
138 | (resourcePath, resourceQuery) => {
139 | // resourcePath - `/app/js/javascript.js`
140 | // resourceQuery - `?foo=bar`
141 |
142 | return "js/[hash].script.[ext]";
143 | },
144 | { content: ... }
145 | );
146 | // => js/9473fdd0d880a43c21b7778d34872157.script.js
147 | ```
148 |
149 | ### `getHashDigest`
150 |
151 | ```javascript
152 | const digestString = loaderUtils.getHashDigest(
153 | buffer,
154 | hashType,
155 | digestType,
156 | maxLength
157 | );
158 | ```
159 |
160 | - `buffer` the content that should be hashed
161 | - `hashType` one of `xxhash64`, `sha1`, `md4`, `md5`, `sha256`, `sha512` or any other node.js supported hash type
162 | - `digestType` one of `hex`, `base26`, `base32`, `base36`, `base49`, `base52`, `base58`, `base62`, `base64`, `base64safe`
163 | - `maxLength` the maximum length in chars
164 |
165 | ## License
166 |
167 | MIT (http://www.opensource.org/licenses/mit-license.php)
168 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting Security Issues
4 |
5 | If you discover a security issue in webpack/loader-utils, please report it by following the instructions
6 | here: https://github.com/webpack/webpack/blob/main/SECURITY.md
7 |
--------------------------------------------------------------------------------
/lib/getHashDigest.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const baseEncodeTables = {
4 | 26: "abcdefghijklmnopqrstuvwxyz",
5 | 32: "123456789abcdefghjkmnpqrstuvwxyz", // no 0lio
6 | 36: "0123456789abcdefghijklmnopqrstuvwxyz",
7 | 49: "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", // no lIO
8 | 52: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
9 | 58: "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", // no 0lIO
10 | 62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
11 | 64: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_",
12 | };
13 |
14 | /**
15 | * @param {Uint32Array} uint32Array Treated as a long base-0x100000000 number, little endian
16 | * @param {number} divisor The divisor
17 | * @return {number} Modulo (remainder) of the division
18 | */
19 | function divmod32(uint32Array, divisor) {
20 | let carry = 0;
21 | for (let i = uint32Array.length - 1; i >= 0; i--) {
22 | const value = carry * 0x100000000 + uint32Array[i];
23 | carry = value % divisor;
24 | uint32Array[i] = Math.floor(value / divisor);
25 | }
26 | return carry;
27 | }
28 |
29 | function encodeBufferToBase(buffer, base, length) {
30 | const encodeTable = baseEncodeTables[base];
31 |
32 | if (!encodeTable) {
33 | throw new Error("Unknown encoding base" + base);
34 | }
35 |
36 | // Input bits are only enough to generate this many characters
37 | const limit = Math.ceil((buffer.length * 8) / Math.log2(base));
38 | length = Math.min(length, limit);
39 |
40 | // Most of the crypto digests (if not all) has length a multiple of 4 bytes.
41 | // Fewer numbers in the array means faster math.
42 | const uint32Array = new Uint32Array(Math.ceil(buffer.length / 4));
43 |
44 | // Make sure the input buffer data is copied and is not mutated by reference.
45 | // divmod32() would corrupt the BulkUpdateDecorator cache otherwise.
46 | buffer.copy(Buffer.from(uint32Array.buffer));
47 |
48 | let output = "";
49 |
50 | for (let i = 0; i < length; i++) {
51 | output = encodeTable[divmod32(uint32Array, base)] + output;
52 | }
53 |
54 | return output;
55 | }
56 |
57 | let crypto = undefined;
58 | let createXXHash64 = undefined;
59 | let createMd4 = undefined;
60 | let BatchedHash = undefined;
61 | let BulkUpdateDecorator = undefined;
62 |
63 | function getHashDigest(buffer, algorithm, digestType, maxLength) {
64 | algorithm = algorithm || "xxhash64";
65 | maxLength = maxLength || 9999;
66 |
67 | let hash;
68 |
69 | if (algorithm === "xxhash64") {
70 | if (createXXHash64 === undefined) {
71 | createXXHash64 = require("./hash/xxhash64");
72 |
73 | if (BatchedHash === undefined) {
74 | BatchedHash = require("./hash/BatchedHash");
75 | }
76 | }
77 |
78 | hash = new BatchedHash(createXXHash64());
79 | } else if (algorithm === "md4") {
80 | if (createMd4 === undefined) {
81 | createMd4 = require("./hash/md4");
82 |
83 | if (BatchedHash === undefined) {
84 | BatchedHash = require("./hash/BatchedHash");
85 | }
86 | }
87 |
88 | hash = new BatchedHash(createMd4());
89 | } else if (algorithm === "native-md4") {
90 | if (typeof crypto === "undefined") {
91 | crypto = require("crypto");
92 |
93 | if (BulkUpdateDecorator === undefined) {
94 | BulkUpdateDecorator = require("./hash/BulkUpdateDecorator");
95 | }
96 | }
97 |
98 | hash = new BulkUpdateDecorator(() => crypto.createHash("md4"), "md4");
99 | } else {
100 | if (typeof crypto === "undefined") {
101 | crypto = require("crypto");
102 |
103 | if (BulkUpdateDecorator === undefined) {
104 | BulkUpdateDecorator = require("./hash/BulkUpdateDecorator");
105 | }
106 | }
107 |
108 | hash = new BulkUpdateDecorator(
109 | () => crypto.createHash(algorithm),
110 | algorithm
111 | );
112 | }
113 |
114 | hash.update(buffer);
115 |
116 | if (
117 | digestType === "base26" ||
118 | digestType === "base32" ||
119 | digestType === "base36" ||
120 | digestType === "base49" ||
121 | digestType === "base52" ||
122 | digestType === "base58" ||
123 | digestType === "base62" ||
124 | digestType === "base64safe"
125 | ) {
126 | return encodeBufferToBase(
127 | hash.digest(),
128 | digestType === "base64safe" ? 64 : digestType.substr(4),
129 | maxLength
130 | );
131 | }
132 |
133 | return hash.digest(digestType || "hex").substr(0, maxLength);
134 | }
135 |
136 | module.exports = getHashDigest;
137 |
--------------------------------------------------------------------------------
/lib/hash/BatchedHash.js:
--------------------------------------------------------------------------------
1 | const MAX_SHORT_STRING = require("./wasm-hash").MAX_SHORT_STRING;
2 |
3 | class BatchedHash {
4 | constructor(hash) {
5 | this.string = undefined;
6 | this.encoding = undefined;
7 | this.hash = hash;
8 | }
9 |
10 | /**
11 | * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding}
12 | * @param {string|Buffer} data data
13 | * @param {string=} inputEncoding data encoding
14 | * @returns {this} updated hash
15 | */
16 | update(data, inputEncoding) {
17 | if (this.string !== undefined) {
18 | if (
19 | typeof data === "string" &&
20 | inputEncoding === this.encoding &&
21 | this.string.length + data.length < MAX_SHORT_STRING
22 | ) {
23 | this.string += data;
24 |
25 | return this;
26 | }
27 |
28 | this.hash.update(this.string, this.encoding);
29 | this.string = undefined;
30 | }
31 |
32 | if (typeof data === "string") {
33 | if (
34 | data.length < MAX_SHORT_STRING &&
35 | // base64 encoding is not valid since it may contain padding chars
36 | (!inputEncoding || !inputEncoding.startsWith("ba"))
37 | ) {
38 | this.string = data;
39 | this.encoding = inputEncoding;
40 | } else {
41 | this.hash.update(data, inputEncoding);
42 | }
43 | } else {
44 | this.hash.update(data);
45 | }
46 |
47 | return this;
48 | }
49 |
50 | /**
51 | * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding}
52 | * @param {string=} encoding encoding of the return value
53 | * @returns {string|Buffer} digest
54 | */
55 | digest(encoding) {
56 | if (this.string !== undefined) {
57 | this.hash.update(this.string, this.encoding);
58 | }
59 |
60 | return this.hash.digest(encoding);
61 | }
62 | }
63 |
64 | module.exports = BatchedHash;
65 |
--------------------------------------------------------------------------------
/lib/hash/BulkUpdateDecorator.js:
--------------------------------------------------------------------------------
1 | const BULK_SIZE = 2000;
2 |
3 | // We are using an object instead of a Map as this will stay static during the runtime
4 | // so access to it can be optimized by v8
5 | const digestCaches = {};
6 |
7 | class BulkUpdateDecorator {
8 | /**
9 | * @param {Hash | function(): Hash} hashOrFactory function to create a hash
10 | * @param {string=} hashKey key for caching
11 | */
12 | constructor(hashOrFactory, hashKey) {
13 | this.hashKey = hashKey;
14 |
15 | if (typeof hashOrFactory === "function") {
16 | this.hashFactory = hashOrFactory;
17 | this.hash = undefined;
18 | } else {
19 | this.hashFactory = undefined;
20 | this.hash = hashOrFactory;
21 | }
22 |
23 | this.buffer = "";
24 | }
25 |
26 | /**
27 | * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding}
28 | * @param {string|Buffer} data data
29 | * @param {string=} inputEncoding data encoding
30 | * @returns {this} updated hash
31 | */
32 | update(data, inputEncoding) {
33 | if (
34 | inputEncoding !== undefined ||
35 | typeof data !== "string" ||
36 | data.length > BULK_SIZE
37 | ) {
38 | if (this.hash === undefined) {
39 | this.hash = this.hashFactory();
40 | }
41 |
42 | if (this.buffer.length > 0) {
43 | this.hash.update(this.buffer);
44 | this.buffer = "";
45 | }
46 |
47 | this.hash.update(data, inputEncoding);
48 | } else {
49 | this.buffer += data;
50 |
51 | if (this.buffer.length > BULK_SIZE) {
52 | if (this.hash === undefined) {
53 | this.hash = this.hashFactory();
54 | }
55 |
56 | this.hash.update(this.buffer);
57 | this.buffer = "";
58 | }
59 | }
60 |
61 | return this;
62 | }
63 |
64 | /**
65 | * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding}
66 | * @param {string=} encoding encoding of the return value
67 | * @returns {string|Buffer} digest
68 | */
69 | digest(encoding) {
70 | let digestCache;
71 |
72 | const buffer = this.buffer;
73 |
74 | if (this.hash === undefined) {
75 | // short data for hash, we can use caching
76 | const cacheKey = `${this.hashKey}-${encoding}`;
77 |
78 | digestCache = digestCaches[cacheKey];
79 |
80 | if (digestCache === undefined) {
81 | digestCache = digestCaches[cacheKey] = new Map();
82 | }
83 |
84 | const cacheEntry = digestCache.get(buffer);
85 |
86 | if (cacheEntry !== undefined) {
87 | return cacheEntry;
88 | }
89 |
90 | this.hash = this.hashFactory();
91 | }
92 |
93 | if (buffer.length > 0) {
94 | this.hash.update(buffer);
95 | }
96 |
97 | const digestResult = this.hash.digest(encoding);
98 |
99 | if (digestCache !== undefined) {
100 | digestCache.set(buffer, digestResult);
101 | }
102 |
103 | return digestResult;
104 | }
105 | }
106 |
107 | module.exports = BulkUpdateDecorator;
108 |
--------------------------------------------------------------------------------
/lib/hash/md4.js:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License http://www.opensource.org/licenses/mit-license.php
3 | Author Tobias Koppers @sokra
4 | */
5 |
6 | "use strict";
7 |
8 | const create = require("./wasm-hash");
9 |
10 | //#region wasm code: md4 (../../../assembly/hash/md4.asm.ts) --initialMemory 1
11 | const md4 = new WebAssembly.Module(
12 | Buffer.from(
13 | // 2150 bytes
14 | "AGFzbQEAAAABCAJgAX8AYAAAAwUEAQAAAAUDAQABBhoFfwFBAAt/AUEAC38BQQALfwFBAAt/AUEACwciBARpbml0AAAGdXBkYXRlAAIFZmluYWwAAwZtZW1vcnkCAAqFEAQmAEGBxpS6BiQBQYnXtv5+JAJB/rnrxXkkA0H2qMmBASQEQQAkAAvMCgEYfyMBIQojAiEGIwMhByMEIQgDQCAAIAVLBEAgBSgCCCINIAcgBiAFKAIEIgsgCCAHIAUoAgAiDCAKIAggBiAHIAhzcXNqakEDdyIDIAYgB3Nxc2pqQQd3IgEgAyAGc3FzampBC3chAiAFKAIUIg8gASACIAUoAhAiCSADIAEgBSgCDCIOIAYgAyACIAEgA3Nxc2pqQRN3IgQgASACc3FzampBA3ciAyACIARzcXNqakEHdyEBIAUoAiAiEiADIAEgBSgCHCIRIAQgAyAFKAIYIhAgAiAEIAEgAyAEc3FzampBC3ciAiABIANzcXNqakETdyIEIAEgAnNxc2pqQQN3IQMgBSgCLCIVIAQgAyAFKAIoIhQgAiAEIAUoAiQiEyABIAIgAyACIARzcXNqakEHdyIBIAMgBHNxc2pqQQt3IgIgASADc3FzampBE3chBCAPIBAgCSAVIBQgEyAFKAI4IhYgAiAEIAUoAjQiFyABIAIgBSgCMCIYIAMgASAEIAEgAnNxc2pqQQN3IgEgAiAEc3FzampBB3ciAiABIARzcXNqakELdyIDIAkgAiAMIAEgBSgCPCIJIAQgASADIAEgAnNxc2pqQRN3IgEgAiADcnEgAiADcXJqakGZ84nUBWpBA3ciAiABIANycSABIANxcmpqQZnzidQFakEFdyIEIAEgAnJxIAEgAnFyaiASakGZ84nUBWpBCXciAyAPIAQgCyACIBggASADIAIgBHJxIAIgBHFyampBmfOJ1AVqQQ13IgEgAyAEcnEgAyAEcXJqakGZ84nUBWpBA3ciAiABIANycSABIANxcmpqQZnzidQFakEFdyIEIAEgAnJxIAEgAnFyampBmfOJ1AVqQQl3IgMgECAEIAIgFyABIAMgAiAEcnEgAiAEcXJqakGZ84nUBWpBDXciASADIARycSADIARxcmogDWpBmfOJ1AVqQQN3IgIgASADcnEgASADcXJqakGZ84nUBWpBBXciBCABIAJycSABIAJxcmpqQZnzidQFakEJdyIDIBEgBCAOIAIgFiABIAMgAiAEcnEgAiAEcXJqakGZ84nUBWpBDXciASADIARycSADIARxcmpqQZnzidQFakEDdyICIAEgA3JxIAEgA3FyampBmfOJ1AVqQQV3IgQgASACcnEgASACcXJqakGZ84nUBWpBCXciAyAMIAIgAyAJIAEgAyACIARycSACIARxcmpqQZnzidQFakENdyIBcyAEc2pqQaHX5/YGakEDdyICIAQgASACcyADc2ogEmpBodfn9gZqQQl3IgRzIAFzampBodfn9gZqQQt3IgMgAiADIBggASADIARzIAJzampBodfn9gZqQQ93IgFzIARzaiANakGh1+f2BmpBA3ciAiAUIAQgASACcyADc2pqQaHX5/YGakEJdyIEcyABc2pqQaHX5/YGakELdyIDIAsgAiADIBYgASADIARzIAJzampBodfn9gZqQQ93IgFzIARzampBodfn9gZqQQN3IgIgEyAEIAEgAnMgA3NqakGh1+f2BmpBCXciBHMgAXNqakGh1+f2BmpBC3chAyAKIA4gAiADIBcgASADIARzIAJzampBodfn9gZqQQ93IgFzIARzampBodfn9gZqQQN3IgJqIQogBiAJIAEgESADIAIgFSAEIAEgAnMgA3NqakGh1+f2BmpBCXciBHMgAXNqakGh1+f2BmpBC3ciAyAEcyACc2pqQaHX5/YGakEPd2ohBiADIAdqIQcgBCAIaiEIIAVBQGshBQwBCwsgCiQBIAYkAiAHJAMgCCQECw0AIAAQASMAIABqJAAL/wQCA38BfiMAIABqrUIDhiEEIABByABqQUBxIgJBCGshAyAAIgFBAWohACABQYABOgAAA0AgACACSUEAIABBB3EbBEAgAEEAOgAAIABBAWohAAwBCwsDQCAAIAJJBEAgAEIANwMAIABBCGohAAwBCwsgAyAENwMAIAIQAUEAIwGtIgRC//8DgyAEQoCA/P8Pg0IQhoQiBEL/gYCA8B+DIARCgP6DgIDgP4NCCIaEIgRCj4C8gPCBwAeDQgiGIARC8IHAh4CegPgAg0IEiIQiBEKGjJiw4MCBgwZ8QgSIQoGChIiQoMCAAYNCJ34gBEKw4MCBg4aMmDCEfDcDAEEIIwKtIgRC//8DgyAEQoCA/P8Pg0IQhoQiBEL/gYCA8B+DIARCgP6DgIDgP4NCCIaEIgRCj4C8gPCBwAeDQgiGIARC8IHAh4CegPgAg0IEiIQiBEKGjJiw4MCBgwZ8QgSIQoGChIiQoMCAAYNCJ34gBEKw4MCBg4aMmDCEfDcDAEEQIwOtIgRC//8DgyAEQoCA/P8Pg0IQhoQiBEL/gYCA8B+DIARCgP6DgIDgP4NCCIaEIgRCj4C8gPCBwAeDQgiGIARC8IHAh4CegPgAg0IEiIQiBEKGjJiw4MCBgwZ8QgSIQoGChIiQoMCAAYNCJ34gBEKw4MCBg4aMmDCEfDcDAEEYIwStIgRC//8DgyAEQoCA/P8Pg0IQhoQiBEL/gYCA8B+DIARCgP6DgIDgP4NCCIaEIgRCj4C8gPCBwAeDQgiGIARC8IHAh4CegPgAg0IEiIQiBEKGjJiw4MCBgwZ8QgSIQoGChIiQoMCAAYNCJ34gBEKw4MCBg4aMmDCEfDcDAAs=",
15 | "base64"
16 | )
17 | );
18 | //#endregion
19 |
20 | module.exports = create.bind(null, md4, [], 64, 32);
21 |
--------------------------------------------------------------------------------
/lib/hash/wasm-hash.js:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License http://www.opensource.org/licenses/mit-license.php
3 | Author Tobias Koppers @sokra
4 | */
5 |
6 | "use strict";
7 |
8 | // 65536 is the size of a wasm memory page
9 | // 64 is the maximum chunk size for every possible wasm hash implementation
10 | // 4 is the maximum number of bytes per char for string encoding (max is utf-8)
11 | // ~3 makes sure that it's always a block of 4 chars, so avoid partially encoded bytes for base64
12 | const MAX_SHORT_STRING = Math.floor((65536 - 64) / 4) & ~3;
13 |
14 | class WasmHash {
15 | /**
16 | * @param {WebAssembly.Instance} instance wasm instance
17 | * @param {WebAssembly.Instance[]} instancesPool pool of instances
18 | * @param {number} chunkSize size of data chunks passed to wasm
19 | * @param {number} digestSize size of digest returned by wasm
20 | */
21 | constructor(instance, instancesPool, chunkSize, digestSize) {
22 | const exports = /** @type {any} */ (instance.exports);
23 |
24 | exports.init();
25 |
26 | this.exports = exports;
27 | this.mem = Buffer.from(exports.memory.buffer, 0, 65536);
28 | this.buffered = 0;
29 | this.instancesPool = instancesPool;
30 | this.chunkSize = chunkSize;
31 | this.digestSize = digestSize;
32 | }
33 |
34 | reset() {
35 | this.buffered = 0;
36 | this.exports.init();
37 | }
38 |
39 | /**
40 | * @param {Buffer | string} data data
41 | * @param {BufferEncoding=} encoding encoding
42 | * @returns {this} itself
43 | */
44 | update(data, encoding) {
45 | if (typeof data === "string") {
46 | while (data.length > MAX_SHORT_STRING) {
47 | this._updateWithShortString(data.slice(0, MAX_SHORT_STRING), encoding);
48 | data = data.slice(MAX_SHORT_STRING);
49 | }
50 |
51 | this._updateWithShortString(data, encoding);
52 |
53 | return this;
54 | }
55 |
56 | this._updateWithBuffer(data);
57 |
58 | return this;
59 | }
60 |
61 | /**
62 | * @param {string} data data
63 | * @param {BufferEncoding=} encoding encoding
64 | * @returns {void}
65 | */
66 | _updateWithShortString(data, encoding) {
67 | const { exports, buffered, mem, chunkSize } = this;
68 |
69 | let endPos;
70 |
71 | if (data.length < 70) {
72 | if (!encoding || encoding === "utf-8" || encoding === "utf8") {
73 | endPos = buffered;
74 | for (let i = 0; i < data.length; i++) {
75 | const cc = data.charCodeAt(i);
76 |
77 | if (cc < 0x80) {
78 | mem[endPos++] = cc;
79 | } else if (cc < 0x800) {
80 | mem[endPos] = (cc >> 6) | 0xc0;
81 | mem[endPos + 1] = (cc & 0x3f) | 0x80;
82 | endPos += 2;
83 | } else {
84 | // bail-out for weird chars
85 | endPos += mem.write(data.slice(i), endPos, encoding);
86 | break;
87 | }
88 | }
89 | } else if (encoding === "latin1") {
90 | endPos = buffered;
91 |
92 | for (let i = 0; i < data.length; i++) {
93 | const cc = data.charCodeAt(i);
94 |
95 | mem[endPos++] = cc;
96 | }
97 | } else {
98 | endPos = buffered + mem.write(data, buffered, encoding);
99 | }
100 | } else {
101 | endPos = buffered + mem.write(data, buffered, encoding);
102 | }
103 |
104 | if (endPos < chunkSize) {
105 | this.buffered = endPos;
106 | } else {
107 | const l = endPos & ~(this.chunkSize - 1);
108 |
109 | exports.update(l);
110 |
111 | const newBuffered = endPos - l;
112 |
113 | this.buffered = newBuffered;
114 |
115 | if (newBuffered > 0) {
116 | mem.copyWithin(0, l, endPos);
117 | }
118 | }
119 | }
120 |
121 | /**
122 | * @param {Buffer} data data
123 | * @returns {void}
124 | */
125 | _updateWithBuffer(data) {
126 | const { exports, buffered, mem } = this;
127 | const length = data.length;
128 |
129 | if (buffered + length < this.chunkSize) {
130 | data.copy(mem, buffered, 0, length);
131 |
132 | this.buffered += length;
133 | } else {
134 | const l = (buffered + length) & ~(this.chunkSize - 1);
135 |
136 | if (l > 65536) {
137 | let i = 65536 - buffered;
138 |
139 | data.copy(mem, buffered, 0, i);
140 | exports.update(65536);
141 |
142 | const stop = l - buffered - 65536;
143 |
144 | while (i < stop) {
145 | data.copy(mem, 0, i, i + 65536);
146 | exports.update(65536);
147 | i += 65536;
148 | }
149 |
150 | data.copy(mem, 0, i, l - buffered);
151 |
152 | exports.update(l - buffered - i);
153 | } else {
154 | data.copy(mem, buffered, 0, l - buffered);
155 |
156 | exports.update(l);
157 | }
158 |
159 | const newBuffered = length + buffered - l;
160 |
161 | this.buffered = newBuffered;
162 |
163 | if (newBuffered > 0) {
164 | data.copy(mem, 0, length - newBuffered, length);
165 | }
166 | }
167 | }
168 |
169 | digest(type) {
170 | const { exports, buffered, mem, digestSize } = this;
171 |
172 | exports.final(buffered);
173 |
174 | this.instancesPool.push(this);
175 |
176 | const hex = mem.toString("latin1", 0, digestSize);
177 |
178 | if (type === "hex") {
179 | return hex;
180 | }
181 |
182 | if (type === "binary" || !type) {
183 | return Buffer.from(hex, "hex");
184 | }
185 |
186 | return Buffer.from(hex, "hex").toString(type);
187 | }
188 | }
189 |
190 | const create = (wasmModule, instancesPool, chunkSize, digestSize) => {
191 | if (instancesPool.length > 0) {
192 | const old = instancesPool.pop();
193 |
194 | old.reset();
195 |
196 | return old;
197 | } else {
198 | return new WasmHash(
199 | new WebAssembly.Instance(wasmModule),
200 | instancesPool,
201 | chunkSize,
202 | digestSize
203 | );
204 | }
205 | };
206 |
207 | module.exports = create;
208 | module.exports.MAX_SHORT_STRING = MAX_SHORT_STRING;
209 |
--------------------------------------------------------------------------------
/lib/hash/xxhash64.js:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License http://www.opensource.org/licenses/mit-license.php
3 | Author Tobias Koppers @sokra
4 | */
5 |
6 | "use strict";
7 |
8 | const create = require("./wasm-hash");
9 |
10 | //#region wasm code: xxhash64 (../../../assembly/hash/xxhash64.asm.ts) --initialMemory 1
11 | const xxhash64 = new WebAssembly.Module(
12 | Buffer.from(
13 | // 1173 bytes
14 | "AGFzbQEAAAABCAJgAX8AYAAAAwQDAQAABQMBAAEGGgV+AUIAC34BQgALfgFCAAt+AUIAC34BQgALByIEBGluaXQAAAZ1cGRhdGUAAQVmaW5hbAACBm1lbW9yeQIACrUIAzAAQtbrgu7q/Yn14AAkAELP1tO+0ser2UIkAUIAJAJC+erQ0OfJoeThACQDQgAkBAvUAQIBfwR+IABFBEAPCyMEIACtfCQEIwAhAiMBIQMjAiEEIwMhBQNAIAIgASkDAELP1tO+0ser2UJ+fEIfiUKHla+vmLbem55/fiECIAMgASkDCELP1tO+0ser2UJ+fEIfiUKHla+vmLbem55/fiEDIAQgASkDEELP1tO+0ser2UJ+fEIfiUKHla+vmLbem55/fiEEIAUgASkDGELP1tO+0ser2UJ+fEIfiUKHla+vmLbem55/fiEFIAAgAUEgaiIBSw0ACyACJAAgAyQBIAQkAiAFJAMLqwYCAX8EfiMEQgBSBH4jACICQgGJIwEiA0IHiXwjAiIEQgyJfCMDIgVCEol8IAJCz9bTvtLHq9lCfkIfiUKHla+vmLbem55/foVCh5Wvr5i23puef35CnaO16oOxjYr6AH0gA0LP1tO+0ser2UJ+Qh+JQoeVr6+Ytt6bnn9+hUKHla+vmLbem55/fkKdo7Xqg7GNivoAfSAEQs/W077Sx6vZQn5CH4lCh5Wvr5i23puef36FQoeVr6+Ytt6bnn9+Qp2jteqDsY2K+gB9IAVCz9bTvtLHq9lCfkIfiUKHla+vmLbem55/foVCh5Wvr5i23puef35CnaO16oOxjYr6AH0FQsXP2bLx5brqJwsjBCAArXx8IQIDQCABQQhqIABNBEAgAiABKQMAQs/W077Sx6vZQn5CH4lCh5Wvr5i23puef36FQhuJQoeVr6+Ytt6bnn9+Qp2jteqDsY2K+gB9IQIgAUEIaiEBDAELCyABQQRqIABNBEACfyACIAE1AgBCh5Wvr5i23puef36FQheJQs/W077Sx6vZQn5C+fPd8Zn2masWfCECIAFBBGoLIQELA0AgACABRwRAIAIgATEAAELFz9my8eW66id+hUILiUKHla+vmLbem55/fiECIAFBAWohAQwBCwtBACACIAJCIYiFQs/W077Sx6vZQn4iAiACQh2IhUL5893xmfaZqxZ+IgIgAkIgiIUiAkIgiCIDQv//A4NCIIYgA0KAgPz/D4NCEIiEIgNC/4GAgPAfg0IQhiADQoD+g4CA4D+DQgiIhCIDQo+AvIDwgcAHg0IIhiADQvCBwIeAnoD4AINCBIiEIgNChoyYsODAgYMGfEIEiEKBgoSIkKDAgAGDQid+IANCsODAgYOGjJgwhHw3AwBBCCACQv////8PgyICQv//A4NCIIYgAkKAgPz/D4NCEIiEIgJC/4GAgPAfg0IQhiACQoD+g4CA4D+DQgiIhCICQo+AvIDwgcAHg0IIhiACQvCBwIeAnoD4AINCBIiEIgJChoyYsODAgYMGfEIEiEKBgoSIkKDAgAGDQid+IAJCsODAgYOGjJgwhHw3AwAL",
15 | "base64"
16 | )
17 | );
18 | //#endregion
19 |
20 | module.exports = create.bind(null, xxhash64, [], 32, 16);
21 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const isUrlRequest = require("./isUrlRequest");
4 | const urlToRequest = require("./urlToRequest");
5 | const getHashDigest = require("./getHashDigest");
6 | const interpolateName = require("./interpolateName");
7 |
8 | exports.urlToRequest = urlToRequest;
9 | exports.getHashDigest = getHashDigest;
10 | exports.interpolateName = interpolateName;
11 | exports.isUrlRequest = isUrlRequest;
12 |
--------------------------------------------------------------------------------
/lib/interpolateName.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const path = require("path");
4 | const getHashDigest = require("./getHashDigest");
5 |
6 | function interpolateName(loaderContext, name, options = {}) {
7 | let filename;
8 |
9 | const hasQuery =
10 | loaderContext.resourceQuery && loaderContext.resourceQuery.length > 1;
11 |
12 | if (typeof name === "function") {
13 | filename = name(
14 | loaderContext.resourcePath,
15 | hasQuery ? loaderContext.resourceQuery : undefined
16 | );
17 | } else {
18 | filename = name || "[hash].[ext]";
19 | }
20 |
21 | const context = options.context;
22 | const content = options.content;
23 | const regExp = options.regExp;
24 |
25 | let ext = "bin";
26 | let basename = "file";
27 | let directory = "";
28 | let folder = "";
29 | let query = "";
30 |
31 | if (loaderContext.resourcePath) {
32 | const parsed = path.parse(loaderContext.resourcePath);
33 | let resourcePath = loaderContext.resourcePath;
34 |
35 | if (parsed.ext) {
36 | ext = parsed.ext.substr(1);
37 | }
38 |
39 | if (parsed.dir) {
40 | basename = parsed.name;
41 | resourcePath = parsed.dir + path.sep;
42 | }
43 |
44 | if (typeof context !== "undefined") {
45 | directory = path
46 | .relative(context, resourcePath + "_")
47 | .replace(/\\/g, "/")
48 | .replace(/\.\.(\/)?/g, "_$1");
49 | directory = directory.substr(0, directory.length - 1);
50 | } else {
51 | directory = resourcePath.replace(/\\/g, "/").replace(/\.\.(\/)?/g, "_$1");
52 | }
53 |
54 | if (directory.length <= 1) {
55 | directory = "";
56 | } else {
57 | // directory.length > 1
58 | folder = path.basename(directory);
59 | }
60 | }
61 |
62 | if (loaderContext.resourceQuery && loaderContext.resourceQuery.length > 1) {
63 | query = loaderContext.resourceQuery;
64 |
65 | const hashIdx = query.indexOf("#");
66 |
67 | if (hashIdx >= 0) {
68 | query = query.substr(0, hashIdx);
69 | }
70 | }
71 |
72 | let url = filename;
73 |
74 | if (content) {
75 | // Match hash template
76 | url = url
77 | // `hash` and `contenthash` are same in `loader-utils` context
78 | // let's keep `hash` for backward compatibility
79 | .replace(
80 | /\[(?:([^[:\]]+):)?(?:hash|contenthash)(?::([a-z]+\d*(?:safe)?))?(?::(\d+))?\]/gi,
81 | (all, hashType, digestType, maxLength) =>
82 | getHashDigest(content, hashType, digestType, parseInt(maxLength, 10))
83 | );
84 | }
85 |
86 | url = url
87 | .replace(/\[ext\]/gi, () => ext)
88 | .replace(/\[name\]/gi, () => basename)
89 | .replace(/\[path\]/gi, () => directory)
90 | .replace(/\[folder\]/gi, () => folder)
91 | .replace(/\[query\]/gi, () => query);
92 |
93 | if (regExp && loaderContext.resourcePath) {
94 | const match = loaderContext.resourcePath.match(new RegExp(regExp));
95 |
96 | match &&
97 | match.forEach((matched, i) => {
98 | url = url.replace(new RegExp("\\[" + i + "\\]", "ig"), matched);
99 | });
100 | }
101 |
102 | if (
103 | typeof loaderContext.options === "object" &&
104 | typeof loaderContext.options.customInterpolateName === "function"
105 | ) {
106 | url = loaderContext.options.customInterpolateName.call(
107 | loaderContext,
108 | url,
109 | name,
110 | options
111 | );
112 | }
113 |
114 | return url;
115 | }
116 |
117 | module.exports = interpolateName;
118 |
--------------------------------------------------------------------------------
/lib/isUrlRequest.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const path = require("path");
4 |
5 | function isUrlRequest(url) {
6 | // An URL is not an request if
7 |
8 | // 1. Allow `data URI`
9 | if (/^data:/i.test(url)) {
10 | return true;
11 | }
12 |
13 | // 2. It's an absolute url and it is not `windows` path like `C:\dir\file`
14 | if (/^[a-z][a-z0-9+.-]*:/i.test(url) && !path.win32.isAbsolute(url)) {
15 | return false;
16 | }
17 |
18 | // 3. It's a protocol-relative
19 | if (/^\/\//.test(url)) {
20 | return false;
21 | }
22 |
23 | // 4. It's some kind of url for a template
24 | if (/^#/.test(url)) {
25 | return false;
26 | }
27 |
28 | return true;
29 | }
30 |
31 | module.exports = isUrlRequest;
32 |
--------------------------------------------------------------------------------
/lib/urlToRequest.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // we can't use path.win32.isAbsolute because it also matches paths starting with a forward slash
4 | const matchNativeWin32Path = /^[A-Z]:[/\\]|^\\\\/i;
5 |
6 | function urlToRequest(url, root) {
7 | // Do not rewrite an empty url
8 | if (url === "") {
9 | return "";
10 | }
11 |
12 | const moduleRequestRegex = /^[^?]*~/;
13 | let request;
14 |
15 | if (matchNativeWin32Path.test(url)) {
16 | // absolute windows path, keep it
17 | request = url;
18 | } else if (root !== undefined && root !== false && /^\//.test(url)) {
19 | // if root is set and the url is root-relative
20 | switch (typeof root) {
21 | // 1. root is a string: root is prefixed to the url
22 | case "string":
23 | // special case: `~` roots convert to module request
24 | if (moduleRequestRegex.test(root)) {
25 | request = root.replace(/([^~/])$/, "$1/") + url.slice(1);
26 | } else {
27 | request = root + url;
28 | }
29 | break;
30 | // 2. root is `true`: absolute paths are allowed
31 | // *nix only, windows-style absolute paths are always allowed as they doesn't start with a `/`
32 | case "boolean":
33 | request = url;
34 | break;
35 | default:
36 | throw new Error(
37 | "Unexpected parameters to loader-utils 'urlToRequest': url = " +
38 | url +
39 | ", root = " +
40 | root +
41 | "."
42 | );
43 | }
44 | } else if (/^\.\.?\//.test(url)) {
45 | // A relative url stays
46 | request = url;
47 | } else {
48 | // every other url is threaded like a relative url
49 | request = "./" + url;
50 | }
51 |
52 | // A `~` makes the url an module
53 | if (moduleRequestRegex.test(request)) {
54 | request = request.replace(moduleRequestRegex, "");
55 | }
56 |
57 | return request;
58 | }
59 |
60 | module.exports = urlToRequest;
61 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "loader-utils",
3 | "version": "3.3.1",
4 | "author": "Tobias Koppers @sokra",
5 | "description": "utils for webpack loaders",
6 | "dependencies": {},
7 | "scripts": {
8 | "lint": "prettier --list-different . && eslint .",
9 | "pretest": "yarn lint",
10 | "test": "jest",
11 | "test:only": "jest --coverage",
12 | "test:ci": "yarn test:only",
13 | "release": "yarn test && standard-version"
14 | },
15 | "license": "MIT",
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/webpack/loader-utils.git"
19 | },
20 | "engines": {
21 | "node": ">= 12.13.0"
22 | },
23 | "devDependencies": {
24 | "coveralls": "^3.1.1",
25 | "eslint": "^8.0.1",
26 | "eslint-plugin-node": "^11.1.0",
27 | "jest": "^27.3.1",
28 | "prettier": "^2.4.1",
29 | "standard-version": "^9.3.2"
30 | },
31 | "main": "lib/index.js",
32 | "files": [
33 | "lib"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/test/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "jest": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/test/getHashDigest.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const loaderUtils = require("../");
4 |
5 | describe("getHashDigest()", () => {
6 | [
7 | ["test string", "xxhash64", "hex", undefined, "e9e2c351e3c6b198"],
8 | ["test string", "xxhash64", "base64", undefined, "6eLDUePGsZg="],
9 | ["test string", "xxhash64", "base52", undefined, "byfYGDmnmyUr"],
10 | ["abc\\0♥", "xxhash64", "hex", undefined, "4b9a34297dc03d20"],
11 | ["abc\\0💩", "xxhash64", "hex", undefined, "86733ec125b93904"],
12 | ["abc\\0💩", "xxhash64", "base64", undefined, "hnM+wSW5OQQ="],
13 | ["abc\\0♥", "xxhash64", "base64", undefined, "S5o0KX3APSA="],
14 | ["abc\\0💩", "xxhash64", "base52", undefined, "acfByjQcJZIU"],
15 | ["abc\\0♥", "xxhash64", "base52", undefined, "aqdLyAQjLlod"],
16 |
17 | ["test string", "md4", "hex", 4, "2e06"],
18 | ["test string", "md4", "base64", undefined, "Lgbt1PFiMmjFpRcw2KCyrw=="],
19 | ["test string", "md4", "base52", undefined, "egWqIKxsDHdZTteemJqXfuo"],
20 | ["abc\\0♥", "md4", "hex", undefined, "46b9627fecf49b80eaf01c01d86ae9fd"],
21 | ["abc\\0💩", "md4", "hex", undefined, "45aa5b332f8e562aaf0106ad6fc1d78f"],
22 | ["abc\\0💩", "md4", "base64", undefined, "RapbMy+OViqvAQatb8HXjw=="],
23 | ["abc\\0♥", "md4", "base64", undefined, "Rrlif+z0m4Dq8BwB2Grp/Q=="],
24 | ["abc\\0♥", "md4", "base64safe", undefined, "3ZWmHo0hPMWE2rZeN_oHB6"],
25 | ["abc\\0💩", "md4", "base52", undefined, "dtXZENFEkYHXGxOkJbevPoD"],
26 | ["abc\\0♥", "md4", "base52", undefined, "fYFFcfXRGsVweukHKlPayHs"],
27 |
28 | ["test string", "md5", "hex", 4, "6f8d"],
29 | [
30 | "test string",
31 | "md5",
32 | "hex",
33 | undefined,
34 | "6f8db599de986fab7a21625b7916589c",
35 | ],
36 | ["test string", "md5", "base52", undefined, "dJnldHSAutqUacjgfBQGLQx"],
37 | ["test string", "md5", "base64", undefined, "b421md6Yb6t6IWJbeRZYnA=="],
38 | ["test string", "md5", "base26", undefined, "bhtsgujtzvmjtgtzlqvubqggbvgx"],
39 | ["test string", "md5", "base26", 6, "ggbvgx"],
40 | ["abc\\0♥", "md5", "hex", undefined, "2e897b64f8050e66aff98d38f7a012c5"],
41 | ["abc\\0💩", "md5", "hex", undefined, "63ad5b3d675c5890e0c01ed339ba0187"],
42 | ["abc\\0💩", "md5", "base64", undefined, "Y61bPWdcWJDgwB7TOboBhw=="],
43 | ["abc\\0♥", "md5", "base64", undefined, "Lol7ZPgFDmav+Y0496ASxQ=="],
44 | ["abc\\0💩", "md5", "base52", undefined, "djhVWGHaUKUxqxEhcTnOfBx"],
45 | ["abc\\0♥", "md5", "base52", undefined, "eHeasSeRyOnorzxUJpayzJc"],
46 |
47 | [
48 | "test string",
49 | "sha512",
50 | "base64",
51 | undefined,
52 | "EObWR69EYkRC84jCwUp4f/ixfmFluD12fsBHdo2MvLcaGjIm58x4Frx5wEJ9lKnaaIxBo5kse/Xk18w+C+XbrA==",
53 | ],
54 | [
55 | "test string",
56 | "sha512",
57 | "hex",
58 | undefined,
59 | "10e6d647af44624442f388c2c14a787ff8b17e6165b83d767ec047768d8cbcb71a1a3226e7cc7816bc79c0427d94a9da688c41a3992c7bf5e4d7cc3e0be5dbac",
60 | ],
61 | ].forEach((test) => {
62 | it(
63 | "should getHashDigest " +
64 | test[0] +
65 | " " +
66 | test[1] +
67 | " " +
68 | test[2] +
69 | " " +
70 | test[3],
71 | () => {
72 | const hashDigest = loaderUtils.getHashDigest(
73 | test[0],
74 | test[1],
75 | test[2],
76 | test[3]
77 | );
78 |
79 | expect(hashDigest).toBe(test[4]);
80 | }
81 | );
82 | });
83 | });
84 |
85 | function testDistribution(digestType, length, tableSize, iterations) {
86 | const lowerBound = Math.round(iterations / 2);
87 | const upperBound = Math.round(iterations * 2);
88 |
89 | const stats = [];
90 | for (let i = tableSize * iterations; i-- > 0; ) {
91 | const generatedString = loaderUtils.getHashDigest(
92 | `Some input #${i}`,
93 | undefined,
94 | digestType,
95 | length
96 | );
97 |
98 | for (let pos = 0; pos < length; pos++) {
99 | const char = generatedString[pos];
100 | stats[pos] = stats[pos] || {};
101 | stats[pos][char] = (stats[pos][char] || 0) + 1;
102 | }
103 | }
104 |
105 | for (let pos = 0; pos < length; pos++) {
106 | const chars = Object.keys(stats[pos]).sort();
107 | test(`distinct chars at position ${pos}`, () => {
108 | expect(chars.length).toBe(tableSize);
109 | });
110 | for (const char of chars) {
111 | test(`occurences of char "${char}" at position ${pos} should be around ${iterations}`, () => {
112 | expect(stats[pos][char]).toBeLessThanOrEqual(upperBound);
113 | expect(stats[pos][char]).toBeGreaterThanOrEqual(lowerBound);
114 | });
115 | }
116 | }
117 | }
118 |
119 | describe("getHashDigest() char distribution", () => {
120 | describe("should be uniform for base62", () => {
121 | testDistribution("base62", 8, 62, 100);
122 | });
123 | describe("should be uniform for base26", () => {
124 | testDistribution("base26", 8, 26, 100);
125 | });
126 | });
127 |
--------------------------------------------------------------------------------
/test/interpolateName.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const loaderUtils = require("../");
4 |
5 | describe("interpolateName()", () => {
6 | function run(tests) {
7 | tests.forEach((test) => {
8 | const args = test[0];
9 | const expected = test[1];
10 | const message = test[2];
11 | it(message, () => {
12 | const result = loaderUtils.interpolateName.apply(loaderUtils, args);
13 | if (typeof expected === "function") {
14 | expected(result);
15 | } else {
16 | expect(result).toBe(expected);
17 | }
18 | });
19 | });
20 | }
21 |
22 | [
23 | [
24 | "/app/js/javascript.js",
25 | "js/[hash].script.[ext]",
26 | "test content",
27 | "js/0e6882304e9adbd5.script.js",
28 | ],
29 | [
30 | "/app/js/javascript.js",
31 | "js/[contenthash].script.[ext]",
32 | "test content",
33 | "js/0e6882304e9adbd5.script.js",
34 | ],
35 | [
36 | "/app/page.html",
37 | "html-[hash:6].html",
38 | "test content",
39 | "html-0e6882.html",
40 | ],
41 | [
42 | "/app/page.html",
43 | "html-[contenthash:6].html",
44 | "test content",
45 | "html-0e6882.html",
46 | ],
47 | ["/app/flash.txt", "[hash]", "test content", "0e6882304e9adbd5"],
48 | ["/app/flash.txt", "[contenthash]", "test content", "0e6882304e9adbd5"],
49 | [
50 | "/app/img/image.png",
51 | "[sha512:hash:base64:7].[ext]",
52 | "test content",
53 | "DL9MrvO.png",
54 | ],
55 | [
56 | "/app/img/image.png",
57 | "[sha512:contenthash:base64:7].[ext]",
58 | "test content",
59 | "DL9MrvO.png",
60 | ],
61 | [
62 | "/app/dir/file.png",
63 | "[path][name].[ext]?[hash]",
64 | "test content",
65 | "/app/dir/file.png?0e6882304e9adbd5",
66 | ],
67 | [
68 | "/app/dir/file.png",
69 | "[path][name].[ext]?[contenthash]",
70 | "test content",
71 | "/app/dir/file.png?0e6882304e9adbd5",
72 | ],
73 | [
74 | "/vendor/test/images/loading.gif",
75 | (path) => path.replace(/\/?vendor\/?/, ""),
76 | "test content",
77 | "test/images/loading.gif",
78 | ],
79 | [
80 | "/pathWith.period/filename.js",
81 | "js/[name].[ext]",
82 | "test content",
83 | "js/filename.js",
84 | ],
85 | [
86 | "/pathWith.period/filenameWithoutExt",
87 | "js/[name].[ext]",
88 | "test content",
89 | "js/filenameWithoutExt.bin",
90 | ],
91 | [
92 | "/lib/components/modal/modal.css",
93 | "[name]__modalTitle___[sha1:hash:hex:4]",
94 | "test content",
95 | "modal__modalTitle___1eeb",
96 | ],
97 | [
98 | "/lib/components/modal/modal.css",
99 | "[name]__modalTitle___[sha1:contenthash:hex:4]",
100 | "test content",
101 | "modal__modalTitle___1eeb",
102 | ],
103 | [
104 | "/lib/components/modal/modal.css",
105 | "[name].[md4:hash:base64:20].[ext]",
106 | "test content",
107 | "modal.ppiZgUkxKA4vUnIZrWrH.css",
108 | ],
109 | [
110 | "/lib/components/modal/modal.css",
111 | "[name].[md5:hash:base64:20].[ext]",
112 | "test content",
113 | "modal.lHP90NiApDwht3eNNIch.css",
114 | ],
115 | [
116 | "/lib/components/modal/modal.css",
117 | "[name].[md5:contenthash:base64:20].[ext]",
118 | "test content",
119 | "modal.lHP90NiApDwht3eNNIch.css",
120 | ],
121 | [
122 | "/lib/components/modal/modal.css",
123 | "[name].[md5:contenthash:base64safe:20].[ext]",
124 | "test content",
125 | "modal.8osQznuT8jOAwdzg_nek.css",
126 | ],
127 | // Should not interpret without `hash` or `contenthash`
128 | [
129 | "/lib/components/modal/modal.css",
130 | "[name].[md5::base64:20].[ext]",
131 | "test content",
132 | "modal.[md5::base64:20].css",
133 | ],
134 | [
135 | "/app/js/javascript.js?foo=bar",
136 | "js/[hash].script.[ext][query]",
137 | "test content",
138 | "js/0e6882304e9adbd5.script.js?foo=bar",
139 | ],
140 | [
141 | "/app/js/javascript.js?foo=bar&bar=baz",
142 | "js/[hash].script.[ext][query]",
143 | "test content",
144 | "js/0e6882304e9adbd5.script.js?foo=bar&bar=baz",
145 | ],
146 | [
147 | "/app/js/javascript.js?foo",
148 | "js/[hash].script.[ext][query]",
149 | "test content",
150 | "js/0e6882304e9adbd5.script.js?foo",
151 | ],
152 | [
153 | "/app/js/javascript.js?",
154 | "js/[hash].script.[ext][query]",
155 | "test content",
156 | "js/0e6882304e9adbd5.script.js",
157 | ],
158 | [
159 | "/app/js/javascript.js?a",
160 | "js/[hash].script.[ext][query]",
161 | "test content",
162 | "js/0e6882304e9adbd5.script.js?a",
163 | ],
164 | [
165 | "/app/js/javascript.js?foo=bar#hash",
166 | "js/[hash].script.[ext][query]",
167 | "test content",
168 | "js/0e6882304e9adbd5.script.js?foo=bar",
169 | ],
170 | [
171 | "/app/js/javascript.js?foo=bar#hash",
172 | (resourcePath, resourceQuery) => {
173 | expect(resourcePath).toBeDefined();
174 | expect(resourceQuery).toBeDefined();
175 |
176 | return "js/[hash].script.[ext][query]";
177 | },
178 | "test content",
179 | "js/0e6882304e9adbd5.script.js?foo=bar",
180 | ],
181 | [
182 | "/app/js/javascript.js?a",
183 | (resourcePath, resourceQuery) => {
184 | expect(resourcePath).toBeDefined();
185 | expect(resourceQuery).toBeDefined();
186 |
187 | return "js/[hash].script.[ext][query]";
188 | },
189 | "test content",
190 | "js/0e6882304e9adbd5.script.js?a",
191 | ],
192 | [
193 | "/app/js/javascript.js",
194 | (resourcePath, resourceQuery) => {
195 | expect(resourcePath).toBeDefined();
196 | expect(resourceQuery).not.toBeDefined();
197 |
198 | return "js/[hash].script.[ext][query]";
199 | },
200 | "test content",
201 | "js/0e6882304e9adbd5.script.js",
202 | ],
203 | [
204 | "/app/js/javascript.js?",
205 | (resourcePath, resourceQuery) => {
206 | expect(resourcePath).toBeDefined();
207 | expect(resourceQuery).not.toBeDefined();
208 |
209 | return "js/[hash].script.[ext][query]";
210 | },
211 | "test content",
212 | "js/0e6882304e9adbd5.script.js",
213 | ],
214 | ].forEach((test) => {
215 | it("should interpolate " + test[0] + " " + test[1], () => {
216 | let resourcePath = "";
217 | let resourceQuery = "";
218 |
219 | const queryIdx = test[0].indexOf("?");
220 |
221 | if (queryIdx >= 0) {
222 | resourcePath = test[0].substr(0, queryIdx);
223 | resourceQuery = test[0].substr(queryIdx);
224 | } else {
225 | resourcePath = test[0];
226 | }
227 |
228 | const interpolatedName = loaderUtils.interpolateName(
229 | { resourcePath, resourceQuery },
230 | test[1],
231 | { content: test[2] }
232 | );
233 |
234 | expect(interpolatedName).toBe(test[3]);
235 | });
236 | });
237 |
238 | [
239 | "sha1fakename",
240 | "9dxfakename",
241 | "RSA-SHA256-fakename",
242 | "ecdsa-with-SHA1-fakename",
243 | "tls1.1-sha512-fakename",
244 | ].forEach((hashName) => {
245 | it("should pick hash algorithm by name " + hashName, () => {
246 | expect(() => {
247 | const interpolatedName = loaderUtils.interpolateName(
248 | {},
249 | "[" + hashName + ":hash:base64:10]",
250 | { content: "a" }
251 | );
252 | // if for any reason the system we're running on has a hash
253 | // algorithm matching any of our bogus names, at least make sure
254 | // the output is not the unmodified name:
255 | expect(interpolatedName[0]).not.toBe("[");
256 | }).toThrow(/digest method not supported/i);
257 | });
258 | });
259 |
260 | run([
261 | [
262 | [{}, "", { content: "test string" }],
263 | "e9e2c351e3c6b198.bin",
264 | "should interpolate default tokens",
265 | ],
266 | [
267 | [{}, "[hash:base64]", { content: "test string" }],
268 | "6eLDUePGsZg=",
269 | "should interpolate [hash] token with options",
270 | ],
271 | [
272 | [{}, "[unrecognized]", { content: "test string" }],
273 | "[unrecognized]",
274 | "should not interpolate unrecognized token",
275 | ],
276 | ]);
277 |
278 | it("should work without options", () => {
279 | const args = [{}, "foo/bar/[hash]"];
280 | const result = loaderUtils.interpolateName.apply(loaderUtils, args);
281 |
282 | expect(result).toBe("foo/bar/[hash]");
283 | });
284 |
285 | describe("no loader context", () => {
286 | const loaderContext = {};
287 | run([
288 | [[loaderContext, "[ext]", {}], "bin", "should interpolate [ext] token"],
289 | [
290 | [loaderContext, "[name]", {}],
291 | "file",
292 | "should interpolate [name] token",
293 | ],
294 | [[loaderContext, "[path]", {}], "", "should interpolate [path] token"],
295 | [
296 | [loaderContext, "[folder]", {}],
297 | "",
298 | "should interpolate [folder] token",
299 | ],
300 | ]);
301 | });
302 |
303 | describe("with loader context", () => {
304 | const loaderContext = { resourcePath: "/path/to/file.exe" };
305 | run([
306 | [[loaderContext, "[ext]", {}], "exe", "should interpolate [ext] token"],
307 | [
308 | [loaderContext, "[name]", {}],
309 | "file",
310 | "should interpolate [name] token",
311 | ],
312 | [
313 | [loaderContext, "[path]", {}],
314 | "/path/to/",
315 | "should interpolate [path] token",
316 | ],
317 | [
318 | [loaderContext, "[path]", { context: "/path/" }],
319 | "to/",
320 | "should interpolate [path] token with context",
321 | ],
322 | [
323 | [loaderContext, "[folder]", {}],
324 | "to",
325 | "should interpolate [folder] token",
326 | ],
327 | [
328 | [{ resourcePath: "." }, "[folder]", {}],
329 | "",
330 | "should interpolate [folder] token with short resourcePath",
331 | ],
332 | ]);
333 | });
334 |
335 | run([
336 | [
337 | [
338 | {
339 | resourcePath: "/xyz",
340 | options: {
341 | customInterpolateName(str, name, options) {
342 | return str + "-" + name + "-" + options.special;
343 | },
344 | },
345 | },
346 | "[name]",
347 | {
348 | special: "special",
349 | },
350 | ],
351 | "xyz-[name]-special",
352 | "should provide a custom interpolateName function in options",
353 | ],
354 | [
355 | [
356 | {
357 | resourcePath: "/foo/xyz.png",
358 | },
359 | "[1]-[name].[ext]",
360 | {
361 | regExp: /\/([a-z0-9]+)\/[a-z0-9]+\.png$/,
362 | },
363 | ],
364 | "foo-xyz.png",
365 | "should support regExp in options",
366 | ],
367 | ]);
368 | });
369 |
--------------------------------------------------------------------------------
/test/isUrlRequest.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const loaderUtils = require("../");
4 |
5 | function ExpectedError(regex) {
6 | this.regex = regex;
7 | }
8 | ExpectedError.prototype.matches = function (err) {
9 | return this.regex.test(err.message);
10 | };
11 |
12 | describe("isUrlRequest()", () => {
13 | [
14 | // without root
15 | [["//google.com"], false, "should be negative for scheme-agnostic urls"],
16 | [["http://google.com"], false, "should be negative for http urls"],
17 | [["HTTP://google.com"], false, "should be negative for http urls"],
18 | [["https://google.com"], false, "should be negative for https urls"],
19 | [["HTTPS://google.com"], false, "should be negative for https urls"],
20 |
21 | [["chrome-extension://"], false, "should be negative for nonstandard urls"],
22 | [["moz-extension://"], false, "should be negative for nonstandard urls"],
23 | [
24 | ["ms-browser-extension://"],
25 | false,
26 | "should be negative for nonstandard urls",
27 | ],
28 | [["custom-extension://"], false, "should be negative for nonstandard urls"],
29 |
30 | [["path/to/thing"], true, "should be positive for implicit relative urls"],
31 | [["./img.png"], true, "should be positive for implicit relative urls"],
32 | [["../img.png"], true, "should be positive for implicit relative urls"],
33 | [
34 | ["./img.png?foo=bar#hash"],
35 | true,
36 | "should be positive for implicit relative urls",
37 | ],
38 | [
39 | ["./path/to/thing"],
40 | true,
41 | "should be positive for explicit relative urls",
42 | ],
43 | [["~path/to/thing"], true, "should be positive for module urls (with ~)"],
44 | [
45 | ["some/other/stuff/and/then~path/to/thing"],
46 | true,
47 | "should be positive for module urls with path prefix",
48 | ],
49 | [
50 | ["./some/other/stuff/and/then~path/to/thing"],
51 | true,
52 | "should be positive for module urls with relative path prefix",
53 | ],
54 | [["C:/thing"], true, "should be positive for linux path with driver"],
55 | [["C:\\thing"], true, "should be positive for windows path with driver"],
56 | [
57 | ["directory/things"],
58 | true,
59 | "should be positive for relative path (linux)",
60 | ],
61 | [
62 | ["directory\\things"],
63 | true,
64 | "should be positive for relative path (windows)",
65 | ],
66 |
67 | // with root (normal path)
68 | [
69 | ["path/to/thing", "root/dir"],
70 | true,
71 | "should be positive with root if implicit relative url",
72 | ],
73 | [
74 | ["./path/to/thing", "root/dir"],
75 | true,
76 | "should be positive with root if explicit relative url",
77 | ],
78 |
79 | // with root (boolean) on Windows
80 | [
81 | ["C:\\path\\to\\thing"],
82 | true,
83 | "should be positive for Windows absolute paths with drive letter",
84 | ],
85 | [
86 | ["\\\\?\\UNC\\ComputerName\\path\\to\\thing"],
87 | true,
88 | "should be positive for Windows absolute UNC paths",
89 | ],
90 |
91 | // with root (module)
92 | [
93 | ["/path/to/thing", "~"],
94 | true,
95 | "should be positive for module url if root = ~",
96 | ],
97 |
98 | // with root (module path)
99 | [
100 | ["/path/to/thing", "~module"],
101 | true,
102 | "should be positive for module prefixes when root starts with ~",
103 | ],
104 | [
105 | ["/path/to/thing", "~module/"],
106 | true,
107 | "should be positive for module prefixes (with trailing slash) when root starts with ~",
108 | ],
109 |
110 | // error cases
111 | [
112 | ["/path/to/thing", 1],
113 | new ExpectedError(/unexpected parameters/i),
114 | "should throw an error on invalid root",
115 | ],
116 |
117 | // empty url
118 | [[""], true, "should be positive if url is empty"],
119 |
120 | // about url
121 | [["about:blank"], false, "should be negative for about:blank"],
122 |
123 | // hash
124 | [["#gradient"], false, "should be negative for hash url"],
125 |
126 | // url
127 | [["//sindresorhus.com"], false, "should ignore noscheme url"],
128 | [
129 | ["//at.alicdn.com/t/font_515771_emcns5054x3whfr.eot"],
130 | false,
131 | "should ignore noscheme url with path",
132 | ],
133 | [
134 | ["https://example.com/././foo"],
135 | false,
136 | "should ignore absolute url with relative",
137 | ],
138 |
139 | // non standard protocols
140 | [
141 | ["file://sindresorhus.com"],
142 | false,
143 | "should ignore non standard protocols (file)",
144 | ],
145 | [
146 | ["mailto:someone@example.com"],
147 | false,
148 | "should ignore non standard protocols (mailto)",
149 | ],
150 | [
151 | ["data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D"],
152 | true,
153 | "should work with non standard protocols (data)",
154 | ],
155 | [
156 | ["DATA:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D"],
157 | true,
158 | "should work with non standard protocols (data)",
159 | ],
160 |
161 | // root-relative url
162 | [["/"], true, "should work with root-relative url 1"],
163 | [["//"], false, "ignore root-relative url 1"],
164 | ].forEach((test) => {
165 | it(test[2], () => {
166 | const expected = test[1];
167 |
168 | try {
169 | const request = loaderUtils.isUrlRequest.apply(loaderUtils, test[0]);
170 |
171 | expect(request).toBe(expected);
172 | } catch (e) {
173 | if (expected instanceof ExpectedError) {
174 | expect(expected.matches(e)).toBe(true);
175 | } else {
176 | throw new Error("should not have thrown an error: " + e.message);
177 | }
178 | }
179 | });
180 | });
181 | });
182 |
--------------------------------------------------------------------------------
/test/urlToRequest.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const loaderUtils = require("../");
4 |
5 | function ExpectedError(regex) {
6 | this.regex = regex;
7 | }
8 |
9 | ExpectedError.prototype.matches = function (err) {
10 | return this.regex.test(err.message);
11 | };
12 |
13 | describe("urlToRequest()", () => {
14 | [
15 | // without root
16 | [
17 | ["path/to/thing"],
18 | "./path/to/thing",
19 | "should handle implicit relative urls",
20 | ],
21 | [
22 | ["./path/to/thing"],
23 | "./path/to/thing",
24 | "should handle explicit relative urls",
25 | ],
26 | [["~path/to/thing"], "path/to/thing", "should handle module urls (with ~)"],
27 | [
28 | ["some/other/stuff/and/then~path/to/thing"],
29 | "path/to/thing",
30 | "should handle module urls with path prefix",
31 | ],
32 | [
33 | ["./some/other/stuff/and/then~path/to/thing"],
34 | "path/to/thing",
35 | "should handle module urls with relative path prefix",
36 | ],
37 | // with root (normal path)
38 | [
39 | ["path/to/thing", "root/dir"],
40 | "./path/to/thing",
41 | "should do nothing with root if implicit relative url",
42 | ],
43 | [
44 | ["./path/to/thing", "root/dir"],
45 | "./path/to/thing",
46 | "should do nothing with root if explicit relative url",
47 | ],
48 | [
49 | ["/path/to/thing", "root/dir"],
50 | "root/dir/path/to/thing",
51 | "should include root if root-relative url",
52 | ],
53 | // with root (boolean)
54 | [
55 | ["/path/to/thing", true],
56 | "/path/to/thing",
57 | "should allow root-relative to exist as-is if root = `true`",
58 | ],
59 | // with root (boolean) on Windows
60 | [
61 | ["C:\\path\\to\\thing"],
62 | "C:\\path\\to\\thing",
63 | "should handle Windows absolute paths with drive letter",
64 | ],
65 | [
66 | ["\\\\?\\UNC\\ComputerName\\path\\to\\thing"],
67 | "\\\\?\\UNC\\ComputerName\\path\\to\\thing",
68 | "should handle Windows absolute UNC paths",
69 | ],
70 | // with root (module)
71 | [
72 | ["/path/to/thing", "~"],
73 | "path/to/thing",
74 | "should convert to module url if root = ~",
75 | ],
76 | // with root (module path)
77 | [
78 | ["/path/to/thing", "~module"],
79 | "module/path/to/thing",
80 | "should allow module prefixes when root starts with ~",
81 | ],
82 | [
83 | ["/path/to/thing", "~module/"],
84 | "module/path/to/thing",
85 | "should allow module prefixes (with trailing slash) when root starts with ~",
86 | ],
87 | // error cases
88 | [
89 | ["/path/to/thing", 1],
90 | new ExpectedError(/unexpected parameters/i),
91 | "should throw an error on invalid root",
92 | ],
93 | // difficult cases
94 | [
95 | ["a:b-not-\\window-path"],
96 | "./a:b-not-\\window-path",
97 | "should not incorrectly detect windows paths",
98 | ],
99 | // empty url
100 | [[""], "", "should do nothing if url is empty"],
101 | ].forEach((test) => {
102 | it(test[2], () => {
103 | const expected = test[1];
104 |
105 | try {
106 | const request = loaderUtils.urlToRequest.apply(loaderUtils, test[0]);
107 |
108 | expect(request).toBe(expected);
109 | } catch (e) {
110 | if (expected instanceof ExpectedError) {
111 | expect(expected.matches(e)).toBe(true);
112 | } else {
113 | throw new Error("should not have thrown an error: " + e.message);
114 | }
115 | }
116 | });
117 | });
118 | });
119 |
--------------------------------------------------------------------------------