Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: webpack-contrib/sass-loader
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v7.1.0
Choose a base ref
...
head repository: webpack-contrib/sass-loader
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v7.2.0
Choose a head ref

Commits on Aug 1, 2018

  1. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    LastDragon-ru Aleksei Lebedev
    Copy the full SHA
    6085bf6 View commit details

Commits on Aug 7, 2018

  1. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    LastDragon-ru Aleksei Lebedev
    Copy the full SHA
    b7aca40 View commit details

Commits on Aug 23, 2018

  1. Copy the full SHA
    f4bdcfe View commit details

Commits on Aug 25, 2018

  1. Copy the full SHA
    a7bf7c0 View commit details

Commits on Oct 30, 2018

  1. Copy the full SHA
    472d09a View commit details

Commits on Dec 12, 2018

  1. Copy the full SHA
    5984a2c View commit details
  2. Copy the full SHA
    69dc5e5 View commit details
  3. Copy the full SHA
    d56c0f8 View commit details
  4. Copy the full SHA
    f799569 View commit details

Commits on Dec 13, 2018

  1. Copy the full SHA
    a80cdb1 View commit details
  2. Copy the full SHA
    bc3b848 View commit details
  3. Verified

    This commit was signed with the committer’s verified signature.
    ramsey Ben Ramsey
    Copy the full SHA
    2adcca3 View commit details

Commits on Dec 14, 2018

  1. Verified

    This commit was signed with the committer’s verified signature.
    ramsey Ben Ramsey
    Copy the full SHA
    f524223 View commit details
  2. feat: support auto resolving dart-sass

    Now you don't need setup `implementation: require('sass')`, just add `sass` to your `package.json` and install dependencies and `sass-loader` automatically load `sass`. Beware situation when `node-sass` and `sass` was installed, by default `sass-loader` loads `node-sass`, to avoid this situation use `implementation` option.
    evilebottnawi authored Dec 14, 2018

    Verified

    This commit was signed with the committer’s verified signature.
    ramsey Ben Ramsey
    Copy the full SHA
    ff90dd6 View commit details
  3. Copy the full SHA
    a8709c9 View commit details
  4. Copy the full SHA
    aa64e1b View commit details
  5. Copy the full SHA
    2d6045b View commit details

Commits on Dec 17, 2018

  1. Copy the full SHA
    6c9654d View commit details

Commits on Mar 14, 2019

  1. docs: add source-map to style-loader (#661)

    Jack Zhao authored and evilebottnawi committed Mar 14, 2019
    Copy the full SHA
    69c6f91 View commit details

Commits on Mar 26, 2019

  1. Copy the full SHA
    9162e45 View commit details

Commits on May 7, 2019

  1. Copy the full SHA
    e279f2a View commit details

Commits on Aug 8, 2019

  1. Copy the full SHA
    9e5a45d View commit details
  2. Copy the full SHA
    2a51502 View commit details
  3. Copy the full SHA
    28f1884 View commit details
  4. Copy the full SHA
    6fc9d4e View commit details
  5. Copy the full SHA
    bcb06d5 View commit details
Showing with 10,028 additions and 8,341 deletions.
  1. +8 −10 .editorconfig
  2. +1 −1 .eslintignore
  3. +13 −0 .eslintrc.js
  4. +0 −9 .eslintrc.json
  5. +3 −0 .gitattributes
  6. +6 −0 .github/CODEOWNERS
  7. +124 −0 .github/CONTRIBUTING.md
  8. +16 −0 .github/ISSUE_TEMPLATE.md
  9. +51 −0 .github/ISSUE_TEMPLATE/BUG.md
  10. +30 −0 .github/ISSUE_TEMPLATE/DOCS.md
  11. +28 −0 .github/ISSUE_TEMPLATE/FEATURE.md
  12. +33 −0 .github/ISSUE_TEMPLATE/MODIFICATION.md
  13. +9 −0 .github/ISSUE_TEMPLATE/SUPPORT.md
  14. +35 −0 .github/PULL_REQUEST_TEMPLATE.md
  15. +14 −13 .gitignore
  16. +1 −1 .nycrc
  17. +5 −0 .prettierrc
  18. +8 −15 .travis.yml
  19. +20 −0 CHANGELOG.md
  20. +62 −5 README.md
  21. +20 −4 appveyor.yml
  22. +47 −41 lib/formatSassError.js
  23. +46 −44 lib/importsToResolve.js
  24. +172 −90 lib/loader.js
  25. +71 −52 lib/normalizeOptions.js
  26. +16 −12 lib/proxyCustomImporters.js
  27. +41 −35 lib/webpackImporter.js
  28. +7,876 −7,327 package-lock.json
  29. +46 −21 package.json
  30. +0 −5 test/.eslintrc.json
  31. +39 −31 test/bootstrapSass/webpack.config.js
  32. +33 −27 test/extractText/webpack.config.js
  33. +4 −3 test/hmr/entry.js
  34. +27 −20 test/hmr/webpack.config.js
  35. +654 −390 test/index.test.js
  36. +1 −0 test/node_modules/@org/style.js
  37. +3 −0 test/node_modules/@org/style.scss
  38. +2 −0 test/node_modules/sass-custom-sass-field/nested/style.sass
  39. +12 −0 test/node_modules/sass-custom-sass-field/package.json
  40. +2 −0 test/node_modules/sass-main-field/nested/style.sass
  41. +11 −0 test/node_modules/sass-main-field/package.json
  42. +2 −0 test/node_modules/sass-package-with-index/index.sass
  43. +2 −0 test/node_modules/sass-sass-field/nested/style.sass
  44. +12 −0 test/node_modules/sass-sass-field/package.json
  45. +2 −0 test/node_modules/sass-style-field/nested/style.sass
  46. +12 −0 test/node_modules/sass-style-field/package.json
  47. +3 −0 test/node_modules/scss-custom-sass-field/nested/style.scss
  48. +12 −0 test/node_modules/scss-custom-sass-field/package.json
  49. +3 −0 test/node_modules/scss-main-field/nested/style.scss
  50. +11 −0 test/node_modules/scss-main-field/package.json
  51. +3 −0 test/node_modules/scss-package-with-index/index.scss
  52. +3 −0 test/node_modules/scss-sass-field/nested/style.scss
  53. +12 −0 test/node_modules/scss-sass-field/package.json
  54. +3 −0 test/node_modules/scss-style-field/nested/style.scss
  55. +12 −0 test/node_modules/scss-style-field/package.json
  56. +1 −0 test/node_modules/scss/style.js
  57. +3 −0 test/node_modules/scss/style.scss
  58. +1 −0 test/sass/import-custom-sass-field.sass
  59. +1 −0 test/sass/import-index.sass
  60. +1 −0 test/sass/import-main-field.sass
  61. +1 −0 test/sass/import-sass-field.sass
  62. +1 −0 test/sass/import-style-field.sass
  63. +2 −0 test/sass/imports.sass
  64. +2 −1 test/scss/another/module.js
  65. +1 −0 test/scss/import-custom-sass-field.scss
  66. +1 −0 test/scss/import-index.scss
  67. +1 −0 test/scss/import-main-field.scss
  68. +1 −0 test/scss/import-sass-field.scss
  69. +1 −0 test/scss/import-style-field.scss
  70. +3 −0 test/scss/imports.scss
  71. +7 −0 test/scss/simple.scss
  72. +33 −24 test/sourceMap/webpack.config.js
  73. +31 −33 test/spec.test.js
  74. +189 −67 test/tools/createSpec.js
  75. +14 −14 test/tools/customFunctions.js
  76. +7 −7 test/tools/customImporter.js
  77. +4 −4 test/tools/runCreateSpec.js
  78. +5 −5 test/tools/testLoader.js
  79. +2 −2 test/watch/entry.js
  80. +33 −28 test/watch/webpack.config.js
18 changes: 8 additions & 10 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
# This file is for unifying the coding style for different editors and IDEs.
# More information at http://EditorConfig.org

# No .editorconfig files above the root directory
root = true
# editorconfig.org

[*]
indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[package.json]
indent_size = 2
[*.md]
insert_final_newline = true
trim_trailing_whitespace = false
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/node_modules
# Compiled by webpack
test/output

# Fake node_modules folder for tests
test/node_modules
13 changes: 13 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
root: true,
plugins: ['prettier'],
extends: ['@webpack-contrib/eslint-config-webpack'],
rules: {
// Remove strict rule in next major
strict: 'off',
'prettier/prettier': [
'error',
{ singleQuote: true, trailingComma: 'es5', arrowParens: 'always' },
],
},
};
9 changes: 0 additions & 9 deletions .eslintrc.json

This file was deleted.

