├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE.md
├── ISSUE_TEMPLATE
│ ├── BUG.md
│ ├── DOCS.md
│ ├── FEATURE.md
│ ├── MODIFICATION.md
│ └── SUPPORT.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── node.js.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── CHANGELOG.md
├── LICENSE
├── README.md
├── azure-pipelines.yml
├── babel.config.js
├── commitlint.config.js
├── husky.config.js
├── lint-staged.config.js
├── package-lock.json
├── package.json
├── src
├── .gitignore
├── constants.js
├── index.js
├── lib
│ ├── compress.js
│ ├── emit.js
│ └── inline.js
└── options.json
└── test
├── fixtures
├── images
│ ├── jpg
│ │ └── mini.jpg
│ └── png
│ │ ├── 1.png
│ │ ├── 2.png
│ │ └── 3.png
└── index.js
├── helpers
├── compile.js
├── execute.js
├── getCompiler.js
├── getErrors.js
├── getWarnings.js
├── index.js
├── normalizeErrors.js
├── readAsset.js
└── readAssets.js
└── loader.test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
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 | [*.md]
12 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /dist
3 | /node_modules
4 | /test/fixtures
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['@webpack-contrib/eslint-config-webpack', 'prettier'],
4 | };
5 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | bin/* eol=lf
3 | yarn.lock -diff
4 | package-lock.json -diff
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/BUG.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🐛 Bug Report
3 | about: Something went awry and you'd like to tell us about it.
4 | ---
5 |
6 |
16 |
17 | - Operating System:
18 | - Node Version:
19 | - NPM Version:
20 | - webpack Version:
21 | - image-optimize-loader Version:
22 |
23 | ### Expected Behavior
24 |
25 |
26 |
27 | ### Actual Behavior
28 |
29 |
30 |
31 | ### Code
32 |
33 | ```js
34 | // webpack.config.js
35 | // If your code blocks are over 20 lines, please paste a link to a gist
36 | // (https://gist.github.com).
37 | ```
38 |
39 | ```js
40 | // additional code, HEY YO remove this block if you don't need it
41 | ```
42 |
43 | ### How Do We Reproduce?
44 |
45 |
51 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/DOCS.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 📚 Documentation
3 | about: Are the docs lacking or missing something? Do they need some new 🔥 hotness? Tell us here.
4 | ---
5 |
6 |
16 |
17 | Documentation Is:
18 |
19 |
20 |
21 | - [ ] Missing
22 | - [ ] Needed
23 | - [ ] Confusing
24 | - [ ] Not Sure?
25 |
26 | ### Please Explain in Detail...
27 |
28 | ### Your Proposal for Changes
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/FEATURE.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: ✨ Feature Request
3 | about: Suggest an idea for this project
4 | ---
5 |
6 |
16 |
17 | - Operating System:
18 | - Node Version:
19 | - NPM Version:
20 | - webpack Version:
21 | - image-optimize-loader Version:
22 |
23 | ### Feature Proposal
24 |
25 | ### Feature Use Case
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/MODIFICATION.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🔧 Modification Request
3 | about: Would you like something work differently? Have an alternative approach? This is the template for you.
4 | ---
5 |
6 |
16 |
17 | - Operating System:
18 | - Node Version:
19 | - NPM Version:
20 | - webpack Version:
21 | - image-optimize-loader Version:
22 |
23 | ### Expected Behavior / Situation
24 |
25 | ### Actual Behavior / Situation
26 |
27 | ### Modification Proposal
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/SUPPORT.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🆘 Support, Help, and Advice
3 | about: 👉🏽 Need support, help, or advice? Don't open an issue! Head to StackOverflow or https://gitter.im/webpack/webpack.
4 | ---
5 |
6 | Hey there! If you need support, help, or advice then this is not the place to ask.
7 | Please visit [StackOverflow](https://stackoverflow.com/questions/tagged/webpack)
8 | or [the Webpack Gitter](https://gitter.im/webpack/webpack) instead.
9 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
10 |
11 | This PR contains a:
12 |
13 | - [ ] **bugfix**
14 | - [ ] new **feature**
15 | - [ ] **code refactor**
16 | - [ ] **test update**
17 | - [ ] **typo fix**
18 | - [ ] **metadata update**
19 |
20 | ### Motivation / Use-Case
21 |
22 |
27 |
28 | ### Breaking Changes
29 |
30 |
34 |
35 | ### Additional Info
36 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: test
5 |
6 | on:
7 | push:
8 | branches: [master]
9 | pull_request:
10 | branches: [master]
11 |
12 | jobs:
13 | lint:
14 | runs-on: ubuntu-latest
15 |
16 | strategy:
17 | matrix:
18 | node-version: [10.x, 12.x, 14.x]
19 |
20 | steps:
21 | - uses: actions/checkout@v2
22 | - name: Use Node.js ${{ matrix.node-version }}
23 | uses: actions/setup-node@v1
24 | with:
25 | node-version: ${{ matrix.node-version }}
26 | - run: npm ci
27 | - run: npm run lint
28 |
29 | test:
30 | runs-on: ubuntu-latest
31 |
32 | strategy:
33 | matrix:
34 | node-version: [10.x, 12.x, 14.x]
35 |
36 | steps:
37 | - uses: actions/checkout@v2
38 | - name: Use Node.js ${{ matrix.node-version }}
39 | uses: actions/setup-node@v1
40 | with:
41 | node-version: ${{ matrix.node-version }}
42 | - run: npm ci
43 | - run: npm i file-loader
44 | - run: npm test
45 |
46 | build:
47 | runs-on: ubuntu-latest
48 |
49 | strategy:
50 | matrix:
51 | node-version: [10.x, 12.x, 14.x]
52 |
53 | steps:
54 | - uses: actions/checkout@v2
55 | - name: Use Node.js ${{ matrix.node-version }}
56 | uses: actions/setup-node@v1
57 | with:
58 | node-version: ${{ matrix.node-version }}
59 | - run: npm ci
60 | - run: npm run build --if-present
61 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | logs
2 | *.log
3 | npm-debug.log*
4 | .eslintcache
5 | /coverage
6 | /dist
7 | /local
8 | /reports
9 | /node_modules
10 | .DS_Store
11 | Thumbs.db
12 | .idea
13 | *.iml
14 | .vscode
15 | *.sublime-project
16 | *.sublime-workspace
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /dist
3 | /node_modules
4 | /test/fixtures
5 | CHANGELOG.md
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | trailingComma: 'es5',
4 | arrowParens: 'always',
5 | };
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
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.
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
6 |
7 | # image-optimize-loader
8 |
9 | [![npm][npm]][npm-url]
10 | [![node][node]][node-url]
11 | [![deps][deps]][deps-url]
12 | 
13 |
14 | This special **image optimize webpack loader** can:
15 |
16 | - Help you [**encode image** / **inline image**] and [**compress image** / **minify image**] when loaded with webpack.
17 |
18 | - Help you **tranform PNG/JPG images into WEBP** when needed.
19 |
20 | Its special encoding(inlining) ability is stronger than [url-loader](https://github.com/webpack-contrib/url-loader), especially useful for performance-optimization scenarios. And it will compress images automatically both for `emited image file` or `inlined images string`, without complicated configurations.
21 |
22 | ## Getting Started
23 |
24 | To begin, you'll need to install `img-optimize-loader`:
25 |
26 | ```console
27 | $ npm install img-optimize-loader --save-dev
28 | ```
29 |
30 | Then, all you need to do is adding the `img-optimize-loader` to your `webpack` config.
31 |
32 | > You don't need to specify extra loaders like `file-loader` or `url-loader` for your images. `img-optimize-loader` will automaticlly handle everything.
33 |
34 | For example:
35 |
36 | **webpack.config.js**
37 |
38 | ```js
39 | module.exports = {
40 | module: {
41 | rules: [
42 | {
43 | test: /\.(png|jpe?g|webp|git|svg|)$/i,
44 | use: [
45 | {
46 | loader: 'img-optimize-loader',
47 | },
48 | ],
49 | },
50 | ],
51 | },
52 | };
53 | ```
54 |
55 | **You can import your image. Compression and encoding will happen according to your configuration.**
56 |
57 | ```js
58 | import file from 'image.png';
59 | ```
60 |
61 | ## Features
62 |
63 | ### 1. Encode images and inline them into js/css files.
64 |
65 | > **Encode image with `base64`, `utf8`, `latin1`, `hex`, `ascii`, `binary`,`ucs2`**
66 |
67 | > **Inline image into JS / CSS files**
68 |
69 | When I use `url-loader` to encode images, I can only depend on [limit](https://github.com/webpack-contrib/url-loader#limit) configuration to decide whether to enable encodeing. **As we know, the image whose size was smaller than the limit, will always be encoded.**
70 |
71 | But I found problems when i want to inline a large size image into my entry jsbundle because this image is so important to my first screen rendering; Or when I don't want to inline trivial small images because there is no need to load them in time. I can't improve my page performance in this scene with `url-loader`.
72 |
73 | Now with `img-optimize-loader` we can take more flexible control on this. We can specify every image whether or not to be encoded easily(using file query)regardless of `limit` configuration. Still, if we don't specify it, `limit` configuration will take control.
74 |
75 | **index.js**
76 |
77 | ```js
78 | // Always let foo.png be encoded and inlined here regardless of 'limit configuration'
79 | import encodedImage from './encode.png?__inline';
80 |
81 | // Always emit real image file regardless of 'limit configuration'
82 | import fileImage from './emit.png?__antiInline';
83 | ```
84 |
85 | The query symbol `__inline` and `__antiInline` can be customed by your self.
86 |
87 | ### 2. Compress your images
88 |
89 | > The compression algorithm is based on [imagemin](https://github.com/kevva/imagemin). It supports images in png, jpg, gif, webp, svg format.
90 | >
91 | > - **minify JPEG image**
92 | > - **minify PNG image**
93 | > - **minify GIF image**
94 | > - **minify WEBP image**
95 | > - **minify SVG image**
96 |
97 | We support 3 levels for you to compress images automaticlly.
98 |
99 | | level | description |
100 | | -------- | --------------------------------------------------------------------------------------- |
101 | | loseless | Only use lossless compress algorithm. Only support png/webp/svg images |
102 | | low | Cause a little distortion,and get small files. It will compress png/jpg/svg/gif images |
103 | | high | Cause more distortion,and get smaller files. It will compress png/jpg/svg/gif images |
104 |
105 | > To deal with webp images, please refer [webp](https://github.com/GaoYYYang/image-optimize-loader#3-transform-your-pngjpg-into-webp)
106 |
107 | **webpack.config.js**
108 |
109 | ```js
110 | module.exports = {
111 | module: {
112 | rules: [
113 | {
114 | test: /\.(png|jpe?g|webp|git|svg|)$/i,
115 | use: [
116 | {
117 | loader: `img-optimize-loader`,
118 | options: {
119 | compress: {
120 | // This will take more time and get smaller images.
121 | mode: 'high', // 'lossless', 'low'
122 | disableOnDevelopment: true,
123 | },
124 | },
125 | },
126 | ],
127 | },
128 | ],
129 | },
130 | };
131 | ```
132 |
133 | And you can also adjust the compression manually using [more params](https://github.com/GaoYYYang/image-optimize-loader#compressmozjpeg).
134 |
135 | ```js
136 | module.exports = {
137 | module: {
138 | rules: [
139 | {
140 | test: /\.(png|jpe?g|webp|git|svg|)$/i,
141 | use: [
142 | {
143 | loader: 'img-optimize-loader',
144 | options: {
145 | compress: {
146 | // loseless compression for png
147 | optipng: {
148 | optimizationLevel: 4,
149 | },
150 | // lossy compression for png. This will generate smaller file than optipng.
151 | pngquant: {
152 | quality: [0.2, 0.8],
153 | },
154 | // Compression for webp.
155 | // You can also tranform jpg/png into webp.
156 | webp: {
157 | quality: 100,
158 | },
159 | // Compression for svg.
160 | svgo: true,
161 | // Compression for gif.
162 | gifsicle: {
163 | optimizationLevel: 3,
164 | },
165 | // Compression for jpg.
166 | mozjpeg: {
167 | progressive: true,
168 | quality: 60,
169 | },
170 | },
171 | },
172 | },
173 | ],
174 | },
175 | ],
176 | },
177 | };
178 | ```
179 |
180 | ### 3. Transform your png/jpg into webp
181 |
182 | When you enable `compress.webp`, it will transform your png/jpg into webp files, and there will be no png/jpg files generated. Your source code will directly use webp file instead of png/jpg.
183 |
184 | Generally, when you can use webp without incompatibility problem
185 | , there will be no need to use png or jpg any more, because webp files are always smaller than their png/jpg origin.
186 |
187 | **webpack.config.js**
188 |
189 | ```js
190 | module.exports = {
191 | module: {
192 | rules: [
193 | {
194 | test: /\.(png|jpe?g|webp|git|svg|)$/i,
195 | use: [
196 | {
197 | loader: `img-optimize-loader`,
198 | options: {
199 | compress: {
200 | // This will transform your png/jpg into webp.
201 | webp: true,
202 | disableOnDevelopment: true,
203 | },
204 | },
205 | },
206 | ],
207 | },
208 | ],
209 | },
210 | };
211 | ```
212 |
213 | Referer to [webp configuration](https://github.com/GaoYYYang/image-optimize-loader#compresswebp) for details.
214 |
215 | **index.js**
216 |
217 | ```js
218 | // This two images will be transformed into webp and your source code will use the webp format.
219 | import encodedImage from './encode.png';
220 |
221 | import fileImage from './test.jpg';
222 | ```
223 |
224 | ## Options
225 |
226 | ### `name`
227 |
228 | Type: `[string]`
229 | Default: `'imgs/[contenthash].[ext]'`
230 |
231 | Specifies a custom filename template for the target images(s) using the query parameter name. For example, to emit a image from your context directory into the output directory retaining the full directory structure, you might use:
232 |
233 | **webpack.config.js**
234 |
235 | ```js
236 | module.exports = {
237 | module: {
238 | rules: [
239 | {
240 | loader: `img-optimize-loader`,
241 | options: {
242 | name: '[path][name].[ext]',
243 | },
244 | },
245 | ],
246 | },
247 | };
248 | ```
249 |
250 | ### `esModule`
251 |
252 | Type: `[Boolean]`
253 | Default: `false`
254 |
255 | Decides js modules generated from image. Whether to use the ES modules or commonjs.
256 |
257 | **webpack.config.js**
258 |
259 | ```js
260 | module.exports = {
261 | module: {
262 | rules: [
263 | {
264 | loader: `img-optimize-loader`,
265 | options: {
266 | esModule: false,
267 | },
268 | },
269 | ],
270 | },
271 | };
272 | ```
273 | ### `outputPath`
274 |
275 | Type: `String|Function`
276 | Default: `undefined`
277 |
278 | Specify a filesystem path where the target file(s) will be placed.
279 |
280 | #### `String`
281 |
282 | **webpack.config.js**
283 |
284 | ```js
285 | module.exports = {
286 | module: {
287 | rules: [
288 | {
289 | test: /\.(png|jpe?g|gif)$/i,
290 | loader: 'img-optimize-loader',
291 | options: {
292 | outputPath: 'images',
293 | },
294 | },
295 | ],
296 | },
297 | };
298 | ```
299 |
300 | #### `Function`
301 |
302 | **webpack.config.js**
303 |
304 | ```js
305 | module.exports = {
306 | module: {
307 | rules: [
308 | {
309 | test: /\.(png|jpe?g|gif)$/i,
310 | loader: 'img-optimize-loader',
311 | options: {
312 | outputPath: (url, resourcePath, context) => {
313 | return `output_path/${url}`;
314 | },
315 | },
316 | },
317 | ],
318 | },
319 | };
320 | ```
321 |
322 | ### `publicPath`
323 |
324 | Type: `String|Function`
325 | Default: [`__webpack_public_path__`](https://webpack.js.org/api/module-variables/#__webpack_public_path__-webpack-specific-)+outputPath
326 |
327 | Specifies a custom public path for the target file(s).
328 |
329 | #### `String`
330 |
331 | **webpack.config.js**
332 |
333 | ```js
334 | module.exports = {
335 | module: {
336 | rules: [
337 | {
338 | test: /\.(png|jpe?g|gif)$/i,
339 | loader: 'img-optimize-loader',
340 | options: {
341 | publicPath: 'assets',
342 | },
343 | },
344 | ],
345 | },
346 | };
347 | ```
348 |
349 | #### `Function`
350 |
351 | **webpack.config.js**
352 |
353 | ```js
354 | module.exports = {
355 | module: {
356 | rules: [
357 | {
358 | test: /\.(png|jpe?g|gif)$/i,
359 | loader: 'img-optimize-loader',
360 | options: {
361 | publicPath: (url, resourcePath, context) => {
362 | return `public_path/${url}`;
363 | },
364 | },
365 | },
366 | ],
367 | },
368 | };
369 | ```
370 |
371 | ### `context`
372 |
373 | Type: `String`
374 | Default: [`context`](https://webpack.js.org/configuration/entry-context/#context)
375 |
376 | Specifies a custom file context.
377 |
378 | ### `emitFile`
379 |
380 | Type: `Boolean`
381 | Default: `true`
382 |
383 | If true, emits a file (writes a file to the filesystem). If false, the loader
384 | will return a public URI but **will not** emit the file. It is often useful to
385 | disable this option for server-side packages.
386 |
387 |
388 | ### `inline.symbol`
389 |
390 | Type: `[String]`
391 | Default: `__inline`
392 |
393 | Query symbol used to specify the image that should be encoded and inlined.
394 |
395 | **webpack.config.js**
396 |
397 | ```js
398 | module.exports = {
399 | module: {
400 | rules: [
401 | {
402 | loader: `img-optimize-loader`,
403 | options: {
404 | inline: {
405 | symbol: '__inline',
406 | },
407 | },
408 | },
409 | ],
410 | },
411 | };
412 | ```
413 |
414 | ### `inline.antiSymbol`
415 |
416 | Type: `[String]`
417 | Default: `__antiInline`
418 |
419 | Query symbol used to specify the image that should not be encoded and inlined.
420 |
421 | **webpack.config.js**
422 |
423 | ```js
424 | module.exports = {
425 | module: {
426 | rules: [
427 | {
428 | loader: `img-optimize-loader`,
429 | options: {
430 | inline: {
431 | antiSymbol: '__antiInline',
432 | },
433 | },
434 | },
435 | ],
436 | },
437 | };
438 | ```
439 |
440 | ### `inline.limit`
441 |
442 | Type: `[Boolean|Number|String]`
443 | Default: `5000`
444 |
445 | A Number or String specifying the maximum size of a encoded image in bytes. If the image size is equal or greater than the limit `file-loader` will be used (by default) and all query parameters are passed to it.
446 |
447 | **webpack.config.js**
448 |
449 | ```js
450 | module.exports = {
451 | module: {
452 | rules: [
453 | {
454 | loader: `img-optimize-loader`,
455 | options: {
456 | inline: {
457 | antiSymbol: '__antiInline',
458 | },
459 | },
460 | },
461 | ],
462 | },
463 | };
464 | ```
465 |
466 | ### `inline.mimetype`
467 |
468 | Type: `Boolean|String`
469 | Default: based from [mime-types](https://github.com/jshttp/mime-types)
470 |
471 | Specify the `mimetype` which the file will be inlined with.
472 | If unspecified the `mimetype` value will be used from [mime-types](https://github.com/jshttp/mime-types).
473 |
474 | #### `Boolean`
475 |
476 | The `true` value allows to generation the `mimetype` part from [mime-types](https://github.com/jshttp/mime-types).
477 | The `false` value removes the `mediatype` part from a Data URL (if omitted, defaults to `text/plain;charset=US-ASCII`).
478 |
479 | **webpack.config.js**
480 |
481 | ```js
482 | module.exports = {
483 | module: {
484 | rules: [
485 | {
486 | loader: `img-optimize-loader`,
487 | options: {
488 | inline: {
489 | mimetype: false,
490 | },
491 | },
492 | },
493 | ],
494 | },
495 | };
496 | ```
497 |
498 | #### `String`
499 |
500 | Sets the MIME type for the file to be transformed.
501 |
502 | **webpack.config.js**
503 |
504 | ```js
505 | module.exports = {
506 | module: {
507 | rules: [
508 | {
509 | loader: `img-optimize-loader`,
510 | options: {
511 | inline: {
512 | mimetype: 'image/png',
513 | },
514 | },
515 | },
516 | ],
517 | },
518 | };
519 | ```
520 |
521 | ### `inline.encoding`
522 |
523 | Type: `Boolean|String`
524 | Default: `base64`
525 |
526 | Specify the `encoding` which the file will be inlined with.
527 | If unspecified the `encoding` will be `base64`.
528 |
529 | #### `Boolean`
530 |
531 | If you don't want to use any encoding you can set `encoding` to `false` however if you set it to `true` it will use the default encoding `base64`.
532 |
533 | **webpack.config.js**
534 |
535 | ```js
536 | module.exports = {
537 | module: {
538 | rules: [
539 | {
540 | test: /\.svg$/i,
541 | use: [
542 | {
543 | loader: `img-optimize-loader`,
544 | options: {
545 | inline: {
546 | encoding: false,
547 | },
548 | },
549 | },
550 | ],
551 | },
552 | ],
553 | },
554 | };
555 | ```
556 |
557 | #### `String`
558 |
559 | It supports [Node.js Buffers and Character Encodings](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings) which are `["utf8","utf16le","latin1","base64","hex","ascii","binary","ucs2"]`.
560 |
561 | **webpack.config.js**
562 |
563 | ```js
564 | module.exports = {
565 | module: {
566 | rules: [
567 | {
568 | loader: `img-optimize-loader`,
569 | options: {
570 | inline: {
571 | encoding: 'utf8',
572 | },
573 | },
574 | },
575 | ],
576 | },
577 | };
578 | ```
579 |
580 | ### `inline.generator`
581 |
582 | Type: `Function`
583 | Default: `(mimetype, encoding, content, resourcePath) => mimetype;encoding,base64_content`
584 |
585 | You can create you own custom implementation for encoding data.
586 |
587 | **webpack.config.js**
588 |
589 | ```js
590 | module.exports = {
591 | module: {
592 | rules: [
593 | {
594 | loader: `img-optimize-loader`,
595 | options: {
596 | inline: {
597 | // The `mimetype` and `encoding` arguments will be obtained from your options
598 | // The `resourcePath` argument is path to file.
599 | generator: (content, mimetype, encoding, resourcePath) => {
600 | if (/\.html$/i.test(resourcePath)) {
601 | return `data:${mimetype},${content.toString()}`;
602 | }
603 |
604 | return `data:${mimetype}${
605 | encoding ? `;${encoding}` : ''
606 | },${content.toString(encoding)}`;
607 | },
608 | },
609 | },
610 | },
611 | ],
612 | },
613 | };
614 | ```
615 |
616 | ### `compress.mode`
617 |
618 | Type: `string`
619 | Default: `low`
620 |
621 | Specify the compress level.
622 | | level | description|
623 | |-|-|
624 | | loseless | Only use lossless compress algorithm. Only support png/webp/svg images|
625 | | low | Cause a little distortion,and get small files. It will compress png/jpg/svg/webp/gif images|
626 | | high | Cause more distortion,and get smaller files. It will compress png/jpg/svg/webp/gif images|
627 |
628 | **webpack.config.js**
629 |
630 | ```js
631 | module.exports = {
632 | module: {
633 | rules: [
634 | {
635 | loader: `img-optimize-loader`,
636 | options: {
637 | compress: {
638 | mode: 'high',
639 | },
640 | },
641 | },
642 | ],
643 | },
644 | };
645 | ```
646 |
647 | ### `compress.mozjpeg`
648 |
649 | Type: `[Object|Boolean]`
650 |
651 | Compress jpg images.
652 |
653 | #### `Boolean`
654 |
655 | If you don't want to compress jpg files, you can set `mozjpeg` to `false` however if you set it to `true` it will generator the settings according to `compress.mode` configuration.
656 |
657 | **webpack.config.js**
658 |
659 | ```js
660 | module.exports = {
661 | module: {
662 | rules: [
663 | {
664 | loader: `img-optimize-loader`,
665 | options: {
666 | compress: {
667 | mozjpeg: false,
668 | },
669 | },
670 | },
671 | ],
672 | },
673 | };
674 | ```
675 |
676 | #### `Object`
677 |
678 | **webpack.config.js**
679 |
680 | ```js
681 | module.exports = {
682 | module: {
683 | rules: [
684 | {
685 | loader: `img-optimize-loader`,
686 | options: {
687 | compress: {
688 | mozjpeg: {},
689 | },
690 | },
691 | },
692 | ],
693 | },
694 | };
695 | ```
696 |
697 | Link to [mozjpeg configuration](https://github.com/imagemin/imagemin-mozjpeg)
698 |
699 | ### `compress.optipng`
700 |
701 | Type: `[Object|Boolean]`
702 |
703 | Compress png images.
704 |
705 | #### `Boolean`
706 |
707 | If you don't want to use optipng to compress png files, you can set `optipng` to `false` however if you set it to `true` it will generator the settings according to `compress.mode` configuration.
708 |
709 | **webpack.config.js**
710 |
711 | ```js
712 | module.exports = {
713 | module: {
714 | rules: [
715 | {
716 | loader: `img-optimize-loader`,
717 | options: {
718 | compress: {
719 | optipng: false,
720 | },
721 | },
722 | },
723 | ],
724 | },
725 | };
726 | ```
727 |
728 | #### `Object`
729 |
730 | **webpack.config.js**
731 |
732 | ```js
733 | module.exports = {
734 | module: {
735 | rules: [
736 | {
737 | loader: `img-optimize-loader`,
738 | options: {
739 | compress: {
740 | optipng: {},
741 | },
742 | },
743 | },
744 | ],
745 | },
746 | };
747 | ```
748 |
749 | Link to [optipng configuration](https://github.com/kevva/imagemin-optipng)
750 |
751 | ### `compress.pngquant`
752 |
753 | Type: `[Object|Boolean]`
754 |
755 | Compress png images.
756 |
757 | #### `Boolean`
758 |
759 | If you don't want to use pngquant to compress png files, you can set `pngquant` to `false` however if you set it to `true` it will generator the settings according to `compress.mode` configuration.
760 |
761 | **webpack.config.js**
762 |
763 | ```js
764 | module.exports = {
765 | module: {
766 | rules: [
767 | {
768 | loader: `img-optimize-loader`,
769 | options: {
770 | compress: {
771 | pngquant: false,
772 | },
773 | },
774 | },
775 | ],
776 | },
777 | };
778 | ```
779 |
780 | #### `Object`
781 |
782 | **webpack.config.js**
783 |
784 | ```js
785 | module.exports = {
786 | module: {
787 | rules: [
788 | {
789 | loader: `img-optimize-loader`,
790 | options: {
791 | compress: {
792 | pngquant: {},
793 | },
794 | },
795 | },
796 | ],
797 | },
798 | };
799 | ```
800 |
801 | Link to [pngquant configuration](https://github.com/imagemin/imagemin-pngquant)
802 |
803 | ### `compress.svgo`
804 |
805 | Type: `[Object|Boolean]`
806 |
807 | Compress svg images.
808 |
809 | #### `Boolean`
810 |
811 | If you don't want to compress svg files, you can set `svgo` to `false` however if you set it to `true` it will generator the settings according to `compress.mode` configuration.
812 |
813 | **webpack.config.js**
814 |
815 | ```js
816 | module.exports = {
817 | module: {
818 | rules: [
819 | {
820 | loader: `img-optimize-loader`,
821 | options: {
822 | compress: {
823 | svgo: false,
824 | },
825 | },
826 | },
827 | ],
828 | },
829 | };
830 | ```
831 |
832 | #### `Object`
833 |
834 | **webpack.config.js**
835 |
836 | ```js
837 | module.exports = {
838 | module: {
839 | rules: [
840 | {
841 | loader: `img-optimize-loader`,
842 | options: {
843 | compress: {
844 | svgo: {},
845 | },
846 | },
847 | },
848 | ],
849 | },
850 | };
851 | ```
852 |
853 | Link to [svgo configuration](https://github.com/kevva/imagemin-svgo)
854 |
855 | ### `compress.gifsicle`
856 |
857 | Type: `[Object|Boolean]`
858 |
859 | Compress gif images.
860 |
861 | #### `Boolean`
862 |
863 | If you don't want to compress gif files, you can set `gifsicle` to `false` however if you set it to `true` it will generator the settings according to `compress.mode` configuration.
864 |
865 | **webpack.config.js**
866 |
867 | ```js
868 | module.exports = {
869 | module: {
870 | rules: [
871 | {
872 | loader: `img-optimize-loader`,
873 | options: {
874 | compress: {
875 | gifsicle: false,
876 | },
877 | },
878 | },
879 | ],
880 | },
881 | };
882 | ```
883 |
884 | #### `Object`
885 |
886 | **webpack.config.js**
887 |
888 | ```js
889 | module.exports = {
890 | module: {
891 | rules: [
892 | {
893 | loader: `img-optimize-loader`,
894 | options: {
895 | compress: {
896 | gifsicle: {},
897 | },
898 | },
899 | },
900 | ],
901 | },
902 | };
903 | ```
904 |
905 | ### `compress.webp`
906 |
907 | Type: `[Object|Boolean]`
908 | Default: `false`
909 |
910 | Transform png/jpg into webp. Compress webp files.
911 |
912 | #### `Boolean`
913 |
914 | If you want to transform png/jpg into webp, you can set `webp` to `true`.
915 |
916 | **webpack.config.js**
917 |
918 | ```js
919 | module.exports = {
920 | module: {
921 | rules: [
922 | {
923 | loader: `img-optimize-loader`,
924 | options: {
925 | compress: {
926 | webp: true,
927 | },
928 | },
929 | },
930 | ],
931 | },
932 | };
933 | ```
934 |
935 | #### `Object`
936 |
937 | **webpack.config.js**
938 |
939 | ```js
940 | module.exports = {
941 | module: {
942 | rules: [
943 | {
944 | loader: `img-optimize-loader`,
945 | options: {
946 | compress: {
947 | webp: {},
948 | },
949 | },
950 | },
951 | ],
952 | },
953 | };
954 | ```
955 |
956 | Link to [webp configuration](https://github.com/imagemin/imagemin-webp#options)
957 |
958 | ## Inspiration
959 |
960 | - [image-webpack-loader](https://github.com/tcoopman/image-webpack-loader)
961 | - [url-loader](https://github.com/webpack/url-loader)
962 | - [imagemin](https://github.com/imagemin/imagemin)
963 |
964 | ## License
965 |
966 | [MIT](./LICENSE)
967 |
968 | [npm]: https://img.shields.io/npm/v/img-optimize-loader.svg
969 | [npm-url]: https://npmjs.com/package/img-optimize-loader
970 | [node]: https://img.shields.io/node/v/img-optimize-loader.svg
971 | [node-url]: https://nodejs.org
972 | [deps]: https://david-dm.org/GaoYYYang/image-optimize-loader.svg
973 | [deps-url]: https://david-dm.org/GaoYYYang/image-optimize-loader
974 | [tests]: https://github.com/GaoYYYang/image-optimize-loader/workflows/test/badge.svg
975 | [tests-url]: https://github.com/GaoYYYang/image-optimize-loader/actions
976 | [size]: https://packagephobia.now.sh/badge?p=img-optimize-loader
977 | [size-url]: https://packagephobia.now.sh/result?p=img-optimize-loader
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - master
3 | - next
4 |
5 | variables:
6 | npm_config_cache: $(Pipeline.Workspace)/.npm
7 |
8 | jobs:
9 | - job: Lint
10 | pool:
11 | vmImage: ubuntu-latest
12 | steps:
13 | - task: NodeTool@0
14 | inputs:
15 | versionSpec: ^10.13.0
16 | displayName: 'Install Node.js'
17 | - task: Npm@1
18 | inputs:
19 | command: custom
20 | customCommand: i -g npm@latest
21 | displayName: 'Install latest NPM'
22 | - script: |
23 | node -v
24 | npm -v
25 | displayName: 'Print versions'
26 | - task: CacheBeta@1
27 | inputs:
28 | key: npm | $(Agent.OS) | package-lock.json
29 | path: $(npm_config_cache)
30 | displayName: 'Cache npm'
31 | - script: npm ci
32 | displayName: 'Install dependencies'
33 | - script: npm run lint
34 | displayName: 'Run lint'
35 | - script: npm run security
36 | displayName: 'Run NPM audit'
37 | - script: ./node_modules/.bin/commitlint-azure-pipelines
38 | displayName: 'Run lint commit message'
39 |
40 | - job: Linux
41 | pool:
42 | vmImage: ubuntu-latest
43 | strategy:
44 | maxParallel: 4
45 | matrix:
46 | node-13:
47 | node_version: ^13.0.0
48 | webpack_version: latest
49 | node-12:
50 | node_version: ^12.0.0
51 | webpack_version: latest
52 | node-10:
53 | node_version: ^10.13.0
54 | webpack_version: latest
55 | node-10-canary:
56 | node_version: ^10.13.0
57 | webpack_version: next
58 | steps:
59 | - task: NodeTool@0
60 | inputs:
61 | versionSpec: $(node_version)
62 | displayName: 'Install Node.js $(node_version)'
63 | - task: Npm@1
64 | inputs:
65 | command: custom
66 | customCommand: i -g npm@latest
67 | displayName: 'Install latest NPM'
68 | - script: |
69 | node -v
70 | npm -v
71 | displayName: 'Print versions'
72 | - task: CacheBeta@1
73 | inputs:
74 | key: npm | $(Agent.OS) | package-lock.json
75 | path: $(npm_config_cache)
76 | displayName: 'Cache npm'
77 | - script: npm ci
78 | displayName: 'Install dependencies'
79 | - script: npm i webpack@$(webpack_version)
80 | displayName: 'Install "webpack@$(webpack_version)"'
81 | - script: npm run test:coverage -- --ci --reporters="default" --reporters="jest-junit" || $(continue_on_error)
82 | displayName: 'Run tests with coverage'
83 | - task: PublishTestResults@2
84 | inputs:
85 | testRunTitle: 'Linux with Node.js $(node_version)'
86 | testResultsFiles: '**/junit.xml'
87 | condition: succeededOrFailed()
88 | displayName: 'Publish test results'
89 | - script: curl -s https://codecov.io/bash | bash -s -- -t $(CODECOV_TOKEN)
90 | condition: succeededOrFailed()
91 | displayName: 'Submit coverage data to codecov'
92 |
93 | - job: macOS
94 | pool:
95 | vmImage: macOS-latest
96 | strategy:
97 | maxParallel: 4
98 | matrix:
99 | node-13:
100 | node_version: ^13.0.0
101 | webpack_version: latest
102 | node-12:
103 | node_version: ^12.0.0
104 | webpack_version: latest
105 | node-10:
106 | node_version: ^10.13.0
107 | webpack_version: latest
108 | node-10-canary:
109 | node_version: ^10.13.0
110 | webpack_version: next
111 | steps:
112 | - task: NodeTool@0
113 | inputs:
114 | versionSpec: $(node_version)
115 | displayName: 'Install Node.js $(node_version)'
116 | - task: Npm@1
117 | inputs:
118 | command: custom
119 | customCommand: i -g npm@latest
120 | displayName: 'Install latest NPM'
121 | - script: |
122 | node -v
123 | npm -v
124 | displayName: 'Print versions'
125 | - task: CacheBeta@1
126 | inputs:
127 | key: npm | $(Agent.OS) | package-lock.json
128 | path: $(npm_config_cache)
129 | displayName: 'Cache npm'
130 | - script: npm ci
131 | displayName: 'Install dependencies'
132 | - script: npm i webpack@$(webpack_version)
133 | displayName: 'Install "webpack@$(webpack_version)"'
134 | - script: npm run test:coverage -- --ci --reporters="default" --reporters="jest-junit" || $(continue_on_error)
135 | displayName: 'Run tests with coverage'
136 | - task: PublishTestResults@2
137 | inputs:
138 | testRunTitle: 'Linux with Node.js $(node_version)'
139 | testResultsFiles: '**/junit.xml'
140 | condition: succeededOrFailed()
141 | displayName: 'Publish test results'
142 | - script: curl -s https://codecov.io/bash | bash -s -- -t $(CODECOV_TOKEN)
143 | condition: succeededOrFailed()
144 | displayName: 'Submit coverage data to codecov'
145 |
146 | - job: Windows
147 | pool:
148 | vmImage: windows-latest
149 | strategy:
150 | maxParallel: 4
151 | matrix:
152 | node-13:
153 | node_version: ^13.0.0
154 | webpack_version: latest
155 | node-12:
156 | node_version: ^12.0.0
157 | webpack_version: latest
158 | node-10:
159 | node_version: ^10.13.0
160 | webpack_version: latest
161 | node-10-canary:
162 | node_version: ^10.13.0
163 | webpack_version: next
164 | steps:
165 | - script: 'git config --global core.autocrlf input'
166 | displayName: 'Config git core.autocrlf'
167 | - checkout: self
168 | - task: NodeTool@0
169 | inputs:
170 | versionSpec: $(node_version)
171 | displayName: 'Install Node.js $(node_version)'
172 | - task: Npm@1
173 | inputs:
174 | command: custom
175 | customCommand: i -g npm@latest
176 | displayName: 'Install latest NPM'
177 | - script: |
178 | node -v
179 | npm -v
180 | displayName: 'Print versions'
181 | - task: CacheBeta@1
182 | inputs:
183 | key: npm | $(Agent.OS) | package-lock.json
184 | path: $(npm_config_cache)
185 | displayName: 'Cache npm'
186 | - script: npm ci
187 | displayName: 'Install dependencies'
188 | - script: npm i webpack@$(webpack_version)
189 | displayName: 'Install "webpack@$(webpack_version)"'
190 | - script: npm run test:coverage -- --ci --reporters="default" --reporters="jest-junit" || $(continue_on_error)
191 | displayName: 'Run tests with coverage'
192 | - task: PublishTestResults@2
193 | inputs:
194 | testRunTitle: 'Linux with Node.js $(node_version)'
195 | testResultsFiles: '**/junit.xml'
196 | condition: succeededOrFailed()
197 | displayName: 'Publish test results'
198 | - script: curl -s https://codecov.io/bash | bash -s -- -t $(CODECOV_TOKEN)
199 | condition: succeededOrFailed()
200 | displayName: 'Submit coverage data to codecov'
201 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | const MIN_BABEL_VERSION = 7;
2 |
3 | module.exports = (api) => {
4 | api.assertVersion(MIN_BABEL_VERSION);
5 | api.cache(true);
6 |
7 | return {
8 | presets: [
9 | [
10 | '@babel/preset-env',
11 | {
12 | targets: {
13 | node: '10.13.0',
14 | },
15 | },
16 | ],
17 | ],
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | };
4 |
--------------------------------------------------------------------------------
/husky.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | hooks: {
3 | 'pre-commit': 'lint-staged',
4 | 'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS',
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/lint-staged.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | '*.js': ['prettier --write', 'eslint --fix', 'git add'],
3 | '*.{json,md,yml,css,ts}': ['prettier --write', 'git add'],
4 | };
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "img-optimize-loader",
3 | "version": "1.0.7",
4 | "description": "Image webpack loader. Minify image, compress image, encode image(eg: base64) and inline image automaticlly. Support PNG, JPG, JPEG, GIF, WEBP, SVG.",
5 | "license": "MIT",
6 | "repository": "GaoYYYang/image-optimize-loader",
7 | "author": "GaoYYYang",
8 | "homepage": "https://github.com/GaoYYYang/image-optimize-loader",
9 | "bugs": "https://github.com/GaoYYYang/image-optimize-loader/issues",
10 | "main": "dist/index.js",
11 | "engines": {
12 | "node": ">= 10.13.0"
13 | },
14 | "scripts": {
15 | "start": "npm run build -- -w",
16 | "clean": "del-cli dist",
17 | "prebuild": "npm run clean",
18 | "build": "cross-env NODE_ENV=production babel src -d dist --copy-files",
19 | "commitlint": "commitlint --from=master",
20 | "security": "npm audit",
21 | "lint:prettier": "prettier \"{**/*,*}.{js,json,md,yml,css}\" --write --list-different",
22 | "lint:js": "eslint --cache .",
23 | "lint": "npm-run-all -l -p \"lint:**\"",
24 | "test:only": "cross-env NODE_ENV=test jest",
25 | "test:watch": "npm run test:only -- --watch",
26 | "test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage",
27 | "test": "npm run test:coverage",
28 | "prepare": "npm run build",
29 | "release": "standard-version",
30 | "defaults": "webpack-defaults"
31 | },
32 | "files": ["dist"],
33 | "peerDependencies": {
34 | "webpack": "^4.0.0 || ^5.0.0",
35 | "file-loader": "*"
36 | },
37 | "dependencies": {
38 | "imagemin": "^7.0.1",
39 | "loader-utils": "^2.0.0",
40 | "mime-types": "^2.1.27",
41 | "schema-utils": "^2.7.0"
42 | },
43 | "optionalDependencies": {
44 | "imagemin-gifsicle": "^6.0.1",
45 | "imagemin-mozjpeg": "^8.0.0",
46 | "imagemin-optipng": "^7.1.0",
47 | "imagemin-pngquant": "^8.0.0",
48 | "imagemin-svgo": "^7.0.0",
49 | "imagemin-webp": "^5.1.0"
50 | },
51 | "devDependencies": {
52 | "@babel/cli": "^7.10.3",
53 | "@babel/core": "^7.10.3",
54 | "@babel/preset-env": "^7.10.3",
55 | "@commitlint/cli": "^9.0.1",
56 | "@commitlint/config-conventional": "^9.0.1",
57 | "@webpack-contrib/defaults": "^6.3.0",
58 | "@webpack-contrib/eslint-config-webpack": "^3.0.0",
59 | "babel-jest": "^26.1.0",
60 | "commitlint-azure-pipelines-cli": "^1.0.3",
61 | "cross-env": "^7.0.2",
62 | "del": "^5.1.0",
63 | "del-cli": "^3.0.1",
64 | "eslint": "^7.3.1",
65 | "eslint-config-prettier": "^6.11.0",
66 | "eslint-plugin-import": "^2.22.0",
67 | "husky": "^4.2.5",
68 | "jest": "^26.1.0",
69 | "jest-junit": "^11.0.1",
70 | "lint-staged": "^10.2.11",
71 | "memfs": "^3.2.0",
72 | "npm-run-all": "^4.1.5",
73 | "prettier": "^2.0.5",
74 | "standard-version": "^8.0.0",
75 | "webpack": "^4.43.0"
76 | },
77 | "keywords": ["webpack image loader", "minify image", "compress image", "inline image", "encode image"]
78 | }
79 |
--------------------------------------------------------------------------------
/src/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | .DS_Store
3 | *.log
4 | .idea
5 | .vscode
6 | .eslintcache
7 | /compress
8 | /test
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file: constants
3 | * @author: GaoYYYang
4 | * @date: 06 30 2020 5:38:34
5 | */
6 |
7 | const DEFAULT_INLINE_SYMBOL = '__inline';
8 | const DEFAULT_ANTI_INLINE_SYMBOL = '__antiInline';
9 | const DEFAULT_INLINE_LIMIT = 5000;
10 | const DEFAULT_INLINE_ENCODING = 'base64';
11 |
12 | const DEFAULT_ES_MODULE = false;
13 | const DEFAULT_NAME = 'imgs/[name].[contenthash:8].[ext]';
14 |
15 | const DEFAULT_INLINE_OPTION = {
16 | disable: false,
17 | symbol: DEFAULT_INLINE_SYMBOL,
18 | antiSymbol: DEFAULT_ANTI_INLINE_SYMBOL,
19 | limit: DEFAULT_INLINE_LIMIT,
20 | encoding: DEFAULT_INLINE_ENCODING,
21 | };
22 |
23 | const LOSSY_LOW_COMPRESS_OPTION = {
24 | disable: false,
25 | disableOnDevelopment: true,
26 | // default optimizers
27 | optipng: {
28 | optimizationLevel: 2,
29 | },
30 |
31 | svgo: true,
32 |
33 | pngquant: {
34 | quality: [0.5, 0.8],
35 | },
36 | gifsicle: {
37 | optimizationLevel: 2,
38 | },
39 | mozjpeg: {
40 | progressive: true,
41 | quality: 80,
42 | },
43 | };
44 | const LOSSY_HIGH_COMPRESS_OPTION = {
45 | disable: false,
46 | disableOnDevelopment: true,
47 | // default optimizers
48 | optipng: {
49 | optimizationLevel: 4,
50 | },
51 |
52 | svgo: true,
53 |
54 | pngquant: {
55 | quality: [0.2, 0.8],
56 | },
57 | gifsicle: {
58 | optimizationLevel: 3,
59 | },
60 | mozjpeg: {
61 | progressive: true,
62 | quality: 60,
63 | },
64 | };
65 |
66 | const LOSELESS_COMPRESS_OPTION = {
67 | disable: false,
68 | disableOnDevelopment: true,
69 | // default optimizers
70 | optipng: {
71 | optimizationLevel: 2,
72 | },
73 |
74 | svgo: true,
75 | };
76 |
77 | export {
78 | DEFAULT_INLINE_OPTION,
79 | LOSSY_HIGH_COMPRESS_OPTION,
80 | LOSSY_LOW_COMPRESS_OPTION,
81 | LOSELESS_COMPRESS_OPTION,
82 | DEFAULT_ES_MODULE,
83 | DEFAULT_NAME,
84 | };
85 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file: main entry
3 | * @author: GaoYYYang
4 | * @date: 06 30 2020 4:51:22
5 | */
6 |
7 | import path from 'path';
8 |
9 | import validateOptions from 'schema-utils';
10 | import { getOptions, parseQuery } from 'loader-utils';
11 |
12 | import compress from './lib/compress';
13 | import inline from './lib/inline';
14 | import emit from './lib/emit';
15 |
16 | import {
17 | DEFAULT_INLINE_OPTION,
18 | LOSSY_HIGH_COMPRESS_OPTION,
19 | LOSSY_LOW_COMPRESS_OPTION,
20 | LOSELESS_COMPRESS_OPTION,
21 | DEFAULT_ES_MODULE,
22 | DEFAULT_NAME
23 | } from './constants';
24 | import schema from './options.json';
25 |
26 | function checkNeedInline(option, size) {
27 | if (option.disable) {
28 | return false;
29 | }
30 |
31 | const { limit, symbol, antiSymbol } = option;
32 | const { resourceQuery } = this;
33 | const query = (resourceQuery && parseQuery(resourceQuery)) || {};
34 |
35 | if (query[symbol]) {
36 | return true;
37 | }
38 |
39 | if (query[antiSymbol]) {
40 | return false;
41 | }
42 |
43 | if (typeof limit === 'boolean') {
44 | return limit;
45 | }
46 |
47 | if (typeof limit === 'string') {
48 | return size <= parseInt(limit, 10);
49 | }
50 |
51 | if (typeof limit === 'number') {
52 | return size <= limit;
53 | }
54 |
55 | return true;
56 | }
57 |
58 | function inlineOrEmit(options, data, callback) {
59 | if (checkNeedInline.call(this, options, data.length)) {
60 | inline.call(this, data, options, callback);
61 | } else {
62 | emit.call(this, data, options, callback);
63 | }
64 | }
65 |
66 | export default function loader(source) {
67 | const options = getOptions(this) || {};
68 | const { mode, resourcePath } = this;
69 | const ext = path.extname(resourcePath);
70 | const { outputPath, publicPath, postTransformPublicPath, context, emitFile } = options;
71 | const esModule = options.esModule || DEFAULT_ES_MODULE;
72 | let name = options.name || DEFAULT_NAME;
73 | if (options.compress && options.compress.webp && /(png|jpe?g)$/i.test(ext)) {
74 | name = name.replace('[ext]', 'webp');
75 | }
76 | validateOptions(schema, options, 'image-optimize-loader');
77 |
78 | let compressOption = LOSSY_LOW_COMPRESS_OPTION;
79 | if (options.compress) {
80 | if (options.compress.mode === 'high') {
81 | compressOption = Object.assign(LOSSY_HIGH_COMPRESS_OPTION, options.compress);
82 | } else if (options.compress.mode === 'loseless') {
83 | compressOption = Object.assign(LOSELESS_COMPRESS_OPTION, options.compress);
84 | } else {
85 | compressOption = Object.assign(compressOption, options.compress);
86 | }
87 | }
88 |
89 | let inlineOption = DEFAULT_INLINE_OPTION;
90 | if (options.inline) {
91 | inlineOption = Object.assign(inlineOption, options.inline);
92 | }
93 |
94 | if (mode === 'production' || compressOption.disableOnDevelopment === false || !compressOption.disable) {
95 | const callback = this.async();
96 | compress(source, compressOption)
97 | .then(data => {
98 | inlineOrEmit.call(
99 | this,
100 | { ...inlineOption, esModule, name, outputPath, publicPath, postTransformPublicPath, context, emitFile },
101 | data,
102 | callback
103 | );
104 | })
105 | .catch(err => {
106 | callback(err);
107 | });
108 | } else {
109 | inlineOrEmit.call(
110 | this,
111 | { ...inlineOption, esModule, name, outputPath, publicPath, postTransformPublicPath, context, emitFile },
112 | source,
113 | this.callback
114 | );
115 | }
116 | }
117 |
118 | module.exports.raw = true;
119 |
--------------------------------------------------------------------------------
/src/lib/compress.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file: use imagemin to compress images
3 | * @author: GaoYYYang
4 | * @date: 06 30 2020 4:49:54
5 | */
6 |
7 | import imagemin from 'imagemin';
8 |
9 | export default function compress(content, options) {
10 | const plugins = [];
11 | Object.keys(options).forEach((type) => {
12 | let thisOption = options[type];
13 | if (typeof thisOption === 'boolean' && thisOption) {
14 | thisOption = {};
15 | } else if (typeof thisOption !== 'object') {
16 | thisOption = false;
17 | }
18 | if (thisOption) {
19 | try {
20 | /* eslint-disable global-require */
21 | /* eslint-disable import/no-dynamic-require */
22 | plugins.push(require(`imagemin-${type}`)(thisOption));
23 | } catch (e) {
24 | /* jump when require failed */
25 | }
26 | }
27 | });
28 |
29 | return imagemin.buffer(content, {
30 | plugins,
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/src/lib/emit.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file: use file-loader to emit files
3 | * @author: GaoYYYang
4 | * @date: 06 30 2020 3:55:59
5 | */
6 |
7 | /* eslint-disable import/no-unresolved */
8 | const fileLoader = require('file-loader');
9 |
10 | export default function emit(content, options, callback) {
11 | const { name, esModule, outputPath, publicPath, postTransformPublicPath, context, emitFile } = options;
12 | const fallbackLoaderContext = Object.assign({}, this, {
13 | query: {
14 | esModule,
15 | name,
16 | outputPath,
17 | publicPath,
18 | postTransformPublicPath,
19 | context,
20 | emitFile,
21 | },
22 | });
23 |
24 | callback(null, fileLoader.call(fallbackLoaderContext, content));
25 | }
26 |
--------------------------------------------------------------------------------
/src/lib/inline.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @file: convert buffer to encoding format
3 | * @author: GaoYYYang
4 | * @date: 06 30 2020 4:43:36
5 | */
6 |
7 | import path from 'path';
8 |
9 | import mime from 'mime-types';
10 |
11 | function getMimetype(mimetype, resourcePath) {
12 | if (typeof mimetype === 'boolean') {
13 | if (mimetype) {
14 | const resolvedMimeType = mime.contentType(path.extname(resourcePath));
15 |
16 | if (!resolvedMimeType) {
17 | return '';
18 | }
19 |
20 | return resolvedMimeType.replace(/;\s+charset/i, ';charset');
21 | }
22 |
23 | return '';
24 | }
25 |
26 | if (typeof mimetype === 'string') {
27 | return mimetype;
28 | }
29 |
30 | const resolvedMimeType = mime.contentType(path.extname(resourcePath));
31 |
32 | if (!resolvedMimeType) {
33 | return '';
34 | }
35 |
36 | return resolvedMimeType.replace(/;\s+charset/i, ';charset');
37 | }
38 |
39 | function getEncoding(encoding) {
40 | if (typeof encoding === 'boolean') {
41 | return encoding ? 'base64' : '';
42 | }
43 |
44 | if (typeof encoding === 'string') {
45 | return encoding;
46 | }
47 |
48 | return 'base64';
49 | }
50 |
51 | function getEncodedData(generator, mimetype, encoding, content, resourcePath) {
52 | if (generator) {
53 | return generator(content, mimetype, encoding, resourcePath);
54 | }
55 |
56 | return `data:${mimetype}${encoding ? `;${encoding}` : ''},${content.toString(
57 | encoding
58 | )}`;
59 | }
60 |
61 | export default function inline(data, inlineOption, callback) {
62 | const { generator, esModule } = inlineOption;
63 | let { mimetype, encoding } = inlineOption;
64 | const { resourcePath } = this;
65 |
66 | mimetype = getMimetype(inlineOption.mimetype, resourcePath);
67 | encoding = getEncoding(inlineOption.encoding);
68 |
69 | let content = data;
70 | if (typeof content === 'string') {
71 | // eslint-disable-next-line no-param-reassign
72 | content = Buffer.from(content);
73 | }
74 | content = getEncodedData(generator, mimetype, encoding, data, resourcePath);
75 |
76 | callback(
77 | null,
78 | `${esModule ? 'export default' : 'module.exports ='} ${JSON.stringify(
79 | content
80 | )}`
81 | );
82 | }
83 |
--------------------------------------------------------------------------------
/src/options.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "object",
3 | "properties": {
4 | "name": {
5 | "type": "string",
6 | "description": "To specify the image file output path."
7 | },
8 | "outputPath": {
9 | "description": "A filesystem path where the target file(s) will be placed (https://github.com/webpack-contrib/file-loader#outputpath).",
10 | "anyOf": [
11 | {
12 | "type": "string"
13 | },
14 | {
15 | "instanceof": "Function"
16 | }
17 | ]
18 | },
19 | "emitFile": {
20 | "description": "Enables/Disables emit files (https://github.com/webpack-contrib/file-loader#emitfile).",
21 | "type": "boolean"
22 | },
23 | "publicPath": {
24 | "description": "A custom public path for the target file(s) (https://github.com/webpack-contrib/file-loader#publicpath).",
25 | "anyOf": [
26 | {
27 | "type": "string"
28 | },
29 | {
30 | "instanceof": "Function"
31 | }
32 | ]
33 | },
34 | "postTransformPublicPath": {
35 | "description": "A custom transformation function for post-processing the publicPath (https://github.com/webpack-contrib/file-loader#posttransformpublicpath).",
36 | "instanceof": "Function"
37 | },
38 | "context": {
39 | "description": "A custom file context (https://github.com/webpack-contrib/file-loader#context).",
40 | "type": "string"
41 | },
42 | "esModule": {
43 | "type": "boolean"
44 | },
45 | "inline": {
46 | "type": "object",
47 | "properties": {
48 | "symbol": {
49 | "type": "string",
50 | "description": "Symboled image will be base64 encoded. Default with __inline"
51 | },
52 | "antiSymbol": {
53 | "type": "string",
54 | "description": "Symboled image will be base64 encoded. Default with __antiInline"
55 | },
56 | "limit": {
57 | "description": "Enables/Disables transformation target file into base64 URIs. Default with 5000",
58 | "type": ["boolean", "number", "string"]
59 | },
60 | "encoding": {
61 | "description": "Specify the encoding which the file will be inlined with. Default with base64",
62 | "oneOf": [
63 | {
64 | "type": "boolean"
65 | },
66 | {
67 | "enum": ["utf8", "utf16le", "latin1", "base64", "hex", "ascii", "binary", "ucs2"]
68 | }
69 | ]
70 | },
71 | "mimetype": {
72 | "description": "The MIME type for the file to be transformed.",
73 | "oneOf": [
74 | {
75 | "type": "boolean"
76 | },
77 | {
78 | "type": "string"
79 | }
80 | ]
81 | },
82 | "generator": {
83 | "description": "Adding custom implementation for encoding files.",
84 | "instanceof": "Function"
85 | },
86 | "disable": {
87 | "type": "boolean"
88 | }
89 | }
90 | },
91 | "compress": {
92 | "type": "object",
93 | "properties": {
94 | "mode": {
95 | "enum": ["lossless", "high", "low"]
96 | },
97 | "mozjpeg": {
98 | "additionalProperties": true,
99 | "oneOf": [
100 | {
101 | "type": "object"
102 | },
103 | {
104 | "type": "boolean"
105 | }
106 | ]
107 | },
108 | "optipng": {
109 | "additionalProperties": true,
110 | "oneOf": [
111 | {
112 | "type": "object"
113 | },
114 | {
115 | "type": "boolean"
116 | }
117 | ]
118 | },
119 | "pngquant": {
120 | "additionalProperties": true,
121 | "oneOf": [
122 | {
123 | "type": "object"
124 | },
125 | {
126 | "type": "boolean"
127 | }
128 | ]
129 | },
130 | "gifsicle": {
131 | "additionalProperties": true,
132 | "oneOf": [
133 | {
134 | "type": "object"
135 | },
136 | {
137 | "type": "boolean"
138 | }
139 | ]
140 | },
141 | "webp": {
142 | "additionalProperties": true,
143 | "oneOf": [
144 | {
145 | "type": "object"
146 | },
147 | {
148 | "type": "boolean"
149 | }
150 | ]
151 | },
152 | "svgo": {
153 | "additionalProperties": true,
154 | "oneOf": [
155 | {
156 | "type": "object"
157 | },
158 | {
159 | "type": "boolean"
160 | }
161 | ]
162 | },
163 | "disableOnDevelopment": {
164 | "type": "boolean"
165 | },
166 | "disable": {
167 | "type": "boolean"
168 | }
169 | }
170 | }
171 | },
172 | "additionalProperties": false
173 | }
174 |
--------------------------------------------------------------------------------
/test/fixtures/images/jpg/mini.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GaoYYYang/image-optimize-loader/74924b7c1d2cb371f85d494456cf2fa06f7369e6/test/fixtures/images/jpg/mini.jpg
--------------------------------------------------------------------------------
/test/fixtures/images/png/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GaoYYYang/image-optimize-loader/74924b7c1d2cb371f85d494456cf2fa06f7369e6/test/fixtures/images/png/1.png
--------------------------------------------------------------------------------
/test/fixtures/images/png/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GaoYYYang/image-optimize-loader/74924b7c1d2cb371f85d494456cf2fa06f7369e6/test/fixtures/images/png/2.png
--------------------------------------------------------------------------------
/test/fixtures/images/png/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GaoYYYang/image-optimize-loader/74924b7c1d2cb371f85d494456cf2fa06f7369e6/test/fixtures/images/png/3.png
--------------------------------------------------------------------------------
/test/fixtures/index.js:
--------------------------------------------------------------------------------
1 | import minijpg from './images/jpg/mini.jpg';
2 | import png1 from './images/png/1.png';
3 | import png2 from './images/png/2.png';
4 | import png3 from './images/png/3.png';
5 |
--------------------------------------------------------------------------------
/test/helpers/compile.js:
--------------------------------------------------------------------------------
1 | export default (compiler) =>
2 | new Promise((resolve, reject) => {
3 | compiler.run((error, stats) => {
4 | if (error) {
5 | return reject(error);
6 | }
7 |
8 | return resolve(stats);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/test/helpers/execute.js:
--------------------------------------------------------------------------------
1 | import Module from 'module';
2 | import path from 'path';
3 |
4 | const parentModule = module;
5 |
6 | export default (code) => {
7 | const resource = 'test.js';
8 | const module = new Module(resource, parentModule);
9 | // eslint-disable-next-line no-underscore-dangle
10 | module.paths = Module._nodeModulePaths(
11 | path.resolve(__dirname, '../fixtures')
12 | );
13 | module.filename = resource;
14 |
15 | // eslint-disable-next-line no-underscore-dangle
16 | module._compile(
17 | `let __export__;${code};module.exports = __export__;`,
18 | resource
19 | );
20 |
21 | return module.exports;
22 | };
23 |
--------------------------------------------------------------------------------
/test/helpers/getCompiler.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 |
3 | import webpack from 'webpack';
4 | import { createFsFromVolume, Volume } from 'memfs';
5 |
6 | export default (fixture, loaderOptions = {}, config = {}) => {
7 | const fullConfig = {
8 | mode: 'production',
9 | devtool: config.devtool || false,
10 | context: path.resolve(__dirname, '../fixtures'),
11 | entry: { main: path.resolve(__dirname, '../fixtures', fixture) },
12 | output: {
13 | path: path.resolve(__dirname, '../outputs'),
14 | filename: '[name].bundle.js',
15 | chunkFilename: '[name].chunk.js',
16 | },
17 | module: {
18 | rules: [
19 | // {
20 | // test: /\.js$/i,
21 | // rules: [
22 | // {
23 | // loader: path.resolve(__dirname, '../../src'),
24 | // options: loaderOptions || {},
25 | // },
26 | // ],
27 | // },
28 | {
29 | test: /\.(png|jpg)$/i,
30 | rules: [
31 | {
32 | loader: path.resolve(__dirname, '../../dist/index'),
33 | options: loaderOptions || {},
34 | },
35 | ],
36 | },
37 | ],
38 | },
39 | plugins: [],
40 | ...config,
41 | };
42 |
43 | const compiler = webpack(fullConfig);
44 |
45 | if (!config.outputFileSystem) {
46 | const outputFileSystem = createFsFromVolume(new Volume());
47 | // Todo remove when we drop webpack@4 support
48 | outputFileSystem.join = path.join.bind(path);
49 |
50 | compiler.outputFileSystem = outputFileSystem;
51 | }
52 |
53 | return compiler;
54 | };
55 |
--------------------------------------------------------------------------------
/test/helpers/getErrors.js:
--------------------------------------------------------------------------------
1 | import normalizeErrors from './normalizeErrors';
2 |
3 | export default (stats) => {
4 | return normalizeErrors(stats.compilation.errors);
5 | };
6 |
--------------------------------------------------------------------------------
/test/helpers/getWarnings.js:
--------------------------------------------------------------------------------
1 | import normalizeErrors from './normalizeErrors';
2 |
3 | export default (stats) => {
4 | return normalizeErrors(stats.compilation.warnings);
5 | };
6 |
--------------------------------------------------------------------------------
/test/helpers/index.js:
--------------------------------------------------------------------------------
1 | import compile from './compile';
2 | import execute from './execute';
3 | import getCompiler from './getCompiler';
4 | import getErrors from './getErrors';
5 | import getWarnings from './getWarnings';
6 | import normalizeErrors from './normalizeErrors';
7 | import readAsset from './readAsset';
8 | import readsAssets from './readAssets';
9 |
10 | export {
11 | compile,
12 | execute,
13 | getCompiler,
14 | getErrors,
15 | getWarnings,
16 | normalizeErrors,
17 | readAsset,
18 | readsAssets,
19 | };
20 |
--------------------------------------------------------------------------------
/test/helpers/normalizeErrors.js:
--------------------------------------------------------------------------------
1 | function removeCWD(str) {
2 | const isWin = process.platform === 'win32';
3 | let cwd = process.cwd();
4 |
5 | if (isWin) {
6 | // eslint-disable-next-line no-param-reassign
7 | str = str.replace(/\\/g, '/');
8 | // eslint-disable-next-line no-param-reassign
9 | cwd = cwd.replace(/\\/g, '/');
10 | }
11 |
12 | return str.replace(new RegExp(cwd, 'g'), '');
13 | }
14 |
15 | export default (errors) => {
16 | return errors.map((error) =>
17 | removeCWD(error.toString().split('\n').slice(0, 2).join('\n'))
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/test/helpers/readAsset.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 |
3 | export default (asset, compiler, stats) => {
4 | const usedFs = compiler.outputFileSystem;
5 | const outputPath = stats.compilation.outputOptions.path;
6 |
7 | let data = '';
8 | let targetFile = asset;
9 |
10 | const queryStringIdx = targetFile.indexOf('?');
11 |
12 | if (queryStringIdx >= 0) {
13 | targetFile = targetFile.substr(0, queryStringIdx);
14 | }
15 |
16 | try {
17 | data = usedFs.readFileSync(path.join(outputPath, targetFile)).toString();
18 | } catch (error) {
19 | data = error.toString();
20 | }
21 |
22 | return data;
23 | };
24 |
--------------------------------------------------------------------------------
/test/helpers/readAssets.js:
--------------------------------------------------------------------------------
1 | import readAsset from './readAsset';
2 |
3 | export default function readAssets(compiler, stats) {
4 | const assets = {};
5 |
6 | Object.keys(stats.compilation.assets).forEach((asset) => {
7 | assets[asset] = readAsset(asset, compiler, stats);
8 | });
9 |
10 | return assets;
11 | }
12 |
--------------------------------------------------------------------------------
/test/loader.test.js:
--------------------------------------------------------------------------------
1 | import { compile, getCompiler, getErrors, getWarnings, readAsset } from './helpers';
2 |
3 | test('loader', async () => {
4 | const compiler = getCompiler('index.js');
5 | const stats = await compile(compiler);
6 | expect(readAsset('main.bundle.js', compiler, stats)).toBeTruthy();
7 | console.log(getErrors(stats));
8 | console.log(getWarnings(stats));
9 | expect(getErrors(stats).length).toBe(0);
10 | expect(getWarnings(stats).length).toBe(0);
11 | });
12 |
--------------------------------------------------------------------------------