├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .github
└── workflows
│ └── release.yml
├── .gitignore
├── .nycrc
├── .releaserc.json
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── examples
├── index-html
│ ├── app
│ │ ├── hi.jpg
│ │ ├── index.html
│ │ ├── main.css
│ │ └── main.js
│ ├── package.json
│ └── webpack.config.js
└── main-css
│ ├── app
│ └── main.css
│ ├── index.html
│ ├── package.json
│ └── webpack.config.js
├── package-lock.json
├── package.json
├── src
└── extractLoader.js
└── test
├── .eslintrc.json
├── extractLoader.test.js
├── mocha.opts
├── modules
├── deep.css
├── error-resolve-loader.js
├── error-resolve.js
├── error-syntax.js
├── error-to-string.js
├── hi.jpg
├── img.css
├── img.html
├── img.js
├── loader.html
├── simple-css-with-query-params-and-loader.js
├── simple-css-with-query-params.js
├── simple.css
├── simple.html
├── simple.js
└── stylesheet.html
└── support
└── compile.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "targets": {
7 | "node": 6
8 | }
9 | }
10 | ]
11 | ],
12 | "plugins": [
13 | "transform-runtime"
14 | ],
15 | "sourceMaps": true,
16 | "retainLines": true,
17 | "env": {
18 | "test": {
19 | "plugins": ["istanbul"]
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs.
2 | # More information at http://EditorConfig.org
3 |
4 | # No .editorconfig files above the root directory
5 | root = true
6 |
7 | [*]
8 | indent_style = space
9 | indent_size = 4
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 |
14 | [package.json]
15 | indent_size = 2
16 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Compiled by babel
2 | lib
3 |
4 | # Compiled by webpack
5 | test/dist
6 |
7 | # Syntax error on purpose
8 | test/modules/error-syntax.js
9 |
10 | examples
11 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "peerigon/base"
4 | ],
5 | "env": {
6 | "node": true
7 | },
8 | "root": true
9 | }
10 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | push:
4 | branches:
5 | - master
6 | - next
7 | jobs:
8 | prepare:
9 | runs-on: ubuntu-latest
10 | if: "! contains(github.event.head_commit.message, '[skip ci]')"
11 | steps:
12 | - run: echo "${{ github.event.head_commit.message }}"
13 | publish:
14 | needs: prepare
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v1
18 | - run: npm ci --ignore-scripts
19 | - run: npm test
20 | - run: npm run build
21 | - run: npm install @semantic-release/changelog@3 @semantic-release/git@7 --ignore-scripts --no-save
22 | - uses: codfish/semantic-release-action@v1
23 | env:
24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Project specific
2 | lib
3 | test/dist
4 | examples/**/dist
5 |
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 |
11 | # Runtime data
12 | pids
13 | *.pid
14 | *.seed
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
23 | .grunt
24 |
25 | # node-waf configuration
26 | .lock-wscript
27 |
28 | # Compiled binary addons (http://nodejs.org/api/addons.html)
29 | build/Release
30 |
31 | # Dependency directory
32 | node_modules
33 |
34 | # Optional npm cache directory
35 | .npm
36 |
37 | # Optional REPL history
38 | .node_repl_history
39 |
40 | # NYC covergae information
41 | .nyc_output
--------------------------------------------------------------------------------
/.nycrc:
--------------------------------------------------------------------------------
1 | {
2 | "reporter": [
3 | "lcov",
4 | "text"
5 | ],
6 | "include": [
7 | "src"
8 | ],
9 | "lines": 97,
10 | "statements": 97,
11 | "functions": 93,
12 | "branches": 87,
13 | "check-coverage": true,
14 | "sourceMap": false,
15 | "instrument": false
16 | }
--------------------------------------------------------------------------------
/.releaserc.json:
--------------------------------------------------------------------------------
1 | {
2 | "branches": ["master", "next"],
3 | "plugins": [
4 | "@semantic-release/commit-analyzer",
5 | "@semantic-release/release-notes-generator",
6 | "@semantic-release/changelog",
7 | ["@semantic-release/git", {
8 | "assets": ["CHANGELOG.md"]
9 | }],
10 | "@semantic-release/github",
11 | "@semantic-release/npm"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os:
2 | - linux
3 | language: node_js
4 | node_js:
5 | - "node"
6 | - "12"
7 | - "10"
8 | - "8"
9 |
10 | script:
11 | - npm test
12 |
13 | after_success:
14 | - npm install coveralls
15 | - npm run coverage && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage
16 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # [5.1.0](https://github.com/peerigon/extract-loader/compare/v5.0.1...v5.1.0) (2020-05-26)
2 |
3 |
4 | ### Features
5 |
6 | * unique placeholders for each match ([#83](https://github.com/peerigon/extract-loader/issues/83)) ([5e61f0c](https://github.com/peerigon/extract-loader/commit/5e61f0c345763d18d3f1be3622cabc86dac8077d))
7 |
8 | # Change Log
9 |
10 | 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.
11 |
12 |
13 | # [3.2.0](https://github.com/peerigon/extract-loader/compare/v5.0.1...v3.2.0) (2020-05-26)
14 |
15 |
16 | ### Features
17 |
18 | * unique placeholders for each match ([#83](https://github.com/peerigon/extract-loader/issues/83)) ([5e61f0c](https://github.com/peerigon/extract-loader/commit/5e61f0c))
19 |
20 |
21 |
22 |
23 | # [3.1.0](https://github.com/peerigon/extract-loader/compare/v3.0.0...v3.1.0) (2018-11-26)
24 |
25 |
26 | ### Features
27 |
28 | * Accept function as publicPath option ([#51](https://github.com/peerigon/extract-loader/issues/51)) ([678933e](https://github.com/peerigon/extract-loader/commit/678933e))
29 |
30 |
31 |
32 |
33 | # [3.0.0](https://github.com/peerigon/extract-loader/compare/v2.0.1...v3.0.0) (2018-08-31)
34 |
35 |
36 | ### Features
37 |
38 | * Add source map support ([#43](https://github.com/peerigon/extract-loader/issues/43)) ([8f56c2f](https://github.com/peerigon/extract-loader/commit/8f56c2f)), closes [#1](https://github.com/peerigon/extract-loader/issues/1)
39 | * Enable deep evaluation of dependency graph ([#42](https://github.com/peerigon/extract-loader/issues/42)) ([c5aff66](https://github.com/peerigon/extract-loader/commit/c5aff66))
40 |
41 |
42 | ### BREAKING CHANGES
43 |
44 | * Although the change is not breaking according to our tests, we assume that there could be problems in certain projects.
45 |
46 |
47 |
48 |
49 | ## [2.0.1](https://github.com/peerigon/extract-loader/compare/v2.0.0...v2.0.1) (2018-03-20)
50 |
51 | Re-Release, because v2.0.0 was missing the `lib/extractLoader.js` file [#37](https://github.com/peerigon/extract-loader/issues/37)
52 |
53 | ### Bug Fixes
54 | * Update package.json `engines` field to properly state Node.js 6+ support
55 |
56 |
57 |
58 | # [2.0.0](https://github.com/peerigon/extract-loader/compare/v1.0.2...v2.0.0) (2018-03-19)
59 |
60 | ### Features
61 |
62 | * Add support for webpack 4 ([77f1a670eea87a7adea05cf66a4d54b2995be0e6](https://github.com/peerigon/extract-loader/commit/77f1a670eea87a7adea05cf66a4d54b2995be0e6))
63 |
64 | ### Bug Fixes
65 |
66 | * TypeError require(...) is not a function ([050f189](https://github.com/peerigon/extract-loader/commit/050f189))
67 |
68 | ### BREAKING CHANGES
69 |
70 | * extract-loader does now officially only support node >= 6. No guarantee for older node versions.
71 |
72 |
73 |
74 | ## [1.0.2](https://github.com/peerigon/extract-loader/compare/v1.0.1...v1.0.2) (2018-01-11)
75 |
76 |
77 |
78 | ## [1.0.1](https://github.com/peerigon/extract-loader/compare/v1.0.0...v1.0.1) (2017-08-19)
79 |
80 | ### Bug Fixes
81 |
82 | * Fix problems with aliased paths ([f5a1946a7b54ef962e5af56aaf29d318efaabf66](https://github.com/peerigon/extract-loader/commit/f5a1946a7b54ef962e5af56aaf29d318efaabf66))
83 |
84 |
85 |
86 | # [1.0.0](https://github.com/peerigon/extract-loader/compare/v0.1.0...v1.0.0) (2017-05-24)
87 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | extract-loader
2 | ==============
3 | **webpack loader to extract HTML and CSS from the bundle.**
4 |
5 | [](https://www.npmjs.com/package/extract-loader)
6 | [](https://www.npmjs.com/package/extract-loader)
7 | [](https://david-dm.org/peerigon/extract-loader)
8 | [](https://travis-ci.org/peerigon/extract-loader)
9 | [](https://coveralls.io/r/peerigon/extract-loader?branch=master)
10 |
11 | The extract-loader evaluates the given source code on the fly and returns the result as string. Its main use-case is to resolve urls within HTML and CSS coming from their respective loaders. Use the [file-loader](https://github.com/webpack/file-loader) to emit the extract-loader's result as separate file.
12 |
13 | ```javascript
14 | import stylesheetUrl from "file-loader!extract-loader!css-loader!main.css";
15 | // stylesheetUrl will now be the hashed url to the final stylesheet
16 | ```
17 |
18 | The extract-loader works similar to the [extract-text-webpack-plugin](https://github.com/webpack/extract-text-webpack-plugin) and the [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) and is meant as a lean alternative to it. When evaluating the source code, it provides a fake context which was especially designed to cope with the code generated by the [html-](https://github.com/webpack/html-loader) or the [css-loader](https://github.com/webpack/css-loader). Thus it might not work in other situations.
19 |
20 |
21 |
22 | Installation
23 | ------------------------------------------------------------------------
24 |
25 | ```bash
26 | $ npm install extract-loader --save-dev
27 | ```
28 |
29 |
30 |
31 | Examples
32 | ------------------------------------------------------------------------
33 |
34 | ### [Extracting a main.css](https://github.com/peerigon/extract-loader/tree/master/examples/main-css)
35 |
36 | Bundling CSS with webpack has some nice advantages like referencing images and fonts with hashed urls or [hot module replacement](https://webpack.js.org/concepts/hot-module-replacement) in development. In production, on the other hand, it's not a good idea to apply your stylesheets depending on JS execution. Rendering may be delayed or even a [FOUC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content) might be visible. Thus it's still better to have them as separate files in your final production build.
37 |
38 | With the extract-loader, you are able to reference your `main.css` as regular `entry`. The following `webpack.config.js` shows how to load your styles with the [style-loader](https://github.com/webpack/style-loader) in development and as separate file in production.
39 |
40 | ```js
41 | module.exports = ({ mode }) => {
42 | const pathToMainCss = require.resolve("./app/main.css");
43 | const loaders = [{
44 | loader: "css-loader",
45 | options: {
46 | sourceMap: true
47 | }
48 | }];
49 |
50 | if (mode === "production") {
51 | loaders.unshift(
52 | "file-loader",
53 | "extract-loader"
54 | );
55 | } else {
56 | loaders.unshift("style-loader");
57 | }
58 |
59 | return {
60 | mode,
61 | entry: pathToMainCss,
62 | module: {
63 | rules: [
64 | {
65 | test: pathToMainCss,
66 | loaders: loaders
67 | },
68 | ]
69 | }
70 | };
71 | };
72 | ```
73 |
74 | ### [Extracting the index.html](https://github.com/peerigon/extract-loader/tree/master/examples/index-html)
75 |
76 | You can even add your `index.html` as `entry` and reference your stylesheets from there. In that case, tell the html-loader to also pick up `link:href`:
77 |
78 | ```js
79 | module.exports = ({ mode }) => {
80 | const pathToMainJs = require.resolve("./app/main.js");
81 | const pathToIndexHtml = require.resolve("./app/index.html");
82 |
83 | return {
84 | mode,
85 | entry: [
86 | pathToMainJs,
87 | pathToIndexHtml
88 | ],
89 | module: {
90 | rules: [
91 | {
92 | test: pathToIndexHtml,
93 | use: [
94 | "file-loader",
95 | "extract-loader",
96 | {
97 | loader: "html-loader",
98 | options: {
99 | attrs: ["img:src", "link:href"]
100 | }
101 | }
102 | ]
103 | },
104 | {
105 | test: /\.css$/,
106 | use: [
107 | "file-loader",
108 | "extract-loader",
109 | {
110 | loader: "css-loader",
111 | options: {
112 | sourceMap: true
113 | }
114 | }
115 | ]
116 | },
117 | {
118 | test: /\.jpg$/,
119 | use: "file-loader"
120 | }
121 | ]
122 | }
123 | };
124 | }
125 | ```
126 |
127 | turns
128 |
129 | ```html
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | ```
139 |
140 | into
141 |
142 |
143 | ```html
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | ```
153 |
154 |
155 |
156 | Source Maps
157 | ------------------------------------------------------------------------
158 |
159 | If you want source maps in your extracted CSS files, you need to set the [`sourceMap` option](https://github.com/webpack-contrib/css-loader#sourcemap) of the **css-loader**:
160 |
161 | ```js
162 | {
163 | loader: "css-loader",
164 | options: {
165 | sourceMap: true
166 | }
167 | }
168 | ```
169 |
170 |
171 |
172 | Options
173 | ------------------------------------------------------------------------
174 |
175 | There is currently exactly one option: `publicPath`.
176 | If you are using a relative `publicPath` in webpack's [output options](https://webpack.js.org/configuration/output/#output-publicpath) and extracting to a file with the `file-loader`, you might need this to account for the location of your extracted file. `publicPath` may be defined as a string or a function that accepts current [loader context](https://webpack.js.org/api/loaders/#the-loader-context) as single argument.
177 |
178 | Example with publicPath option as a string:
179 |
180 | ```js
181 | module.exports = {
182 | output: {
183 | path: path.resolve("./dist"),
184 | publicPath: "dist/"
185 | },
186 | module: {
187 | rules: [
188 | {
189 | test: /\.css$/,
190 | use: [
191 | {
192 | loader: "file-loader",
193 | options: {
194 | name: "assets/[name].[ext]",
195 | },
196 | },
197 | {
198 | loader: "extract-loader",
199 | options: {
200 | publicPath: "../",
201 | }
202 | },
203 | {
204 | loader: "css-loader",
205 | },
206 | ],
207 | }
208 | ]
209 | }
210 | };
211 | ```
212 |
213 | Example with publicPath option as a function:
214 |
215 | ```js
216 | module.exports = {
217 | output: {
218 | path: path.resolve("./dist"),
219 | publicPath: "dist/"
220 | },
221 | module: {
222 | rules: [
223 | {
224 | test: /\.css$/,
225 | use: [
226 | {
227 | loader: "file-loader",
228 | options: {
229 | name: "assets/[name].[ext]",
230 | },
231 | },
232 | {
233 | loader: "extract-loader",
234 | options: {
235 | // dynamically return a relative publicPath based on how deep in directory structure the loaded file is in /src/ directory
236 | publicPath: (context) => '../'.repeat(path.relative(path.resolve('src'), context.context).split('/').length),
237 | }
238 | },
239 | {
240 | loader: "css-loader",
241 | },
242 | ],
243 | }
244 | ]
245 | }
246 | };
247 | ```
248 |
249 | You need another option? Then you should think about:
250 |
251 |
252 |
253 | Contributing
254 | ------------------------------------------------------------------------
255 |
256 | From opening a bug report to creating a pull request: **every contribution is appreciated and welcome**. If you're planning to implement a new feature or change the api please create an issue first. This way we can ensure that your precious work is not in vain.
257 |
258 | All pull requests should have 100% test coverage (with notable exceptions) and need to pass all tests.
259 |
260 | - Call `npm test` to run the unit tests
261 | - Call `npm run coverage` to check the test coverage (using [istanbul](https://github.com/gotwarlost/istanbul))
262 |
263 |
264 |
265 | License
266 | ------------------------------------------------------------------------
267 |
268 | Unlicense
269 |
270 | Sponsors
271 | ------------------------------------------------------------------------
272 |
273 | [
](https://peerigon.com)
274 |
--------------------------------------------------------------------------------
/examples/index-html/app/hi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peerigon/extract-loader/85008407e266ef7d7513d10a68ae9d03bddff7b8/examples/index-html/app/hi.jpg
--------------------------------------------------------------------------------
/examples/index-html/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/index-html/app/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: url(hi.jpg);
3 | }
4 |
--------------------------------------------------------------------------------
/examples/index-html/app/main.js:
--------------------------------------------------------------------------------
1 | console.log("hi");
2 |
--------------------------------------------------------------------------------
/examples/index-html/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "index-html",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "webpack.config.js",
6 | "scripts": {
7 | "build": "../../node_modules/.bin/webpack --mode production"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC"
12 | }
13 |
--------------------------------------------------------------------------------
/examples/index-html/webpack.config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = ({ mode }) => {
4 | const pathToMainJs = require.resolve("./app/main.js");
5 | const pathToIndexHtml = require.resolve("./app/index.html");
6 |
7 | return {
8 | mode,
9 | entry: [
10 | pathToMainJs,
11 | pathToIndexHtml
12 | ],
13 | module: {
14 | rules: [
15 | {
16 | test: pathToIndexHtml,
17 | use: [
18 | "file-loader",
19 | // should be just "extract-loader" in your case
20 | require.resolve("../../lib/extractLoader.js"),
21 | {
22 | loader: "html-loader",
23 | options: {
24 | attrs: ["img:src", "link:href"]
25 | }
26 | }
27 | ]
28 | },
29 | {
30 | test: /\.css$/,
31 | use: [
32 | "file-loader",
33 | // should be just "extract-loader" in your case
34 | require.resolve("../../lib/extractLoader.js"),
35 | {
36 | loader: "css-loader",
37 | options: {
38 | sourceMap: true
39 | }
40 | }
41 | ]
42 | },
43 | {
44 | test: /\.jpg$/,
45 | use: "file-loader"
46 | }
47 | ]
48 | }
49 | };
50 | };
51 |
--------------------------------------------------------------------------------
/examples/main-css/app/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: hotpink;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/main-css/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Main CSS Example
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/main-css/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "main-css",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "webpack.config.js",
6 | "scripts": {
7 | "build": "../../node_modules/.bin/webpack --mode production"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC"
12 | }
13 |
--------------------------------------------------------------------------------
/examples/main-css/webpack.config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = ({ mode }) => {
4 | const pathToMainCss = require.resolve("./app/main.css");
5 | const loaders = [{
6 | loader: "css-loader",
7 | options: {
8 | sourceMap: true
9 | }
10 | }];
11 |
12 | if (mode === "production") {
13 | loaders.unshift(
14 | "file-loader",
15 | // should be just "extract-loader" in your case
16 | require.resolve("../../lib/extractLoader.js"),
17 | );
18 | } else {
19 | loaders.unshift("style-loader");
20 | }
21 |
22 | return {
23 | mode,
24 | entry: pathToMainCss,
25 | module: {
26 | rules: [
27 | {
28 | test: pathToMainCss,
29 | loaders: loaders
30 | },
31 | ]
32 | }
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "extract-loader",
3 | "version": "3.2.0",
4 | "description": "webpack loader to extract HTML and CSS from the bundle",
5 | "main": "lib/extractLoader.js",
6 | "scripts": {
7 | "build": "babel src -d lib",
8 | "test": "cross-env NODE_ENV=test nyc mocha -R spec",
9 | "posttest": "eslint src test",
10 | "release": "standard-version",
11 | "prepublishOnly": "npm run build"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/peerigon/extract-loader.git"
16 | },
17 | "keywords": [
18 | "webpack",
19 | "loader",
20 | "extract",
21 | "html",
22 | "css"
23 | ],
24 | "author": "peerigon ",
25 | "license": "Unlicense",
26 | "bugs": {
27 | "url": "https://github.com/peerigon/extract-loader/issues"
28 | },
29 | "homepage": "https://github.com/peerigon/extract-loader#readme",
30 | "engines": {
31 | "node": ">= 6.0.0"
32 | },
33 | "devDependencies": {
34 | "babel-cli": "^6.26.0",
35 | "babel-plugin-istanbul": "^5.0.1",
36 | "babel-plugin-transform-runtime": "^6.23.0",
37 | "babel-register": "^6.26.0",
38 | "chai": "^4.1.0",
39 | "chai-fs": "^2.0.0",
40 | "cross-env": "^5.2.0",
41 | "css-loader": "^1.0.0",
42 | "eslint": "^5.4.0",
43 | "eslint-config-peerigon": "^15.0.2",
44 | "file-loader": "^5.0.2",
45 | "html-loader": "^0.5.5",
46 | "mocha": "^5.2.0",
47 | "nyc": "^13.0.1",
48 | "rimraf": "^2.6.2",
49 | "standard-version": "^4.4.0",
50 | "style-loader": "^0.23.0",
51 | "webpack": "^4.17.1",
52 | "webpack-command": "^0.4.1"
53 | },
54 | "dependencies": {
55 | "babel-core": "^6.26.3",
56 | "babel-plugin-add-module-exports": "^1.0.2",
57 | "babel-preset-env": "^1.7.0",
58 | "babel-runtime": "^6.26.0",
59 | "btoa": "^1.2.1",
60 | "loader-utils": "^1.1.0",
61 | "resolve": "^1.8.1"
62 | },
63 | "files": [
64 | "lib"
65 | ]
66 | }
67 |
--------------------------------------------------------------------------------
/src/extractLoader.js:
--------------------------------------------------------------------------------
1 | import vm from "vm";
2 | import path from "path";
3 | import {getOptions} from "loader-utils";
4 | import resolve from "resolve";
5 | import btoa from "btoa";
6 | import * as babel from "babel-core";
7 |
8 | /**
9 | * @typedef {Object} LoaderContext
10 | * @property {function} cacheable
11 | * @property {function} async
12 | * @property {function} addDependency
13 | * @property {function} loadModule
14 | * @property {string} resourcePath
15 | * @property {object} options
16 | */
17 |
18 | /**
19 | * Executes the given module's src in a fake context in order to get the resulting string.
20 | *
21 | * @this LoaderContext
22 | * @param {string} src
23 | * @throws Error
24 | */
25 | async function extractLoader(src) {
26 | const done = this.async();
27 | const options = getOptions(this) || {};
28 | const publicPath = getPublicPath(options, this);
29 |
30 | this.cacheable();
31 |
32 | try {
33 | done(null, await evalDependencyGraph({
34 | loaderContext: this,
35 | src,
36 | filename: this.resourcePath,
37 | publicPath,
38 | }));
39 | } catch (error) {
40 | done(error);
41 | }
42 | }
43 |
44 | function evalDependencyGraph({loaderContext, src, filename, publicPath = ""}) {
45 | const moduleCache = new Map();
46 |
47 | function loadModule(filename) {
48 | return new Promise((resolve, reject) => {
49 | // loaderContext.loadModule automatically calls loaderContext.addDependency for all requested modules
50 | loaderContext.loadModule(filename, (error, src) => {
51 | if (error) {
52 | reject(error);
53 | } else {
54 | resolve(src);
55 | }
56 | });
57 | });
58 | }
59 |
60 | function extractExports(exports) {
61 | const hasBtoa = "btoa" in global;
62 | const previousBtoa = global.btoa;
63 |
64 | global.btoa = btoa;
65 |
66 | try {
67 | return exports.toString();
68 | } catch (error) {
69 | throw error;
70 | } finally {
71 | if (hasBtoa) {
72 | global.btoa = previousBtoa;
73 | } else {
74 | delete global.btoa;
75 | }
76 | }
77 | }
78 |
79 | function extractQueryFromPath(givenRelativePath) {
80 | const indexOfLastExclMark = givenRelativePath.lastIndexOf("!");
81 | const indexOfQuery = givenRelativePath.lastIndexOf("?");
82 |
83 | if (indexOfQuery !== -1 && indexOfQuery > indexOfLastExclMark) {
84 | return {
85 | relativePathWithoutQuery: givenRelativePath.slice(0, indexOfQuery),
86 | query: givenRelativePath.slice(indexOfQuery),
87 | };
88 | }
89 |
90 | return {
91 | relativePathWithoutQuery: givenRelativePath,
92 | query: "",
93 | };
94 | }
95 |
96 | async function evalModule(src, filename) {
97 | src = babel.transform(src, {
98 | babelrc: false,
99 | presets: [
100 | [
101 | require("babel-preset-env"), {
102 | modules: "commonjs",
103 | targets: {nodejs: "current"},
104 | },
105 | ],
106 | ],
107 | plugins: [require("babel-plugin-add-module-exports")],
108 | }).code;
109 |
110 | const script = new vm.Script(src, {
111 | filename,
112 | displayErrors: true,
113 | });
114 | const newDependencies = [];
115 | const exports = {};
116 | const sandbox = Object.assign({}, global, {
117 | module: {
118 | exports,
119 | },
120 | exports,
121 | __webpack_public_path__: publicPath, // eslint-disable-line camelcase
122 | require: givenRelativePath => {
123 | const {relativePathWithoutQuery, query} = extractQueryFromPath(givenRelativePath);
124 | const indexOfLastExclMark = relativePathWithoutQuery.lastIndexOf("!");
125 | const loaders = givenRelativePath.slice(0, indexOfLastExclMark + 1);
126 | const relativePath = relativePathWithoutQuery.slice(indexOfLastExclMark + 1);
127 | const absolutePath = resolve.sync(relativePath, {
128 | basedir: path.dirname(filename),
129 | });
130 | const ext = path.extname(absolutePath);
131 |
132 | if (moduleCache.has(absolutePath)) {
133 | return moduleCache.get(absolutePath);
134 | }
135 |
136 | // If the required file is a js file, we just require it with node's require.
137 | // If the required file should be processed by a loader we do not touch it (even if it is a .js file).
138 | if (loaders === "" && ext === ".js") {
139 | // Mark the file as dependency so webpack's watcher is working for the css-loader helper.
140 | // Other dependencies are automatically added by loadModule() below
141 | loaderContext.addDependency(absolutePath);
142 |
143 | const exports = require(absolutePath); // eslint-disable-line import/no-dynamic-require
144 |
145 | moduleCache.set(absolutePath, exports);
146 |
147 | return exports;
148 | }
149 |
150 | const rndPlaceholder = "__EXTRACT_LOADER_PLACEHOLDER__" + rndNumber() + rndNumber();
151 |
152 | newDependencies.push({
153 | absolutePath,
154 | absoluteRequest: loaders + absolutePath + query,
155 | rndPlaceholder,
156 | });
157 |
158 | return rndPlaceholder;
159 | },
160 | });
161 |
162 | script.runInNewContext(sandbox);
163 |
164 | const extractedDependencyContent = await Promise.all(
165 | newDependencies.map(async ({absolutePath, absoluteRequest}) => {
166 | const src = await loadModule(absoluteRequest);
167 |
168 | return evalModule(src, absolutePath);
169 | })
170 | );
171 | const contentWithPlaceholders = extractExports(sandbox.module.exports);
172 | const extractedContent = extractedDependencyContent.reduce((content, dependencyContent, idx) => {
173 | const pattern = new RegExp(newDependencies[idx].rndPlaceholder, "g");
174 |
175 | return content.replace(pattern, dependencyContent);
176 | }, contentWithPlaceholders);
177 |
178 | moduleCache.set(filename, extractedContent);
179 |
180 | return extractedContent;
181 | }
182 |
183 | return evalModule(src, filename);
184 | }
185 |
186 | /**
187 | * @returns {string}
188 | */
189 | function rndNumber() {
190 | return Math.random()
191 | .toString()
192 | .slice(2);
193 | }
194 |
195 | // getPublicPath() encapsulates the complexity of reading the publicPath from the current
196 | // webpack config. Let's keep the complexity in this function.
197 | /* eslint-disable complexity */
198 | /**
199 | * Retrieves the public path from the loader options, context.options (webpack <4) or context._compilation (webpack 4+).
200 | * context._compilation is likely to get removed in a future release, so this whole function should be removed then.
201 | * See: https://github.com/peerigon/extract-loader/issues/35
202 | *
203 | * @deprecated
204 | * @param {Object} options - Extract-loader options
205 | * @param {Object} context - Webpack loader context
206 | * @returns {string}
207 | */
208 | function getPublicPath(options, context) {
209 | if ("publicPath" in options) {
210 | return typeof options.publicPath === "function" ? options.publicPath(context) : options.publicPath;
211 | }
212 |
213 | if (context.options && context.options.output && "publicPath" in context.options.output) {
214 | return context.options.output.publicPath;
215 | }
216 |
217 | if (context._compilation && context._compilation.outputOptions && "publicPath" in context._compilation.outputOptions) {
218 | return context._compilation.outputOptions.publicPath;
219 | }
220 |
221 | return "";
222 | }
223 | /* eslint-enable complexity */
224 |
225 | // For CommonJS interoperability
226 | module.exports = extractLoader;
227 | export default extractLoader;
228 |
--------------------------------------------------------------------------------
/test/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "peerigon/tests"
4 | ],
5 | "env": {
6 | "node": true,
7 | "mocha": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/extractLoader.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable promise/always-return, promise/prefer-await-to-then */
2 | import path from "path";
3 | import fs from "fs";
4 | import rimRaf from "rimraf";
5 | import chai, {expect} from "chai";
6 | import chaiFs from "chai-fs";
7 | import extractLoader from "../src/extractLoader";
8 | import compile from "./support/compile";
9 |
10 | chai.use(chaiFs);
11 |
12 | describe("extractLoader", () => {
13 | // Using beforeEach so that we can inspect the test compilation afterwards
14 | beforeEach(() => {
15 | rimRaf.sync(path.resolve(__dirname, "dist"));
16 | });
17 | it("should extract 'hello' into simple.js", () =>
18 | compile({testModule: "simple.js"}).then(() => {
19 | const simpleJs = path.resolve(__dirname, "dist/simple-dist.js");
20 |
21 | expect(simpleJs).to.be.a.file();
22 | expect(simpleJs).to.have.content("hello");
23 | }));
24 | it("should extract resource with query params into simple-css-with-query-param.js", () =>
25 | compile({testModule: "simple-css-with-query-params.js"}).then(() => {
26 | const simpleJs = path.resolve(__dirname, "dist/simple-css-with-query-params-dist.js");
27 |
28 | expect(simpleJs).to.be.a.file();
29 | expect(simpleJs).to.have.content("simple-dist.css");
30 | }));
31 | it("should extract resource with query params and loader into simple-css-with-query-param-and-loader.js", () =>
32 | compile({testModule: "simple-css-with-query-params-and-loader.js"}).then(() => {
33 | const simpleJs = path.resolve(__dirname, "dist/simple-css-with-query-params-and-loader-dist.js");
34 |
35 | expect(simpleJs).to.be.a.file();
36 | expect(simpleJs).to.have.content("renamed-simple.css");
37 | }));
38 | it("should extract the html of modules/simple.html into simple.html", () =>
39 | compile({testModule: "simple.html"}).then(() => {
40 | const simpleHtml = path.resolve(__dirname, "dist/simple-dist.html");
41 |
42 | expect(simpleHtml).to.be.a.file();
43 | expect(simpleHtml).to.have.content(
44 | fs.readFileSync(
45 | path.resolve(__dirname, "modules/simple.html"),
46 | "utf8"
47 | )
48 | );
49 | }));
50 | it("should extract the css of modules/simple.css into simple.css", () =>
51 | compile({testModule: "simple.css"}).then(() => {
52 | const originalContent = fs.readFileSync(
53 | path.resolve(__dirname, "modules/simple.css"),
54 | "utf8"
55 | );
56 | const simpleCss = path.resolve(__dirname, "dist/simple-dist.css");
57 |
58 | expect(simpleCss).to.be.a.file()
59 | .with.contents.that.match(new RegExp(originalContent));
60 | }));
61 | it("should extract the source maps", () =>
62 | compile({testModule: "simple.css"}).then(() => {
63 | const simpleCss = path.resolve(__dirname, "dist/simple-dist.css");
64 |
65 | expect(simpleCss).to.be.a.file()
66 | .with.contents.that.match(/\/\*# sourceMappingURL=data:application\/json;charset=utf-8;base64,/);
67 | }));
68 | it("should extract the img url into img.js", () => compile({testModule: "img.js"}).then(() => {
69 | const imgJs = path.resolve(__dirname, "dist/img-dist.js");
70 |
71 | expect(imgJs).to.be.a.file();
72 | expect(imgJs).to.have.content("hi-dist.jpg");
73 | }));
74 | it("should extract the img.html as file, emit the referenced img and rewrite the url", () =>
75 | compile({testModule: "img.html"}).then(() => {
76 | const imgHtml = path.resolve(__dirname, "dist/img-dist.html");
77 | const imgJpg = path.resolve(__dirname, "dist/hi-dist.jpg");
78 |
79 | expect(imgHtml).to.be.a.file();
80 | expect(imgJpg).to.be.a.file();
81 | expect(imgHtml).to.have.content.that.match(
82 | /
/
83 | );
84 | }));
85 | it("should extract the img.css as file, emit the referenced img and rewrite the url", () =>
86 | compile({testModule: "img.css"}).then(() => {
87 | const imgCss = path.resolve(__dirname, "dist/img-dist.css");
88 | const imgJpg = path.resolve(__dirname, "dist/hi-dist.jpg");
89 |
90 | expect(imgCss).to.be.a.file();
91 | expect(imgJpg).to.be.a.file();
92 | expect(imgCss).to.have.content.that.match(/ url\(hi-dist\.jpg\);/);
93 | }));
94 | it("should extract the stylesheet.html and the referenced img.css as file, emit the files and rewrite all urls", () =>
95 | compile({testModule: "stylesheet.html"}).then(() => {
96 | const stylesheetHtml = path.resolve(
97 | __dirname,
98 | "dist/stylesheet-dist.html"
99 | );
100 | const imgCss = path.resolve(__dirname, "dist/img-dist.css");
101 | const imgJpg = path.resolve(__dirname, "dist/hi-dist.jpg");
102 |
103 | expect(stylesheetHtml).to.be.a.file();
104 | expect(imgCss).to.be.a.file();
105 | expect(imgJpg).to.be.a.file();
106 | expect(stylesheetHtml).to.have.content.that.match(
107 | //
111 | );
112 | expect(imgCss).to.have.content.that.match(/ url\(hi-dist\.jpg\);/);
113 | }));
114 | it("should extract css files with dependencies", () =>
115 | compile({testModule: "deep.css"}).then(() => {
116 | const deepCss = path.resolve(
117 | __dirname,
118 | "dist/deep-dist.css"
119 | );
120 | // const imgCss = path.resolve(__dirname, "dist/img-dist.css");
121 | const imgJpg = path.resolve(__dirname, "dist/hi-dist.jpg");
122 |
123 | expect(deepCss).to.be.a.file();
124 | // expect(imgCss).to.not.be.a.file();
125 | expect(imgJpg).to.be.a.file();
126 | expect(deepCss).to.have.content.that.match(/ url\(hi-dist\.jpg\);/);
127 | }));
128 | it("should track all dependencies", () =>
129 | compile({testModule: "stylesheet.html"}).then(stats => {
130 | const basePath = path.dirname(__dirname); // returns the parent dirname
131 | const dependencies = Array.from(
132 | stats.compilation.fileDependencies,
133 | dependency => dependency.slice(basePath.length)
134 | );
135 |
136 | expect(dependencies.sort()).to.eql(
137 | [
138 | "/node_modules/css-loader/lib/css-base.js",
139 | "/node_modules/css-loader/lib/url/escape.js",
140 | "/test/modules/hi.jpg",
141 | "/test/modules/img.css",
142 | "/test/modules/stylesheet.html",
143 | ].sort()
144 | );
145 | }));
146 | it("should reference the img with the given publicPath", () =>
147 | compile({testModule: "img.html", publicPath: "/test/"}).then(() => {
148 | const imgHtml = path.resolve(__dirname, "dist/img-dist.html");
149 | const imgJpg = path.resolve(__dirname, "dist/hi-dist.jpg");
150 |
151 | expect(imgHtml).to.be.a.file();
152 | expect(imgJpg).to.be.a.file();
153 | expect(imgHtml).to.have.content.that.match(
154 | /
/
155 | );
156 | }));
157 | it("should override the configured publicPath with the publicPath query option", () =>
158 | compile({
159 | testModule: "img.html",
160 | publicPath: "/test/",
161 | loaderOptions: {publicPath: "/other/"},
162 | }).then(() => {
163 | const imgHtml = path.resolve(__dirname, "dist/img-dist.html");
164 | const imgJpg = path.resolve(__dirname, "dist/hi-dist.jpg");
165 |
166 | expect(imgHtml).to.be.a.file();
167 | expect(imgJpg).to.be.a.file();
168 | expect(imgHtml).to.have.content.that.match(
169 | /
/
170 | );
171 | }));
172 | it("should execute options.publicPath if it's defined as a function", done => {
173 | let publicPathCalledWithContext = false;
174 | const loaderContext = {
175 | async: () => () => done(),
176 | cacheable() {},
177 | query: {
178 | publicPath: context => {
179 | publicPathCalledWithContext = context === loaderContext;
180 |
181 | return "";
182 | },
183 | },
184 | };
185 |
186 | extractLoader.call(loaderContext, "");
187 |
188 | expect(publicPathCalledWithContext).to.equal(true);
189 | });
190 | it("should support explicit loader chains", () => compile({testModule: "loader.html"}).then(() => {
191 | const loaderHtml = path.resolve(__dirname, "dist/loader-dist.html");
192 | const errJs = path.resolve(__dirname, "dist/err.js");
193 |
194 | expect(loaderHtml).to.be.a.file();
195 | expect(errJs).to.have.content("this is a syntax error\n");
196 | }));
197 | it("should report syntax errors", () =>
198 | compile({testModule: "error-syntax.js"}).then(
199 | () => {
200 | throw new Error("Did not throw expected error");
201 | },
202 | message => {
203 | expect(message).to.match(/SyntaxError: unknown: Unexpected token/);
204 | }
205 | ));
206 | it("should report resolve errors", () =>
207 | compile({testModule: "error-resolve.js"}).then(
208 | () => {
209 | throw new Error("Did not throw expected error");
210 | },
211 | message => {
212 | expect(message).to.match(/Error: Cannot find module '\.\/does-not-exist\.jpg'/);
213 | }
214 | ));
215 | it("should report resolve loader errors", () =>
216 | compile({testModule: "error-resolve-loader.js"}).then(
217 | () => {
218 | throw new Error("Did not throw expected error");
219 | },
220 | message => {
221 | expect(message).to.match(/Error: Can't resolve 'does-not-exist'/);
222 | }
223 | ));
224 | it("should not leak globals when there is an error during toString()", () =>
225 | compile({testModule: "error-to-string.js"}).then(
226 | () => {
227 | throw new Error("Did not throw expected error");
228 | },
229 | () => {
230 | expect("btoa" in global).to.be.false;
231 | }
232 | ));
233 | it("should restore the original globals when there is an error during toString()", () => {
234 | const myBtoa = {};
235 |
236 | global.btoa = myBtoa;
237 |
238 | return compile({testModule: "error-to-string.js"}).then(
239 | () => {
240 | throw new Error("Did not throw expected error");
241 | },
242 | () => {
243 | expect(global.btoa).to.equal(myBtoa);
244 | }
245 | );
246 | });
247 | it("should flag itself as cacheable", done => {
248 | const loaderContext = {
249 | async() {
250 | return () => {
251 | expect(cacheableCalled).to.equal(
252 | true,
253 | "cacheable() has not been called"
254 | );
255 | done();
256 | };
257 | },
258 | cacheable() {
259 | cacheableCalled = true;
260 | },
261 | options: {output: {}},
262 | };
263 | let cacheableCalled = false;
264 |
265 | extractLoader.call(loaderContext, "");
266 | });
267 | });
268 |
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require babel-register
2 | --timeout 5000
3 |
--------------------------------------------------------------------------------
/test/modules/deep.css:
--------------------------------------------------------------------------------
1 | @import "./img.css";
2 |
--------------------------------------------------------------------------------
/test/modules/error-resolve-loader.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable
2 | import/unambiguous,
3 | import/no-unresolved,
4 | import/no-extraneous-dependencies,
5 | import/no-webpack-loader-syntax
6 | */
7 | module.exports = require("does-not-exist!./hi.jpg");
8 |
--------------------------------------------------------------------------------
/test/modules/error-resolve.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/unambiguous, import/no-unresolved */
2 | module.exports = require("./does-not-exist.jpg");
3 |
--------------------------------------------------------------------------------
/test/modules/error-syntax.js:
--------------------------------------------------------------------------------
1 | this is a syntax error
2 |
--------------------------------------------------------------------------------
/test/modules/error-to-string.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/unambiguous */
2 |
3 | module.exports = {
4 | toString() {
5 | throw new Error("Error during toString()");
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/test/modules/hi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peerigon/extract-loader/85008407e266ef7d7513d10a68ae9d03bddff7b8/test/modules/hi.jpg
--------------------------------------------------------------------------------
/test/modules/img.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: url(hi.jpg);
3 | }
4 |
--------------------------------------------------------------------------------
/test/modules/img.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/modules/img.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/unambiguous */
2 | module.exports = require("./hi.jpg");
3 |
--------------------------------------------------------------------------------
/test/modules/loader.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Hello World
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/modules/simple-css-with-query-params-and-loader.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/unambiguous, import/no-unresolved, import/no-webpack-loader-syntax */
2 | module.exports = require("!!file-loader?name=renamed-simple.css!./simple.css?v=1.2");
3 |
--------------------------------------------------------------------------------
/test/modules/simple-css-with-query-params.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/unambiguous, import/no-unresolved */
2 | module.exports = require("./simple.css?v=1.2");
3 |
--------------------------------------------------------------------------------
/test/modules/simple.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: hotpink;
3 | }
4 |
--------------------------------------------------------------------------------
/test/modules/simple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/modules/simple.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/unambiguous */
2 | module.exports = "hello";
3 |
--------------------------------------------------------------------------------
/test/modules/stylesheet.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/support/compile.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import webpack from "webpack";
3 |
4 | const pathToExtractLoader = path.resolve(
5 | __dirname,
6 | "../../src/extractLoader.js"
7 | );
8 |
9 | function compile({testModule, publicPath, loaderOptions}) {
10 | const testModulePath = path.resolve(__dirname, "../modules/", testModule);
11 |
12 | return new Promise((resolve, reject) => {
13 | webpack(
14 | {
15 | mode: "development",
16 | entry: testModulePath,
17 | output: {
18 | path: path.resolve(__dirname, "../dist"),
19 | filename: "bundle.js",
20 | publicPath,
21 | },
22 | module: {
23 | rules: [
24 | {
25 | test: /\.js$/,
26 | use: [
27 | {
28 | loader: "file-loader",
29 | options: {
30 | // appending -dist so we can check if url rewriting is working
31 | name: "[name]-dist.[ext]",
32 | },
33 | },
34 | {
35 | loader: pathToExtractLoader,
36 | options: loaderOptions,
37 | },
38 | ],
39 | },
40 | {
41 | test: /\.html$/,
42 | use: [
43 | {
44 | loader: "file-loader",
45 | options: {
46 | name: "[name]-dist.[ext]",
47 | },
48 | },
49 | {
50 | loader: pathToExtractLoader,
51 | options: loaderOptions,
52 | },
53 | {
54 | loader: "html-loader",
55 | options: {
56 | attrs: ["img:src", "link:href"],
57 | interpolate: true,
58 | },
59 | },
60 | ],
61 | },
62 | {
63 | test: /\.css$/,
64 | loaders: [
65 | {
66 | loader: "file-loader",
67 | options: {
68 | name: "[name]-dist.[ext]",
69 | },
70 | },
71 | {
72 | loader: pathToExtractLoader,
73 | options: loaderOptions,
74 | },
75 | {
76 | loader: "css-loader",
77 | options: {
78 | sourceMap: true,
79 | },
80 | },
81 | ],
82 | },
83 | {
84 | test: /\.jpg$/,
85 | loaders: [
86 | {
87 | loader: "file-loader",
88 | options: {
89 | name: "[name]-dist.[ext]",
90 | },
91 | },
92 | ],
93 | },
94 | ],
95 | },
96 | },
97 | (err, stats) => { // eslint-disable-line promise/prefer-await-to-callbacks
98 | if (err || stats.hasErrors() || stats.hasWarnings()) {
99 | reject(err || stats.toString("minimal"));
100 | } else {
101 | resolve(stats);
102 | }
103 | }
104 | );
105 | });
106 | }
107 |
108 | export default compile;
109 |
--------------------------------------------------------------------------------