3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package-lock.json -diff
* text=auto
bin/* eol=lf
6 changes: 6 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# These are the default owners for everything in
# webpack-contrib
@webpack-contrib/org-maintainers

# Add repository specific users / groups
# below here for libs that are not maintained by the org.
124 changes: 124 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
## Contributing in @webpack-contrib

We'd always love contributions to further improve the webpack / webpack-contrib ecosystem!
Here are the guidelines we'd like you to follow:

* [Questions and Problems](#question)
* [Issues and Bugs](#issue)
* [Feature Requests](#feature)
* [Pull Request Submission Guidelines](#submit-pr)
* [Commit Message Conventions](#commit)

### <a name="question"></a> Got a Question or Problem?

Please submit support requests and questions to StackOverflow using the tag [[webpack]](http://stackoverflow.com/tags/webpack).
StackOverflow is better suited for this kind of support though you may also inquire in [Webpack Gitter](https://gitter.im/webpack/webpack).
The issue tracker is for bug reports and feature discussions.

### <a name="issue"></a> Found an Issue or Bug?

Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available.

We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs, we ask that you to provide a minimal reproduction scenario (github repo or failing test case). Having a live, reproducible scenario gives us a wealth of important information without going back & forth to you with additional questions like:

- version of Webpack used
- version of the loader / plugin you are creating a bug report for
- the use-case that fails

A minimal reproduce scenario allows us to quickly confirm a bug (or point out config problems) as well as confirm that we are fixing the right problem.

We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.

Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that doesn't have enough info to be reproduced.

### <a name="feature"></a> Feature Requests?

You can *request* a new feature by creating an issue on Github.

If you would like to *implement* a new feature, please submit an issue with a proposal for your work `first`, to be sure that particular makes sense for the project.

### <a name="submit-pr"></a> Pull Request Submission Guidelines

Before you submit your Pull Request (PR) consider the following guidelines:

- Search Github for an open or closed PR that relates to your submission. You don't want to duplicate effort.
- Commit your changes using a descriptive commit message that follows our [commit message conventions](#commit). Adherence to these conventions is necessary because release notes are automatically generated from these messages.
- Fill out our `Pull Request Template`. Your pull request will not be considered if it is ignored.
- Please sign the `Contributor License Agreement (CLA)` when a pull request is opened. We cannot accept your pull request without this. Make sure you sign with the primary email address associated with your local / github account.

### <a name="commit"></a> Webpack Contrib Commit Conventions

Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
format that includes a **type**, a **scope** and a **subject**:

```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```

The **header** is mandatory and the **scope** of the header is optional.

Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
to read on GitHub as well as in various git tools.

The footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.

Examples:
```
docs(readme): update install instructions
```
```
fix: refer to the `entrypoint` instead of the first `module`
```

#### Revert
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit.
In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.

#### Type
Must be one of the following:

* **build**: Changes that affect the build system or external dependencies (example scopes: babel, npm)
* **chore**: Changes that fall outside of build / docs that do not effect source code (example scopes: package, defaults)
* **ci**: Changes to our CI configuration files and scripts (example scopes: circleci, travis)
* **docs**: Documentation only changes (example scopes: readme, changelog)
* **feat**: A new feature
* **fix**: A bug fix
* **perf**: A code change that improves performance
* **refactor**: A code change that neither fixes a bug nor adds a feature
* **revert**: Used when reverting a committed change
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons)
* **test**: Addition of or updates to Jest tests

#### Scope
The scope is subjective & depends on the `type` see above. A good example would be a change to a particular class / module.

#### Subject
The subject contains a succinct description of the change:

* use the imperative, present tense: "change" not "changed" nor "changes"
* don't capitalize the first letter
* no dot (.) at the end

#### Body
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
The body should include the motivation for the change and contrast this with previous behavior.

#### Footer
The footer should contain any information about **Breaking Changes** and is also the place to
reference GitHub issues that this commit **Closes**.

**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.

Example

```
BREAKING CHANGE: Updates to `Chunk.mapModules`.
This release is not backwards compatible with `Webpack 2.x` due to breaking changes in webpack/webpack#4764
Migration: see webpack/webpack#5225
```
16 changes: 16 additions & 0 deletions .github/ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!--
👉🏽 Need support, advice, or help? Don't open an issue!
Head to StackOverflow or https://gitter.im/webpack/webpack.
Hey there!
You arrived at this template because you felt none of the other options
matched the kind of issue you'd like to report. Please use this opportunity to
tell us about your particular type of issue so we can try to accomodate
similar issues in the future.
PLEASE do note, if you're using this to report an issue already covered by the
existing template types, your issue may be closed as invalid. Our issue
templates contain fields that help us help you, and without that important
info, we might as well be ice-skating uphill, carrying a wooly mammoth.
-->
51 changes: 51 additions & 0 deletions .github/ISSUE_TEMPLATE/BUG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
name: 🐛 Bug Report
about: Something went awry and you'd like to tell us about it.

---

<!--
Issues are so 🔥
If you remove or skip this template, you'll make the 🐼 sad and the mighty god
of Github will appear and pile-drive the close button from a great height
while making animal noises.
👉🏽 Need support, advice, or help? Don't open an issue!
Head to StackOverflow or https://gitter.im/webpack/webpack.
-->

* Operating System:
* Node Version:
* NPM Version:
* webpack Version:
* sass-loader Version:

### Expected Behavior

<!-- Remove this section if not reporting a bug or modification request. -->

### Actual Behavior

<!-- Remove this section if not reporting a bug or modification request. -->

### Code

```js
// webpack.config.js
// If your bitchin' code blocks are over 20 lines, please paste a link to a gist
// (https://gist.github.com).
```

```js
// additional code, HEY YO remove this block if you don't need it
```

### How Do We Reproduce?

<!--
Remove this section if not reporting a bug.
If your webpack config is over 50 lines long, please provide a URL to a repo
for your beefy 🍖 app that we can use to reproduce.
-->
30 changes: 30 additions & 0 deletions .github/ISSUE_TEMPLATE/DOCS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
name: 📚 Documentation
about: Are the docs lacking or missing something? Do they need some new 🔥 hotness? Tell us here.

---

<!--
Issues are so 🔥
If you remove or skip this template, you'll make the 🐼 sad and the mighty god
of Github will appear and pile-drive the close button from a great height
while making animal noises.
👉🏽 Need support, advice, or help? Don't open an issue!
Head to StackOverflow or https://gitter.im/webpack/webpack.
-->

Documentation Is:

<!-- Please place an x (no spaces!) in all [ ] that apply -->

- [ ] Missing
- [ ] Needed
- [ ] Confusing
- [ ] Not Sure?

### Please Explain in Detail...


### Your Proposal for Changes
28 changes: 28 additions & 0 deletions .github/ISSUE_TEMPLATE/FEATURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: ✨ Feature Request
about: Suggest an idea for this project

---

<!--
Issues are so 🔥
If you remove or skip this template, you'll make the 🐼 sad and the mighty god
of Github will appear and pile-drive the close button from a great height
while making animal noises.
👉🏽 Need support, advice, or help? Don't open an issue!
Head to StackOverflow or https://gitter.im/webpack/webpack.
-->

* Operating System:
* Node Version:
* NPM Version:
* webpack Version:
* sass-loader Version:

### Feature Proposal



### Feature Use Case
33 changes: 33 additions & 0 deletions .github/ISSUE_TEMPLATE/MODIFICATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
name: 🔧 Modification Request
about: Would you like something work differently? Have an alternative approach? This is the template for you.

---

<!--
Issues are so 🔥
If you remove or skip this template, you'll make the 🐼 sad and the mighty god
of Github will appear and pile-drive the close button from a great height
while making animal noises.
👉🏽 Need support, advice, or help? Don't open an issue!
Head to StackOverflow or https://gitter.im/webpack/webpack.
-->

* Operating System:
* Node Version:
* NPM Version:
* webpack Version:
* sass-loader Version:


### Expected Behavior / Situation



### Actual Behavior / Situation



### Modification Proposal
9 changes: 9 additions & 0 deletions .github/ISSUE_TEMPLATE/SUPPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
name: 🆘 Support, Help, and Advice
about: 👉🏽 Need support, help, or advice? Don't open an issue! Head to StackOverflow or https://gitter.im/webpack/webpack.

---

Hey there! If you need support, help, or advice then this is not the place to ask.
Please visit [StackOverflow](https://stackoverflow.com/questions/tagged/webpack)
or [the Webpack Gitter](https://gitter.im/webpack/webpack) instead.
35 changes: 35 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!--
HOLY CRAP a Pull Request. We ❤️ those!
If you remove or skip this template, you'll make the 🐼 sad and the mighty god
of Github will appear and pile-drive the close button from a great height
while making animal noises.
Please place an x (no spaces!) in all [ ] that apply
-->

This PR contains a:

- [ ] **bugfix**
- [ ] new **feature**
- [ ] **code refactor**
- [ ] **test update** <!-- if bug or feature is checked, this should be too -->
- [ ] **typo fix**
- [ ] **metadata update**

### Motivation / Use-Case

<!--
Please explain the motivation or use-case for your change.
What existing problem does the PR solve?
If this PR addresses an issue, please link to the issue.
-->

### Breaking Changes

<!--
If this PR introduces a breaking change, please describe the impact and a
migration path for existing applications.
-->

### Additional Info
27 changes: 14 additions & 13 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz

pids
logs
results
*.log
npm-debug.log*
.eslintcache

npm-debug.log
/coverage
/dist
/local
/reports
/node_modules
coverage

.DS_Store
Thumbs.db
.idea
*.iml
.vscode
*.sublime-project
*.sublime-workspace
.nyc_output
test/output
2 changes: 1 addition & 1 deletion .nycrc
Original file line number Diff line number Diff line change
@@ -9,6 +9,6 @@
"lines": 97,
"statements": 97,
"functions": 100,
"branches": 91,
"branches": 89,
"check-coverage": true
}
5 changes: 5 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"singleQuote": true,
"trailingComma": "es5",
"arrowParens": "always"
}
23 changes: 8 additions & 15 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -8,28 +8,21 @@ jobs:
fast_finish: true
allow_failures:
- env: WEBPACK_VERSION=canary
- node_js: 9
include:
- &test-latest
stage: Webpack latest
node_js: 6
env: WEBPACK_VERSION=latest JOB_PART=test
script: npm run travis:$JOB_PART
- <<: *test-latest
node_js: 8
- node_js: 6
env: WEBPACK_VERSION=latest JOB_PART=lint
script: npm run travis:$JOB_PART
- <<: *test-latest
node_js: 8
- node_js: 6
env: WEBPACK_VERSION=latest JOB_PART=test
script: npm run travis:$JOB_PART
- node_js: 8
env: WEBPACK_VERSION=latest JOB_PART=coverage
script: npm run travis:$JOB_PART
after_success: 'bash <(curl -s https://codecov.io/bash)'
- stage: Webpack canary
node_js: 8
env: WEBPACK_VERSION=4.0.0 JOB_PART=test
- node_js: 10
env: WEBPACK_VERSION=latest JOB_PART=test
script: npm run travis:$JOB_PART
- stage: NodeJS Next
node_js: 9
- node_js: 11
env: WEBPACK_VERSION=latest JOB_PART=test
script: npm run travis:$JOB_PART
before_install:
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -2,6 +2,26 @@

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.

<a name="7.2.0"></a>
# [7.2.0](https://github.com/webpack-contrib/sass-loader/compare/v7.1.0...v7.2.0) (2019-08-08)


### Bug Fixes

* better handle stdin in sources ([#681](https://github.com/webpack-contrib/sass-loader/issues/681)) ([e279f2a](https://github.com/webpack-contrib/sass-loader/commit/e279f2a))
* prefer `sass`/`scss`/`css` extensions ([#711](https://github.com/webpack-contrib/sass-loader/issues/711)) ([6fc9d4e](https://github.com/webpack-contrib/sass-loader/commit/6fc9d4e))
* relax node engine ([#708](https://github.com/webpack-contrib/sass-loader/issues/708)) ([2a51502](https://github.com/webpack-contrib/sass-loader/commit/2a51502))


### Features

* allow passing `functions` option as function ([#651](https://github.com/webpack-contrib/sass-loader/issues/651)) ([6c9654d](https://github.com/webpack-contrib/sass-loader/commit/6c9654d))
* support `data` as `Function` ([#648](https://github.com/webpack-contrib/sass-loader/issues/648)) ([aa64e1b](https://github.com/webpack-contrib/sass-loader/commit/aa64e1b))
* support `sass` and `style` fields in `package.json` ([#647](https://github.com/webpack-contrib/sass-loader/issues/647)) ([a8709c9](https://github.com/webpack-contrib/sass-loader/commit/a8709c9))
* support auto resolving `dart-sass` ([ff90dd6](https://github.com/webpack-contrib/sass-loader/commit/ff90dd6))



<a name="7.1.0"></a>
# [7.1.0](https://github.com/webpack-contrib/sass-loader/compare/v7.0.3...v7.1.0) (2018-08-01)

67 changes: 62 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@
<p>Loads a Sass/SCSS file and compiles it to CSS.</p>
</div>

Use the [css-loader](https://github.com/webpack-contrib/css-loader) or the [raw-loader](https://github.com/webpack-contrib/raw-loader) to turn it into a JS module and the [MiniCssExtractPlugin](https://github.com/webpack-contrib/mini-css-extract-plugin) to extract it into a separate file.
Use the [css-loader](https://github.com/webpack-contrib/css-loader) or the [raw-loader](https://github.com/webpack-contrib/raw-loader) to turn it into a JS module and the [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) to extract it into a separate file.
Looking for the webpack 1 loader? Check out the [archive/webpack-1 branch](https://github.com/webpack-contrib/sass-loader/tree/archive/webpack-1).

<h2 align="center">Install</h2>
@@ -29,7 +29,7 @@ npm install sass-loader node-sass webpack --save-dev

The sass-loader requires [webpack](https://github.com/webpack) as a
[`peerDependency`](https://docs.npmjs.com/files/package.json#peerdependencies)
and it requires you to install either [Node Sass][] or [Dart Sass][] on your
and it requires you to install either [Node Sass](https://github.com/sass/node-sass) or [Dart Sass](https://github.com/sass/dart-sass) on your
own. This allows you to control the versions of all your dependencies, and to
choose which Sass implementation to use.

@@ -87,6 +87,40 @@ module.exports = {

See [the Node Sass documentation](https://github.com/sass/node-sass/blob/master/README.md#options) for all available Sass options.

By default the loader resolve the implementation based on your dependencies.
Just add required implementation to `package.json`
(`node-sass` or `sass` package) and install dependencies.

Example where the `sass-loader` loader uses the `sass` (`dart-sass`) implementation:

**package.json**

```json
{
"devDependencies": {
"sass-loader": "*",
"sass": "*"
}
}
```

Example where the `sass-loader` loader uses the `node-sass` implementation:

**package.json**

```json
{
"devDependencies": {
"sass-loader": "*",
"node-sass": "*"
}
}
```

Beware the situation
when `node-sass` and `sass` was installed, by default the `sass-loader`
prefers `node-sass`, to avoid this situation use the `implementation` option.

The special `implementation` option determines which implementation of Sass to
use. It takes either a [Node Sass][] or a [Dart Sass][] module. For example, to
use Dart Sass, you'd pass:
@@ -136,7 +170,7 @@ module.exports = {

### In production

Usually, it's recommended to extract the style sheets into a dedicated file in production using the [MiniCssExtractPlugin](https://github.com/webpack-contrib/mini-css-extract-plugin). This way your styles are not dependent on JavaScript:
Usually, it's recommended to extract the style sheets into a dedicated file in production using the [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin). This way your styles are not dependent on JavaScript:

```js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
@@ -196,7 +230,7 @@ Bundling CSS with webpack has some nice advantages like referencing images and f
There are two possibilities to extract a style sheet from the bundle:

- [extract-loader](https://github.com/peerigon/extract-loader) (simpler, but specialized on the css-loader's output)
- [extract-text-webpack-plugin](https://github.com/webpack-contrib/extract-text-webpack-plugin) (more complex, but works in all use-cases)
- [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) (use this, when using webpack 4 configuration. Works in all use-cases)

### Source maps

@@ -210,7 +244,9 @@ module.exports = {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader"
loader: "style-loader", options: {
sourceMap: true
}
}, {
loader: "css-loader", options: {
sourceMap: true
@@ -240,6 +276,27 @@ If you want to prepend Sass code before the actual entry file, you can set the `
}
```

The `data` option supports `Function` notation:

```javascript
{
loader: "sass-loader",
options: {
data: (loaderContext) => {
// More information about avalaible options https://webpack.js.org/api/loaders/
const { resourcePath, rootContext } = loaderContext;
const relativePath = path.relative(rootContext,resourcePath);

if (relativePath === "styles/foo.scss") {
return "$value: 100px;"
}

return "$value: 200px;"
}
}
}
```

**Please note:** Since you're injecting code, this will break the source mappings in your entry file. Often there's a simpler solution than this, like multiple Sass entry files.

<h2 align="center">Maintainers</h2>
24 changes: 20 additions & 4 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,45 @@
branches:
only:
- master
- next
init:
- git config --global core.autocrlf input
cache:
- node_modules
- '%APPDATA%\npm-cache'
environment:
matrix:
- nodejs_version: '6'
webpack_version: latest
job_part: test
- nodejs_version: '8'
webpack_version: latest
job_part: test
- nodejs_version: '6'
- nodejs_version: '10'
webpack_version: latest
job_part: test
- nodejs_version: '11'
webpack_version: latest
job_part: test
- nodejs_version: '8'
webpack_version: 4.0.0
webpack_version: next
job_part: test
build: 'off'
matrix:
fast_finish: true
allow_failures:
- nodejs_version: '8'
webpack_version: next
job_part: test
install:
- ps: Install-Product node $env:nodejs_version x64
- npm i -g npm@latest
- npm install
- npm ci
- npm i -g @webpack-contrib/tag-versions
before_test:
- cmd: npm install webpack@%webpack_version%
test_script:
- node --version
- npm --version
- cmd: npm run appveyor:%job_part%
- cmd: FOR /F %%I in ('compver --name webpack --gte %webpack_version% --lt latest') do SET COMPARED_VERSION_RESULT=%%I
- cmd: IF %COMPARED_VERSION_RESULT% NEQ -1 (npm run appveyor:test) ELSE (ECHO "Next is older than Latest - Skipping Canary Suite")
88 changes: 47 additions & 41 deletions lib/formatSassError.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
"use strict";
'use strict';

const path = require("path");
const os = require("os");
const fs = require("fs");
const path = require('path');
const os = require('os');
const fs = require('fs');

// A typical sass error looks like this
const SassError = { // eslint-disable-line no-unused-vars
message: "invalid property name",
column: 14,
line: 1,
file: "stdin",
status: 1
};
// const SassError = {
// message: "invalid property name",
// column: 14,
// line: 1,
// file: "stdin",
// status: 1
// };

/**
* Enhances the sass error with additional information about what actually went wrong.
@@ -20,31 +20,37 @@ const SassError = { // eslint-disable-line no-unused-vars
* @param {string} resourcePath
*/
function formatSassError(err, resourcePath) {
// Instruct webpack to hide the JS stack from the console
// Usually you're only interested in the SASS stack in this case.
err.hideStack = true;
// Instruct webpack to hide the JS stack from the console
// Usually you're only interested in the SASS stack in this case.
// eslint-disable-next-line no-param-reassign
err.hideStack = true;

// The file property is missing in rare cases.
// No improvement in the error is possible.
if (!err.file) {
return;
}
// The file property is missing in rare cases.
// No improvement in the error is possible.
if (!err.file) {
return;
}

let msg = err.message;
let msg = err.message;

if (err.file === "stdin") {
err.file = resourcePath;
}
// node-sass returns UNIX-style paths
err.file = path.normalize(err.file);
if (err.file === 'stdin') {
// eslint-disable-next-line no-param-reassign
err.file = resourcePath;
}

// The 'Current dir' hint of node-sass does not help us, we're providing
// additional information by reading the err.file property
msg = msg.replace(/\s*Current dir:\s*/, "");
// node-sass returns UNIX-style paths
// eslint-disable-next-line no-param-reassign
err.file = path.normalize(err.file);

err.message = getFileExcerptIfPossible(err) +
msg.charAt(0).toUpperCase() + msg.slice(1) + os.EOL +
" in " + err.file + " (line " + err.line + ", column " + err.column + ")";
// The 'Current dir' hint of node-sass does not help us, we're providing
// additional information by reading the err.file property
msg = msg.replace(/\s*Current dir:\s*/, '');

// eslint-disable-next-line no-param-reassign
err.message = `${getFileExcerptIfPossible(err) +
msg.charAt(0).toUpperCase() +
msg.slice(1) +
os.EOL} in ${err.file} (line ${err.line}, column ${err.column})`;
}

/**
@@ -57,17 +63,17 @@ function formatSassError(err, resourcePath) {
* @returns {string}
*/
function getFileExcerptIfPossible(err) {
try {
const content = fs.readFileSync(err.file, "utf8");
try {
const content = fs.readFileSync(err.file, 'utf8');

return os.EOL +
content.split(os.EOL)[err.line - 1] + os.EOL +
new Array(err.column - 1).join(" ") + "^" + os.EOL +
" ";
} catch (err) {
// If anything goes wrong here, we don't want any errors to be reported to the user
return "";
}
return `${os.EOL +
content.split(os.EOL)[err.line - 1] +
os.EOL +
new Array(err.column - 1).join(' ')}^${os.EOL} `;
} catch (ignoreErr) {
// If anything goes wrong here, we don't want any errors to be reported to the user
return '';
}
}

module.exports = formatSassError;
90 changes: 46 additions & 44 deletions lib/importsToResolve.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"use strict";
'use strict';

const path = require("path");
const utils = require("loader-utils");
const path = require('path');

const matchModuleImport = /^~([^\/]+|@[^\/]+[\/][^\/]+)$/;
const utils = require('loader-utils');

const matchModuleImport = /^~([^/]+|@[^/]+[/][^/]+)$/;

/**
* When libsass tries to resolve an import, it uses a special algorithm.
@@ -15,46 +16,47 @@ const matchModuleImport = /^~([^\/]+|@[^\/]+[\/][^\/]+)$/;
* @returns {Array<string>}
*/
function importsToResolve(url) {
const request = utils.urlToRequest(url);
// Keep in mind: ext can also be something like '.datepicker' when the true extension is omitted and the filename contains a dot.
// @see https://github.com/webpack-contrib/sass-loader/issues/167
const ext = path.extname(request);

if (matchModuleImport.test(url)) {
return [request, url];
}

// libsass' import algorithm works like this:

// In case there is a file extension...
// - If the file is a CSS-file, do not include it all, but just link it via @import url().
// - The exact file name must match (no auto-resolving of '_'-modules).
if (ext === ".css") {
return [];
}
if (ext === ".scss" || ext === ".sass") {
return [request, url];
}

// In case there is no file extension...
// - Prefer modules starting with '_'.
// - File extension precedence: .scss, .sass, .css.
const basename = path.basename(request);

if (basename.charAt(0) === "_") {
return [
`${ request }.scss`, `${ request }.sass`, `${ request }.css`,
url
];
}

const dirname = path.dirname(request);

return [
`${ dirname }/_${ basename }.scss`, `${ dirname }/_${ basename }.sass`, `${ dirname }/_${ basename }.css`,
`${ request }.scss`, `${ request }.sass`, `${ request }.css`,
url
];
const request = utils.urlToRequest(url);
// Keep in mind: ext can also be something like '.datepicker' when the true extension is omitted and the filename contains a dot.
// @see https://github.com/webpack-contrib/sass-loader/issues/167
const ext = path.extname(request);

if (matchModuleImport.test(url)) {
return [request, url];
}

// libsass' import algorithm works like this:

// In case there is a file extension...
// - If the file is a CSS-file, do not include it all, but just link it via @import url().
// - The exact file name must match (no auto-resolving of '_'-modules).
if (ext === '.css') {
return [];
}
if (ext === '.scss' || ext === '.sass') {
return [request, url];
}

// In case there is no file extension...
// - Prefer modules starting with '_'.
// - File extension precedence: .scss, .sass, .css.
const basename = path.basename(request);

if (basename.charAt(0) === '_') {
return [`${request}.scss`, `${request}.sass`, `${request}.css`, url];
}

const dirname = path.dirname(request);

return [
`${dirname}/_${basename}.scss`,
`${dirname}/_${basename}.sass`,
`${dirname}/_${basename}.css`,
`${request}.scss`,
`${request}.sass`,
`${request}.css`,
url,
];
}

module.exports = importsToResolve;
262 changes: 172 additions & 90 deletions lib/loader.js
Original file line number Diff line number Diff line change
@@ -1,80 +1,135 @@
"use strict";
'use strict';

const path = require("path");
const async = require("neo-async");
const formatSassError = require("./formatSassError");
const webpackImporter = require("./webpackImporter");
const normalizeOptions = require("./normalizeOptions");
const pify = require("pify");
const semver = require("semver");
const path = require('path');

const async = require('neo-async');
const pify = require('pify');
const semver = require('semver');

const formatSassError = require('./formatSassError');
const webpackImporter = require('./webpackImporter');
const normalizeOptions = require('./normalizeOptions');

let nodeSassJobQueue = null;

// Very hacky check
function hasGetResolve(loaderContext) {
return (
loaderContext.getResolve &&
// eslint-disable-next-line no-underscore-dangle
loaderContext._compiler &&
// eslint-disable-next-line no-underscore-dangle
loaderContext._compiler.resolverFactory &&
// eslint-disable-next-line no-underscore-dangle
loaderContext._compiler.resolverFactory._create &&
/cachedCleverMerge/.test(
// eslint-disable-next-line no-underscore-dangle
loaderContext._compiler.resolverFactory._create.toString()
)
);
}

/**
* The sass-loader makes node-sass available to webpack modules.
* The sass-loader makes node-sass and dart-sass available to webpack modules.
*
* @this {LoaderContext}
* @param {string} content
*/
function sassLoader(content) {
const callback = this.async();
const isSync = typeof callback !== "function";
const self = this;
const resourcePath = this.resourcePath;

function addNormalizedDependency(file) {
// node-sass returns POSIX paths
self.dependency(path.normalize(file));
const callback = this.async();
const isSync = typeof callback !== 'function';
const self = this;
const { resourcePath } = this;

function addNormalizedDependency(file) {
// node-sass returns POSIX paths
self.dependency(path.normalize(file));
}

if (isSync) {
throw new Error(
'Synchronous compilation is not supported anymore. See https://github.com/webpack-contrib/sass-loader/issues/333'
);
}

let resolve = pify(this.resolve);

// Supported since v4.36.0
if (hasGetResolve(self)) {
resolve = this.getResolve({
mainFields: ['sass', 'style', '...'],
extensions: ['.scss', '.sass', '.css', '...'],
});
}

const options = normalizeOptions(
this,
content,
webpackImporter(resourcePath, resolve, addNormalizedDependency)
);

// Skip empty files, otherwise it will stop webpack, see issue #21
if (options.data.trim() === '') {
callback(null, '');
return;
}

const render = getRenderFuncFromSassImpl(
// eslint-disable-next-line import/no-extraneous-dependencies, global-require
options.implementation || getDefaultSassImpl()
);

render(options, (err, result) => {
if (err) {
formatSassError(err, this.resourcePath);

if (err.file) {
this.dependency(err.file);
}

callback(err);
return;
}

if (isSync) {
throw new Error("Synchronous compilation is not supported anymore. See https://github.com/webpack-contrib/sass-loader/issues/333");
if (result.map && result.map !== '{}') {
// eslint-disable-next-line no-param-reassign
result.map = JSON.parse(result.map);
// result.map.file is an optional property that provides the output filename.
// Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it.
// eslint-disable-next-line no-param-reassign
delete result.map.file;
// One of the sources is 'stdin' according to dart-sass/node-sass because we've used the data input.
// Now let's override that value with the correct relative path.
// Since we specified options.sourceMap = path.join(process.cwd(), "/sass.map"); in normalizeOptions,
// we know that this path is relative to process.cwd(). This is how node-sass works.
// eslint-disable-next-line no-param-reassign
const stdinIndex = result.map.sources.findIndex(
(source) => source.indexOf('stdin') !== -1
);

if (stdinIndex !== -1) {
// eslint-disable-next-line no-param-reassign
result.map.sources[stdinIndex] = path.relative(
process.cwd(),
resourcePath
);
}
// node-sass returns POSIX paths, that's why we need to transform them back to native paths.
// This fixes an error on windows where the source-map module cannot resolve the source maps.
// @see https://github.com/webpack-contrib/sass-loader/issues/366#issuecomment-279460722
// eslint-disable-next-line no-param-reassign
result.map.sourceRoot = path.normalize(result.map.sourceRoot);
// eslint-disable-next-line no-param-reassign
result.map.sources = result.map.sources.map(path.normalize);
} else {
// eslint-disable-next-line no-param-reassign
result.map = null;
}

const options = normalizeOptions(this, content, webpackImporter(
resourcePath,
pify(this.resolve.bind(this)),
addNormalizedDependency
));
result.stats.includedFiles.forEach(addNormalizedDependency);

// Skip empty files, otherwise it will stop webpack, see issue #21
if (options.data.trim() === "") {
callback(null, "");
return;
}

const render = getRenderFuncFromSassImpl(options.implementation || require("node-sass"));

render(options, (err, result) => {
if (err) {
formatSassError(err, this.resourcePath);
err.file && this.dependency(err.file);
callback(err);
return;
}

if (result.map && result.map !== "{}") {
result.map = JSON.parse(result.map);
// result.map.file is an optional property that provides the output filename.
// Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it.
delete result.map.file;
// The first source is 'stdin' according to node-sass because we've used the data input.
// Now let's override that value with the correct relative path.
// Since we specified options.sourceMap = path.join(process.cwd(), "/sass.map"); in normalizeOptions,
// we know that this path is relative to process.cwd(). This is how node-sass works.
result.map.sources[0] = path.relative(process.cwd(), resourcePath);
// node-sass returns POSIX paths, that's why we need to transform them back to native paths.
// This fixes an error on windows where the source-map module cannot resolve the source maps.
// @see https://github.com/webpack-contrib/sass-loader/issues/366#issuecomment-279460722
result.map.sourceRoot = path.normalize(result.map.sourceRoot);
result.map.sources = result.map.sources.map(path.normalize);
} else {
result.map = null;
}

result.stats.includedFiles.forEach(addNormalizedDependency);
callback(null, result.css.toString(), result.map);
});
callback(null, result.css.toString(), result.map);
});
}

/**
@@ -84,41 +139,68 @@ function sassLoader(content) {
* @returns {Function}
*/
function getRenderFuncFromSassImpl(module) {
const info = module.info;
const components = info.split("\t");
const { info } = module;
const components = info.split('\t');

if (components.length < 2) {
throw new Error(`Unknown Sass implementation "${info}".`);
}

if (components.length < 2) {
throw new Error("Unknown Sass implementation \"" + info + "\".");
const [implementation, version] = components;

if (!semver.valid(version)) {
throw new Error(`Invalid Sass version "${version}".`);
}

if (implementation === 'dart-sass') {
if (!semver.satisfies(version, '^1.3.0')) {
throw new Error(
`Dart Sass version ${version} is incompatible with ^1.3.0.`
);
}

return module.render.bind(module);
} else if (implementation === 'node-sass') {
if (!semver.satisfies(version, '^4.0.0')) {
throw new Error(
`Node Sass version ${version} is incompatible with ^4.0.0.`
);
}

const implementation = components[0];
const version = components[1];
// There is an issue with node-sass when async custom importers are used
// See https://github.com/sass/node-sass/issues/857#issuecomment-93594360
// We need to use a job queue to make sure that one thread is always available to the UV lib
if (nodeSassJobQueue === null) {
const threadPoolSize = Number(process.env.UV_THREADPOOL_SIZE || 4);

if (!semver.valid(version)) {
throw new Error("Invalid Sass version \"" + version + "\".");
nodeSassJobQueue = async.queue(
module.render.bind(module),
threadPoolSize - 1
);
}

if (implementation === "dart-sass") {
if (!semver.satisfies(version, "^1.3.0")) {
throw new Error("Dart Sass version " + version + " is incompatible with ^1.3.0.");
}
return module.render.bind(module);
} else if (implementation === "node-sass") {
if (!semver.satisfies(version, "^4.0.0")) {
throw new Error("Node Sass version " + version + " is incompatible with ^4.0.0.");
}
// There is an issue with node-sass when async custom importers are used
// See https://github.com/sass/node-sass/issues/857#issuecomment-93594360
// We need to use a job queue to make sure that one thread is always available to the UV lib
if (nodeSassJobQueue === null) {
const threadPoolSize = Number(process.env.UV_THREADPOOL_SIZE || 4);

nodeSassJobQueue = async.queue(module.render.bind(module), threadPoolSize - 1);
}

return nodeSassJobQueue.push.bind(nodeSassJobQueue);
return nodeSassJobQueue.push.bind(nodeSassJobQueue);
}

throw new Error(`Unknown Sass implementation "${implementation}".`);
}

function getDefaultSassImpl() {
let sassImplPkg = 'node-sass';

try {
require.resolve('node-sass');
} catch (error) {
try {
require.resolve('sass');
sassImplPkg = 'sass';
} catch (ignoreError) {
sassImplPkg = 'node-sass';
}
throw new Error("Unknown Sass implementation \"" + implementation + "\".");
}

// eslint-disable-next-line import/no-dynamic-require, global-require
return require(sassImplPkg);
}

module.exports = sassLoader;
123 changes: 71 additions & 52 deletions lib/normalizeOptions.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"use strict";
'use strict';

const os = require("os");
const utils = require("loader-utils");
const cloneDeep = require("clone-deep");
const path = require("path");
const proxyCustomImporters = require("./proxyCustomImporters");
const os = require('os');
const path = require('path');

const utils = require('loader-utils');
const cloneDeep = require('clone-deep');

const proxyCustomImporters = require('./proxyCustomImporters');

/**
* Derives the sass options from the loader context and normalizes its values with sane defaults.
@@ -18,61 +20,78 @@ const proxyCustomImporters = require("./proxyCustomImporters");
* @returns {Object}
*/
function normalizeOptions(loaderContext, content, webpackImporter) {
const options = cloneDeep(utils.getOptions(loaderContext)) || {};
const resourcePath = loaderContext.resourcePath;
const options = cloneDeep(utils.getOptions(loaderContext)) || {};
const { resourcePath } = loaderContext;

options.data = options.data ? (options.data + os.EOL + content) : content;
// allow opt.functions to be configured WRT loaderContext
if (typeof options.functions === 'function') {
options.functions = options.functions(loaderContext);
}

// opt.outputStyle
if (!options.outputStyle && loaderContext.minimize) {
options.outputStyle = "compressed";
}
let { data } = options;

// opt.sourceMap
// Not using the `this.sourceMap` flag because css source maps are different
// @see https://github.com/webpack/css-loader/pull/40
if (options.sourceMap) {
// Deliberately overriding the sourceMap option here.
// node-sass won't produce source maps if the data option is used and options.sourceMap is not a string.
// In case it is a string, options.sourceMap should be a path where the source map is written.
// But since we're using the data option, the source map will not actually be written, but
// all paths in sourceMap.sources will be relative to that path.
// Pretty complicated... :(
options.sourceMap = path.join(process.cwd(), "/sass.map");
if ("sourceMapRoot" in options === false) {
options.sourceMapRoot = process.cwd();
}
if ("omitSourceMapUrl" in options === false) {
// The source map url doesn't make sense because we don't know the output path
// The css-loader will handle that for us
options.omitSourceMapUrl = true;
}
if ("sourceMapContents" in options === false) {
// If sourceMapContents option is not set, set it to true otherwise maps will be empty/null
// when exported by webpack-extract-text-plugin.
options.sourceMapContents = true;
}
}
if (typeof options.data === 'function') {
data = options.data(loaderContext);
}

options.data = data ? data + os.EOL + content : content;

// indentedSyntax is a boolean flag.
const ext = path.extname(resourcePath);
// opt.outputStyle
if (!options.outputStyle && loaderContext.minimize) {
options.outputStyle = 'compressed';
}

// If we are compiling sass and indentedSyntax isn't set, automatically set it.
if (ext && ext.toLowerCase() === ".sass" && "indentedSyntax" in options === false) {
options.indentedSyntax = true;
} else {
options.indentedSyntax = Boolean(options.indentedSyntax);
// opt.sourceMap
// Not using the `this.sourceMap` flag because css source maps are different
// @see https://github.com/webpack/css-loader/pull/40
if (options.sourceMap) {
// Deliberately overriding the sourceMap option here.
// node-sass won't produce source maps if the data option is used and options.sourceMap is not a string.
// In case it is a string, options.sourceMap should be a path where the source map is written.
// But since we're using the data option, the source map will not actually be written, but
// all paths in sourceMap.sources will be relative to that path.
// Pretty complicated... :(
options.sourceMap = path.join(process.cwd(), '/sass.map');
if ('sourceMapRoot' in options === false) {
options.sourceMapRoot = process.cwd();
}
if ('omitSourceMapUrl' in options === false) {
// The source map url doesn't make sense because we don't know the output path
// The css-loader will handle that for us
options.omitSourceMapUrl = true;
}
if ('sourceMapContents' in options === false) {
// If sourceMapContents option is not set, set it to true otherwise maps will be empty/null
// when exported by webpack-extract-text-plugin.
options.sourceMapContents = true;
}
}

// indentedSyntax is a boolean flag.
const ext = path.extname(resourcePath);

// If we are compiling sass and indentedSyntax isn't set, automatically set it.
if (
ext &&
ext.toLowerCase() === '.sass' &&
'indentedSyntax' in options === false
) {
options.indentedSyntax = true;
} else {
options.indentedSyntax = Boolean(options.indentedSyntax);
}

// Allow passing custom importers to `node-sass`. Accepts `Function` or an array of `Function`s.
options.importer = options.importer ? proxyCustomImporters(options.importer, resourcePath) : [];
options.importer.push(webpackImporter);
// Allow passing custom importers to `node-sass`. Accepts `Function` or an array of `Function`s.
options.importer = options.importer
? proxyCustomImporters(options.importer, resourcePath)
: [];
options.importer.push(webpackImporter);

// `node-sass` uses `includePaths` to resolve `@import` paths. Append the currently processed file.
options.includePaths = options.includePaths || [];
options.includePaths.push(path.dirname(resourcePath));
// `node-sass` uses `includePaths` to resolve `@import` paths. Append the currently processed file.
options.includePaths = options.includePaths || [];
options.includePaths.push(path.dirname(resourcePath));

return options;
return options;
}

module.exports = normalizeOptions;
28 changes: 16 additions & 12 deletions lib/proxyCustomImporters.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"use strict";
'use strict';

/**
* Creates new custom importers that use the given `resourcePath` if libsass calls the custom importer with `prev`
@@ -10,20 +10,24 @@
*
* We have to fix this behavior in order to provide a consistent experience to the webpack user.
*
* @param {function|Array<function>} importer
* @param {Function|Array<Function>} importer
* @param {string} resourcePath
* @returns {Array<function>}
* @returns {Array<Function>}
*/
function proxyCustomImporters(importer, resourcePath) {
return [].concat(importer).map((importer) => {
return function (url, prev, done) {
return importer.apply(
this, // eslint-disable-line no-invalid-this
Array.from(arguments)
.map((arg, i) => i === 1 && arg === "stdin" ? resourcePath : arg)
);
};
});
return [].concat(importer).map(
// eslint-disable-next-line no-shadow
(importer) =>
function customImporter() {
return importer.apply(
this,
// eslint-disable-next-line prefer-rest-params
Array.from(arguments).map((arg, i) =>
i === 1 && arg === 'stdin' ? resourcePath : arg
)
);
}
);
}

module.exports = proxyCustomImporters;
76 changes: 41 additions & 35 deletions lib/webpackImporter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"use strict";
'use strict';

/**
* @name PromisedResolve
@@ -16,9 +16,9 @@
* @param {Function<Error, string>} done
*/

const path = require("path");
const tail = require("lodash.tail");
const importsToResolve = require("./importsToResolve");
const path = require('path');

const importsToResolve = require('./importsToResolve');

const matchCss = /\.css$/;

@@ -34,39 +34,45 @@ const matchCss = /\.css$/;
* @returns {Importer}
*/
function webpackImporter(resourcePath, resolve, addNormalizedDependency) {
function dirContextFrom(fileContext) {
return path.dirname(
// The first file is 'stdin' when we're using the data option
fileContext === "stdin" ? resourcePath : fileContext
);
}
function dirContextFrom(fileContext) {
return path.dirname(
// The first file is 'stdin' when we're using the data option
fileContext === 'stdin' ? resourcePath : fileContext
);
}

function startResolving(dir, importsToResolve) {
return importsToResolve.length === 0 ?
Promise.reject() :
resolve(dir, importsToResolve[0])
.then(resolvedFile => {
// Add the resolvedFilename as dependency. Although we're also using stats.includedFiles, this might come
// in handy when an error occurs. In this case, we don't get stats.includedFiles from node-sass.
addNormalizedDependency(resolvedFile);
return {
// By removing the CSS file extension, we trigger node-sass to include the CSS file instead of just linking it.
file: resolvedFile.replace(matchCss, "")
};
}, () => startResolving(
dir,
tail(importsToResolve)
));
}
// eslint-disable-next-line no-shadow
function startResolving(dir, importsToResolve) {
return importsToResolve.length === 0
? Promise.reject()
: resolve(dir, importsToResolve[0]).then(
(resolvedFile) => {
// Add the resolvedFilename as dependency. Although we're also using stats.includedFiles, this might come
// in handy when an error occurs. In this case, we don't get stats.includedFiles from node-sass.
addNormalizedDependency(resolvedFile);
return {
// By removing the CSS file extension, we trigger node-sass to include the CSS file instead of just linking it.
file: resolvedFile.replace(matchCss, ''),
};
},
() => {
const [, ...tailResult] = importsToResolve;

return startResolving(dir, tailResult);
}
);
}

return (url, prev, done) => {
startResolving(
dirContextFrom(prev),
importsToResolve(url)
) // Catch all resolving errors, return the original file and pass responsibility back to other custom importers
.catch(() => ({ file: url }))
.then(done);
};
return (url, prev, done) => {
startResolving(dirContextFrom(prev), importsToResolve(url))
// Catch all resolving errors, return the original file and pass responsibility back to other custom importers
.catch(() => {
return {
file: url,
};
})
.then(done);
};
}

module.exports = webpackImporter;
15,203 changes: 7,876 additions & 7,327 deletions package-lock.json

Large diffs are not rendered by default.

67 changes: 46 additions & 21 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sass-loader",
"version": "7.1.0",
"version": "7.2.0",
"description": "Sass loader for webpack",
"author": "J. Tangelder",
"license": "MIT",
@@ -12,49 +12,58 @@
"appveyor:test": "npm test",
"create-spec": "node test/tools/runCreateSpec.js",
"lint": "eslint lib test",
"test": "nyc --all mocha -R spec -t 10000",
"test": "nyc mocha -R spec -t 10000",
"test-bootstrap-sass": "webpack-dev-server --config test/bootstrapSass/webpack.config.js --content-base ./test/bootstrapSass",
"test-source-map": "webpack-dev-server --config test/sourceMap/webpack.config.js --content-base ./test/sourceMap --inline",
"test-watch": "webpack --config test/watch/webpack.config.js",
"test-extract-text": "webpack --config test/extractText/webpack.config.js",
"test-hmr": "webpack-dev-server --config test/hmr/webpack.config.js --content-base ./test/hmr --hot --inline",
"travis:lint": "npm run lint",
"travis:lint:commits": "commitlint --from=${TRAVIS_BRANCH} --to=${TRAVIS_COMMIT}",
"travis:lint": "npm run lint && npm run travis:lint:commits",
"travis:test": "npm run test",
"travis:coverage": "npm run test",
"pretest": "npm run create-spec",
"posttest": "npm run lint",
"release": "standard-version"
},
"dependencies": {
"clone-deep": "^2.0.1",
"clone-deep": "^4.0.1",
"loader-utils": "^1.0.1",
"lodash.tail": "^4.1.1",
"neo-async": "^2.5.0",
"pify": "^3.0.0",
"pify": "^4.0.1",
"semver": "^5.5.0"
},
"devDependencies": {
"@commitlint/cli": "^7.2.1",
"@commitlint/config-conventional": "^7.1.2",
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"bootstrap-sass": "^3.3.5",
"css-loader": "^0.28.4",
"eslint": "^3.16.0",
"eslint-config-peerigon": "^9.0.0",
"eslint-plugin-jsdoc": "^2.4.0",
"file-loader": "^0.11.2",
"mocha": "^3.0.2",
"css-loader": "^2.0.0",
"eslint": "^5.10.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsdoc": "^4.4.3",
"eslint-plugin-prettier": "^3.0.0",
"file-loader": "^3.0.1",
"husky": "^1.2.0",
"lint-staged": "^8.1.0",
"mocha": "^6.0.2",
"mock-require": "^3.0.1",
"node-sass": "^4.5.0",
"nyc": "^11.0.2",
"raw-loader": "^0.5.1",
"nyc": "^13.1.0",
"raw-loader": "^1.0.0",
"prettier": "^1.15.2",
"sass": "^1.3.0",
"should": "^11.2.0",
"standard-version": "^4.2.0",
"style-loader": "^0.18.2",
"should": "^13.2.3",
"standard-version": "^5.0.2",
"style-loader": "^0.23.1",
"webpack": "^4.5.0",
"webpack-dev-server": "^2.4.1",
"webpack-merge": "^4.0.0"
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.4",
"webpack-merge": "^4.0.0",
"chokidar": "^2.1.6"
},
"engines": {
"node": ">= 6.9.0 || >= 8.9.0"
"node": ">= 6.9.0"
},
"peerDependencies": {
"webpack": "^3.0.0 || ^4.0.0"
@@ -67,5 +76,21 @@
],
"repository": "https://github.com/webpack-contrib/sass-loader.git",
"bugs": "https://github.com/webpack-contrib/sass-loader/issues",
"homepage": "https://github.com/webpack-contrib/sass-loader"
"homepage": "https://github.com/webpack-contrib/sass-loader",
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.js": [
"eslint --fix",
"git add"
]
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
}
}
5 changes: 0 additions & 5 deletions test/.eslintrc.json

This file was deleted.

70 changes: 39 additions & 31 deletions test/bootstrapSass/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,43 @@
"use strict";
'use strict';

const path = require("path");
const sassLoader = require.resolve("../../lib/loader");
const path = require('path');

const sassLoader = require.resolve('../../lib/loader');

module.exports = {
entry: path.resolve(__dirname, "../scss/bootstrap-sass.scss"),
output: {
path: path.resolve(__dirname, "../output"),
filename: "bundle.bootstrap-sass.js"
},
devtool: "source-map",
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: sassLoader,
options: {
includePaths: [
path.resolve(__dirname, "../scss/includePath")
]
}
}]
}, {
test: /\.woff2?$|\.ttf$|\.eot$|\.svg$/,
use: [{
loader: "file-loader"
}]
}]
}
entry: path.resolve(__dirname, '../scss/bootstrap-sass.scss'),
output: {
path: path.resolve(__dirname, '../output'),
filename: 'bundle.bootstrap-sass.js',
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: sassLoader,
options: {
includePaths: [path.resolve(__dirname, '../scss/includePath')],
},
},
],
},
{
test: /\.woff2?$|\.ttf$|\.eot$|\.svg$/,
use: [
{
loader: 'file-loader',
},
],
},
],
},
};
60 changes: 33 additions & 27 deletions test/extractText/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,40 @@
"use strict";
'use strict';

const path = require("path");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const sassLoader = require.resolve("../../lib/loader");
const path = require('path');

// eslint-disable-next-line import/no-unresolved
const ExtractTextPlugin = require('extract-text-webpack-plugin');

const sassLoader = require.resolve('../../lib/loader');

const extractSass = new ExtractTextPlugin({
filename: "[name].[contenthash].css",
disable: process.env.NODE_ENV === "development"
filename: '[name].[contenthash].css',
disable: process.env.NODE_ENV === 'development',
});

module.exports = {
entry: require.resolve("../scss/language.scss"),
output: {
path: path.resolve(__dirname, "../output"),
filename: "bundle.extractText.js"
},
module: {
rules: [{
test: /\.scss$/,
loader: extractSass.extract({
loader: [{
loader: "css-loader"
}, {
loader: sassLoader
}],
fallbackLoader: "style-loader"
})
}]
},
plugins: [
extractSass
]
entry: require.resolve('../scss/language.scss'),
output: {
path: path.resolve(__dirname, '../output'),
filename: 'bundle.extractText.js',
},
module: {
rules: [
{
test: /\.scss$/,
loader: extractSass.extract({
loader: [
{
loader: 'css-loader',
},
{
loader: sassLoader,
},
],
fallbackLoader: 'style-loader',
}),
},
],
},
plugins: [extractSass],
};
7 changes: 4 additions & 3 deletions test/hmr/entry.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"use strict";
'use strict';

/* global document */

require("./simple.scss");
require('./simple.scss');

setInterval(() => {
document.body.innerHTML += "<br>Now we just change the DOM, so that we can ensure that webpack is not just reloading the page";
document.body.innerHTML +=
'<br>Now we just change the DOM, so that we can ensure that webpack is not just reloading the page';
}, 2000);
47 changes: 27 additions & 20 deletions test/hmr/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
"use strict";
'use strict';

const path = require("path");
const sassLoader = require.resolve("../../lib/loader");
const path = require('path');

const sassLoader = require.resolve('../../lib/loader');

module.exports = {
entry: path.resolve(__dirname, "./entry.js"),
output: {
path: path.resolve(__dirname, "../output"),
filename: "bundle.hmr.js"
},
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: sassLoader
}]
}]
}
entry: path.resolve(__dirname, './entry.js'),
output: {
path: path.resolve(__dirname, '../output'),
filename: 'bundle.hmr.js',
},
module: {
rules: [
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: sassLoader,
},
],
},
],
},
};
1,044 changes: 654 additions & 390 deletions test/index.test.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions test/node_modules/@org/style.js
3 changes: 3 additions & 0 deletions test/node_modules/@org/style.scss
2 changes: 2 additions & 0 deletions test/node_modules/sass-custom-sass-field/nested/style.sass
12 changes: 12 additions & 0 deletions test/node_modules/sass-custom-sass-field/package.json
2 changes: 2 additions & 0 deletions test/node_modules/sass-main-field/nested/style.sass
11 changes: 11 additions & 0 deletions test/node_modules/sass-main-field/package.json
2 changes: 2 additions & 0 deletions test/node_modules/sass-package-with-index/index.sass
2 changes: 2 additions & 0 deletions test/node_modules/sass-sass-field/nested/style.sass
12 changes: 12 additions & 0 deletions test/node_modules/sass-sass-field/package.json
2 changes: 2 additions & 0 deletions test/node_modules/sass-style-field/nested/style.sass
12 changes: 12 additions & 0 deletions test/node_modules/sass-style-field/package.json
3 changes: 3 additions & 0 deletions test/node_modules/scss-custom-sass-field/nested/style.scss
12 changes: 12 additions & 0 deletions test/node_modules/scss-custom-sass-field/package.json
3 changes: 3 additions & 0 deletions test/node_modules/scss-main-field/nested/style.scss
11 changes: 11 additions & 0 deletions test/node_modules/scss-main-field/package.json
3 changes: 3 additions & 0 deletions test/node_modules/scss-package-with-index/index.scss
3 changes: 3 additions & 0 deletions test/node_modules/scss-sass-field/nested/style.scss
12 changes: 12 additions & 0 deletions test/node_modules/scss-sass-field/package.json
3 changes: 3 additions & 0 deletions test/node_modules/scss-style-field/nested/style.scss
12 changes: 12 additions & 0 deletions test/node_modules/scss-style-field/package.json
1 change: 1 addition & 0 deletions test/node_modules/scss/style.js
3 changes: 3 additions & 0 deletions test/node_modules/scss/style.scss
1 change: 1 addition & 0 deletions test/sass/import-custom-sass-field.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "~sass-custom-sass-field"
1 change: 1 addition & 0 deletions test/sass/import-index.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "~sass-package-with-index"
1 change: 1 addition & 0 deletions test/sass/import-main-field.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "~sass-main-field"
1 change: 1 addition & 0 deletions test/sass/import-sass-field.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "~sass-sass-field"
1 change: 1 addition & 0 deletions test/sass/import-style-field.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "~sass-style-field"
2 changes: 2 additions & 0 deletions test/sass/imports.sass
Original file line number Diff line number Diff line change
@@ -24,3 +24,5 @@
@import ~module
/* @import ~another */
@import ~another
// Should prefer `scss`
@import "~@org/style"
3 changes: 2 additions & 1 deletion test/scss/another/module.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ./another/module.js: The sass-loader should not try to import that. scss, sass and css extensions should be preferred.
// See https://github.com/webpack-contrib/sass-loader/issues/556#issuecomment-381154009
"This should not be imported";

'This should not be imported';
1 change: 1 addition & 0 deletions test/scss/import-custom-sass-field.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "~scss-custom-sass-field";
1 change: 1 addition & 0 deletions test/scss/import-index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "~scss-package-with-index";
1 change: 1 addition & 0 deletions test/scss/import-main-field.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "~scss-main-field";
1 change: 1 addition & 0 deletions test/scss/import-sass-field.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "~scss-sass-field";
1 change: 1 addition & 0 deletions test/scss/import-style-field.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "~scss-style-field";
3 changes: 3 additions & 0 deletions test/scss/imports.scss
Original file line number Diff line number Diff line change
@@ -24,3 +24,6 @@
@import "~module";
/* @import "~another"; */
@import "~another";
/* @import "~@org/style"; */
// Should prefer `scss`
@import "~@org/style";
7 changes: 7 additions & 0 deletions test/scss/simple.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
$font-stack: Helvetica, sans-serif;
$primary-color: #333;

body {
font: 100% $font-stack;
color: $primary-color;
}
57 changes: 33 additions & 24 deletions test/sourceMap/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
"use strict";
'use strict';

const path = require("path");
const sassLoader = require.resolve("../../lib/loader");
const path = require('path');

const sassLoader = require.resolve('../../lib/loader');

module.exports = {
entry: path.resolve(__dirname, "..", "scss", "imports.scss"),
output: {
filename: "bundle.js"
},
devtool: "source-map",
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader", options: {
sourceMap: true
}
}, {
loader: sassLoader, options: {
sourceMap: true
}
}]
}]
}
entry: path.resolve(__dirname, '..', 'scss', 'imports.scss'),
output: {
filename: 'bundle.js',
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
sourceMap: true,
},
},
{
loader: sassLoader,
options: {
sourceMap: true,
},
},
],
},
],
},
};
64 changes: 31 additions & 33 deletions test/spec.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"use strict";
'use strict';

/**
* This file can be used to track changes in the spec introduced by newer node-sass versions.
@@ -13,53 +13,51 @@
* 5. Run `npm run test`
*/

require("should");
const fs = require("fs");
const path = require("path");
const createSpec = require("./tools/createSpec.js");
require('should');
const fs = require('fs');
const path = require('path');

const createSpec = require('./tools/createSpec.js');

const testFolder = __dirname;
const matchCss = /\.css$/;

function readSpec(folder) {
const result = {};
const result = {};

fs.readdirSync(folder)
.forEach((file) => {
if (matchCss.test(file)) {
result[file] = fs.readFileSync(path.join(folder, file), "utf8");
}
});
fs.readdirSync(folder).forEach((file) => {
if (matchCss.test(file)) {
result[file] = fs.readFileSync(path.join(folder, file), 'utf8');
}
});

return result;
return result;
}

function writeSpec(folder, spec) {
Object.keys(spec)
.forEach((specName) => {
fs.writeFileSync(path.resolve(folder, specName), spec[specName], "utf8");
});
Object.keys(spec).forEach((specName) => {
fs.writeFileSync(path.resolve(folder, specName), spec[specName], 'utf8');
});
}

["scss", "sass"].forEach((ext) => {
describe(ext + " spec", () => {
const specFolder = path.resolve(testFolder, ext, "spec");
const oldSpec = readSpec(specFolder);
['scss', 'sass'].forEach((ext) => {
describe(`${ext} spec`, () => {
const specFolder = path.resolve(testFolder, ext, 'spec');
const oldSpec = readSpec(specFolder);

createSpec(ext);
createSpec(ext);

const newSpec = readSpec(specFolder);
const newSpec = readSpec(specFolder);

Object.keys(oldSpec)
.forEach((specName) => {
it(specName + " should not have been changed", () => {
oldSpec[specName].should.eql(newSpec[specName]);
});
});
Object.keys(oldSpec).forEach((specName) => {
it(`${specName} should not have been changed`, () => {
oldSpec[specName].should.eql(newSpec[specName]);
});
});

after(() => {
// Write old spec back to the folder so that future tests will also fail
writeSpec(specFolder, oldSpec);
});
after(() => {
// Write old spec back to the folder so that future tests will also fail
writeSpec(specFolder, oldSpec);
});
});
});
256 changes: 189 additions & 67 deletions test/tools/createSpec.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,203 @@
"use strict";
'use strict';

const nodeSass = require("node-sass");
const dartSass = require("sass");
const os = require("os");
const fs = require("fs");
const path = require("path");
const customImporter = require("./customImporter.js");
const customFunctions = require("./customFunctions.js");
const fs = require('fs');
const path = require('path');
const os = require('os');

const dartSass = require('sass');
const nodeSass = require('node-sass');

const customImporter = require('./customImporter.js');
const customFunctions = require('./customFunctions.js');

const implementations = [nodeSass, dartSass];
const testFolder = path.resolve(__dirname, "../");
const error = "error";
const testFolder = path.resolve(__dirname, '../');
const error = 'error';

function createSpec(ext) {
const basePath = path.join(testFolder, ext);
const testNodeModules = path.relative(basePath, path.join(testFolder, "node_modules")) + path.sep;
const pathToBootstrap = path.relative(basePath, path.resolve(testFolder, "..", "node_modules", "bootstrap-sass"));
const pathToScopedNpmPkg = path.relative(basePath, path.resolve(testFolder, "node_modules", "@org", "pkg", "./index.scss"));
const pathToModule = path.relative(basePath, path.resolve(testFolder, "node_modules", "module", "module.scss"));
const pathToAnother = path.relative(basePath, path.resolve(testFolder, "node_modules", "another", "module.scss"));
const pathToFooAlias = path.relative(basePath, path.resolve(testFolder, ext, "another", "alias." + ext));
const basePath = path.join(testFolder, ext);
const testNodeModules =
path.relative(basePath, path.join(testFolder, 'node_modules')) + path.sep;
const pathToBootstrap = path.relative(
basePath,
path.resolve(testFolder, '..', 'node_modules', 'bootstrap-sass')
);
const pathToScopedNpmPkg = path.relative(
basePath,
path.resolve(testFolder, 'node_modules', '@org', 'pkg', './index.scss')
);
const pathToModule = path.relative(
basePath,
path.resolve(testFolder, 'node_modules', 'module', 'module.scss')
);
const pathToAnother = path.relative(
basePath,
path.resolve(testFolder, 'node_modules', 'another', 'module.scss')
);
const pathToFooAlias = path.relative(
basePath,
path.resolve(testFolder, ext, 'another', `alias.${ext}`)
);
const pathToScssSassField = path.relative(
basePath,
path.resolve(
testFolder,
'node_modules',
'scss-sass-field',
'nested',
'style.scss'
)
);
const pathToScssStyleField = path.relative(
basePath,
path.resolve(
testFolder,
'node_modules',
'scss-style-field',
'nested',
'style.scss'
)
);
const pathToScssCustomSassField = path.relative(
basePath,
path.resolve(
testFolder,
'node_modules',
'scss-custom-sass-field',
'nested',
'style.scss'
)
);
const pathToScssMainField = path.relative(
basePath,
path.resolve(
testFolder,
'node_modules',
'scss-main-field',
'nested',
'style.scss'
)
);
const pathToSASSSassField = path.relative(
basePath,
path.resolve(
testFolder,
'node_modules',
'sass-sass-field',
'nested',
'style.sass'
)
);
const pathToSASSStyleField = path.relative(
basePath,
path.resolve(
testFolder,
'node_modules',
'sass-style-field',
'nested',
'style.sass'
)
);
const pathToSASSCustomSassField = path.relative(
basePath,
path.resolve(
testFolder,
'node_modules',
'sass-custom-sass-field',
'nested',
'style.sass'
)
);
const pathToSASSMainField = path.relative(
basePath,
path.resolve(
testFolder,
'node_modules',
'sass-main-field',
'nested',
'style.sass'
)
);

fs.readdirSync(path.join(testFolder, ext))
.filter(
(file) =>
path.extname(file) === `.${ext}` &&
file.slice(0, error.length) !== error
)
.forEach((file) => {
const fileName = path.join(basePath, file);
const fileWithoutExt = file.slice(0, -ext.length - 1);
const sassOptions = {
importer(url) {
if (url === 'import-with-custom-logic') {
return customImporter.returnValue;
}
// Do not transform css imports
if (/\.css$/.test(url) === false) {
// eslint-disable-next-line no-param-reassign
url = url
.replace(/^~scss-sass-field/, pathToScssSassField)
.replace(/^~scss-style-field/, pathToScssStyleField)
.replace(/^~scss-custom-sass-field/, pathToScssCustomSassField)
.replace(/^~scss-main-field/, pathToScssMainField)
.replace(/^~sass-sass-field/, pathToSASSSassField)
.replace(/^~sass-style-field/, pathToSASSStyleField)
.replace(/^~sass-custom-sass-field/, pathToSASSCustomSassField)
.replace(/^~sass-main-field/, pathToSASSMainField)
.replace(/^~bootstrap-sass/, pathToBootstrap)
.replace(/^~@org\/pkg/, pathToScopedNpmPkg)
.replace(/^~module/, pathToModule)
.replace(/^~another/, pathToAnother)
.replace(/^~/, testNodeModules)
.replace(/^path-to-alias/, pathToFooAlias);
}
return {
file: url,
};
},
includePaths: [
path.join(testFolder, ext, 'another'),
path.join(testFolder, ext, 'includePath'),
],
};

fs.readdirSync(path.join(testFolder, ext))
.filter((file) => {
return path.extname(file) === "." + ext && file.slice(0, error.length) !== error;
})
.map((file) => {
const fileName = path.join(basePath, file);
const fileWithoutExt = file.slice(0, -ext.length - 1);
const sassOptions = {
importer(url) {
if (url === "import-with-custom-logic") {
return customImporter.returnValue;
}
if (/\.css$/.test(url) === false) { // Do not transform css imports
url = url
.replace(/^~bootstrap-sass/, pathToBootstrap)
.replace(/^~@org\/pkg/, pathToScopedNpmPkg)
.replace(/^~module/, pathToModule)
.replace(/^~another/, pathToAnother)
.replace(/^~/, testNodeModules)
.replace(/^path-to-alias/, pathToFooAlias);
}
return {
file: url
};
},
includePaths: [
path.join(testFolder, ext, "another"),
path.join(testFolder, ext, "includePath")
]
};
if (/prepending-data/.test(fileName)) {
sassOptions.indentedSyntax = /\.sass$/.test(fileName);
sassOptions.data = `$prepended-data: hotpink${
sassOptions.indentedSyntax ? '\n' : ';'
}${os.EOL}${fs.readFileSync(fileName, 'utf8')}`;
} else {
sassOptions.file = fileName;
}

if (/prepending-data/.test(fileName)) {
sassOptions.indentedSyntax = /\.sass$/.test(fileName);
sassOptions.data = "$prepended-data: hotpink" + (sassOptions.indentedSyntax ? "\n" : ";") +
os.EOL + fs.readFileSync(fileName, "utf8");
} else {
sassOptions.file = fileName;
}
implementations.forEach((implementation) => {
if (fileWithoutExt === 'import-css' && implementation !== nodeSass) {
// Skip CSS imports for all implementations that are not node-sass
// CSS imports is a legacy feature that we only support for node-sass
// See discussion https://github.com/webpack-contrib/sass-loader/pull/573/files?#r199109203
return;
}

implementations.forEach(implementation => {
if (fileWithoutExt === "import-css" && implementation !== nodeSass) {
// Skip CSS imports for all implementations that are not node-sass
// CSS imports is a legacy feature that we only support for node-sass
// See discussion https://github.com/webpack-contrib/sass-loader/pull/573/files?#r199109203
return;
}
if (fileWithoutExt === 'import-index' && implementation !== dartSass) {
// Skip CSS imports for all implementations that are not node-sass
// CSS imports is a legacy feature that we only support for node-sass
// See discussion https://github.com/webpack-contrib/sass-loader/pull/573/files?#r199109203
return;
}

sassOptions.functions = customFunctions(implementation);
sassOptions.functions = customFunctions(implementation);

const name = implementation.info.split("\t")[0];
const css = implementation.renderSync(sassOptions).css;
const [name] = implementation.info.split('\t');
const { css } = implementation.renderSync(sassOptions);

fs.writeFileSync(path.join(basePath, "spec", name, fileWithoutExt + ".css"), css, "utf8");
});
});
fs.writeFileSync(
path.join(basePath, 'spec', name, `${fileWithoutExt}.css`),
css,
'utf8'
);
});
});
}

module.exports = createSpec;
28 changes: 14 additions & 14 deletions test/tools/customFunctions.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
"use strict";
'use strict';

module.exports = function (implementation) {
return {
"headings($from: 0, $to: 6)"(from, to) {
const f = from.getValue();
const t = to.getValue();
const list = new implementation.types.List(t - f + 1);
let i;
module.exports = (implementation) => {
return {
'headings($from: 0, $to: 6)': (from, to) => {
const f = from.getValue();
const t = to.getValue();
const list = new implementation.types.List(t - f + 1);
let i;

for (i = f; i <= t; i++) {
list.setValue(i - f, new implementation.types.String("h" + i));
}
for (i = f; i <= t; i++) {
list.setValue(i - f, new implementation.types.String(`h${i}`));
}

return list;
}
};
return list;
},
};
};
14 changes: 7 additions & 7 deletions test/tools/customImporter.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
"use strict";
'use strict';

require("should");
require('should');

function customImporter(path, prev) {
path.should.equal("import-with-custom-logic");
prev.should.match(/(sass|scss)[/\\]custom-importer\.(scss|sass)/);
path.should.equal('import-with-custom-logic');
prev.should.match(/(sass|scss)[/\\]custom-importer\.(scss|sass)/);

this.should.have.property("options"); // eslint-disable-line no-invalid-this
this.should.have.property('options'); // eslint-disable-line no-invalid-this

return customImporter.returnValue;
return customImporter.returnValue;
}

customImporter.returnValue = {
contents: ".custom-imported {}"
contents: '.custom-imported {}',
};

module.exports = customImporter;
8 changes: 4 additions & 4 deletions test/tools/runCreateSpec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use strict";
'use strict';

const createSpec = require("./createSpec.js");
const createSpec = require('./createSpec.js');

["scss", "sass"].forEach((ext) => {
createSpec(ext);
['scss', 'sass'].forEach((ext) => {
createSpec(ext);
});
10 changes: 5 additions & 5 deletions test/tools/testLoader.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"use strict";
'use strict';

function testLoader(content, sourceMap) {
testLoader.content = content;
testLoader.sourceMap = sourceMap;
testLoader.content = content;
testLoader.sourceMap = sourceMap;

return "";
return '';
}

testLoader.content = "";
testLoader.content = '';
testLoader.sourceMap = null;
testLoader.filename = __filename;

4 changes: 2 additions & 2 deletions test/watch/entry.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"use strict";
'use strict';

require("../scss/imports.scss");
require('../scss/imports.scss');
61 changes: 33 additions & 28 deletions test/watch/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
"use strict";
'use strict';

const path = require("path");
const sassLoader = require.resolve("../../lib/loader");
const path = require('path');

const sassLoader = require.resolve('../../lib/loader');

module.exports = {
entry: [
path.resolve(__dirname, "../scss/imports.scss"),
path.resolve(__dirname, "../scss/import-include-paths.scss")
entry: [
path.resolve(__dirname, '../scss/imports.scss'),
path.resolve(__dirname, '../scss/import-include-paths.scss'),
],
output: {
path: path.resolve(__dirname, '../output'),
filename: 'bundle.watch.js',
},
watch: true,
module: {
rules: [
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: sassLoader,
options: {
includePaths: [path.resolve(__dirname, '../scss/includePath')],
},
},
],
},
],
output: {
path: path.resolve(__dirname, "../output"),
filename: "bundle.watch.js"
},
watch: true,
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: sassLoader,
options: {
includePaths: [
path.resolve(__dirname, "../scss/includePath")
]
}
}]
}]
}
},
};