Skip to content

Commit 7571164

Browse files
committed
feature #517 Allow to use Dart Sass instead of Node Sass (Lyrkan, weaverryan)
This PR was merged into the master branch. Discussion ---------- Allow to use Dart Sass instead of Node Sass This PR closes #422 by allowing `sass` (Dart Sass) as an alternative choice to `node-sass`. --- The `sass-loader` allows to set the implementation that should be used to process Sass files. It currently accepts either `sass` or `node-sass` (default): In theory this can already be used by setting the related option when calling `Encore.enableSassLoader()`, but Encore then throws an error asking you to install `node-sass`, even if you already have `sass` installed. One way to prevent that could be to detect when `options.implementation` is set but in its next version the `sass-loader` will [automatically detect](webpack-contrib/sass-loader@ff90dd6) which implementation is installed and use it accordingly. So, instead of doing that, this PR allows to define alternative packages in our `feature.js` file, for instance: ```js const features = { sass: { method: 'enableSassLoader()', packages: [ { name: 'sass-loader', enforce_version: true }, [{ name: 'node-sass' }, { name: 'sass' }] // Will allow both `node-sass` and `sass` ], description: 'load Sass files' }, // ... } ``` If neither `node-sass` or `sass` is available, the error message will display both choices, and recommend the first one in the `yarn add` advice: ![image](https://user-images.githubusercontent.com/850046/52538612-0338fa00-2d75-11e9-8d89-146fe7ee27eb.png) Note that for now using Dart Sass still requires the `implementation` option to be set: ```js Encore.enableSassLoader(options => { options.implementation = require('sass'); }); ``` This shouldn't be a big issue since the default recommendation is still to use `node-sass`. Commits ------- 4e90bf8 clarifying comment 1346ee5 Allow to use Dart Sass instead of Node Sass
2 parents 72cde14 + 4e90bf8 commit 7571164

File tree

6 files changed

+128
-31
lines changed

6 files changed

+128
-31
lines changed

lib/features.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ const features = {
2020
method: 'enableSassLoader()',
2121
packages: [
2222
{ name: 'sass-loader', enforce_version: true },
23-
{ name: 'node-sass' }
23+
// allow node-sass or sass to be installed
24+
[{ name: 'node-sass' }, { name: 'sass' }]
2425
],
2526
description: 'load Sass files'
2627
},

lib/package-helper.js

Lines changed: 74 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,20 @@ function getInstallCommand(packageConfigs) {
3636
const hasYarnLockfile = fs.existsSync('yarn.lock');
3737
const hasNpmLockfile = fs.existsSync('package-lock.json');
3838
const packageInstallStrings = packageConfigs.map((packageConfig) => {
39-
if (typeof packageConfig.version === 'undefined') {
40-
return packageConfig.name;
39+
const firstPackage = packageConfig[0];
40+
41+
if (typeof firstPackage.version === 'undefined') {
42+
return firstPackage.name;
4143
}
4244

4345
// e.g. ^4.0||^5.0: use the latest version
44-
let recommendedVersion = packageConfig.version;
46+
let recommendedVersion = firstPackage.version;
4547
if (recommendedVersion.indexOf('||') !== -1) {
4648
recommendedVersion = recommendedVersion.split('|').pop().trim();
4749
}
4850

4951
// recommend the version included in our package.json file
50-
return `${packageConfig.name}@${recommendedVersion}`;
52+
return `${firstPackage.name}@${recommendedVersion}`;
5153
});
5254

5355
if (hasNpmLockfile && !hasYarnLockfile) {
@@ -57,13 +59,32 @@ function getInstallCommand(packageConfigs) {
5759
return chalk.yellow(`yarn add ${packageInstallStrings.join(' ')} --dev`);
5860
}
5961

62+
function isPackageInstalled(packageConfig) {
63+
try {
64+
require.resolve(packageConfig.name);
65+
return true;
66+
} catch (e) {
67+
return false;
68+
}
69+
}
70+
71+
function getPackageVersion(packageConfig) {
72+
try {
73+
return require(`${packageConfig.name}/package.json`).version;
74+
} catch (e) {
75+
return null;
76+
}
77+
}
78+
6079
function getMissingPackageRecommendations(packagesConfig, requestedFeature = null) {
6180
let missingPackageConfigs = [];
6281

6382
for (let packageConfig of packagesConfig) {
64-
try {
65-
require.resolve(packageConfig.name);
66-
} catch (e) {
83+
if (!Array.isArray(packageConfig)) {
84+
packageConfig = [packageConfig];
85+
}
86+
87+
if (!packageConfig.some(isPackageInstalled)) {
6788
missingPackageConfigs.push(packageConfig);
6889
}
6990
}
@@ -72,8 +93,18 @@ function getMissingPackageRecommendations(packagesConfig, requestedFeature = nul
7293
return;
7394
}
7495

75-
const missingPackageNamesChalked = missingPackageConfigs.map(function(packageConfig) {
76-
return chalk.green(packageConfig.name);
96+
const missingPackageNamesChalked = missingPackageConfigs.map(function(packageConfigs) {
97+
const packageNames = packageConfigs.map(packageConfig => {
98+
return chalk.green(packageConfig.name);
99+
});
100+
101+
let missingPackages = packageNames[0];
102+
if (packageNames.length > 1) {
103+
const alternativePackages = packageNames.slice(1);
104+
missingPackages = `${missingPackages} (or ${alternativePackages.join(' or ')})`;
105+
}
106+
107+
return missingPackages;
77108
});
78109

79110
let message = `Install ${missingPackageNamesChalked.join(' & ')}`;
@@ -89,45 +120,56 @@ function getMissingPackageRecommendations(packagesConfig, requestedFeature = nul
89120
};
90121
}
91122

92-
function getInvalidPackageVersionRecommendations(packagesConfig, requestedFeature) {
93-
let badVersionMessages = [];
123+
function getInvalidPackageVersionRecommendations(packagesConfig) {
124+
const processPackagesConfig = (packageConfig) => {
125+
if (Array.isArray(packageConfig)) {
126+
let messages = [];
127+
128+
for (const config of packageConfig) {
129+
messages = messages.concat(processPackagesConfig(config));
130+
}
131+
132+
return messages;
133+
}
94134

95-
for (let packageConfig of packagesConfig) {
96135
if (typeof packageConfig.version === 'undefined') {
97-
continue;
136+
return [];
98137
}
99138

100-
let version;
101-
try {
102-
version = require(`${packageConfig.name}/package.json`).version;
103-
} catch (e) {
104-
// should not happen because this functions is meant to be
105-
// called only after verifying a package is actually installed
106-
throw new Error(`Could not find package.json file for ${packageConfig.name}`);
139+
const version = getPackageVersion(packageConfig);
140+
141+
// If version is null at this point it should be because
142+
// of an optional dependency whose presence has already
143+
// been checked before.
144+
if (version === null) {
145+
return [];
107146
}
108147

109148
if (semver.satisfies(version, packageConfig.version)) {
110-
continue;
149+
return [];
111150
}
112151

113152
if (semver.gtr(version, packageConfig.version)) {
114-
badVersionMessages.push(
153+
return [
115154
`Webpack Encore requires version ${chalk.green(packageConfig.version)} of ${chalk.green(packageConfig.name)}. Your version ${chalk.green(version)} is too new. The related feature *may* still work properly. If you have issues, try downgrading the library, or upgrading Encore.`
116-
);
155+
];
117156
} else {
118-
badVersionMessages.push(
157+
return [
119158
`Webpack Encore requires version ${chalk.green(packageConfig.version)} of ${chalk.green(packageConfig.name)}, but your version (${chalk.green(version)}) is too old. The related feature will probably *not* work correctly.`
120-
);
159+
];
121160
}
122-
}
161+
};
123162

124-
return badVersionMessages;
163+
return processPackagesConfig(packagesConfig);
125164
}
126165

127166
function addPackagesVersionConstraint(packages) {
128167
const packageJsonData = require('../package.json');
168+
const addConstraint = (packageData) => {
169+
if (Array.isArray(packageData)) {
170+
return packageData.map(addConstraint);
171+
}
129172

130-
return packages.map(packageData => {
131173
const newData = Object.assign({}, packageData);
132174

133175
if (packageData.enforce_version) {
@@ -143,7 +185,10 @@ function addPackagesVersionConstraint(packages) {
143185
}
144186

145187
return newData;
146-
});
188+
};
189+
190+
191+
return packages.map(addConstraint);
147192
}
148193

149194
module.exports = {

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@
8080
"postcss-loader": "^3.0.0",
8181
"preact": "^8.2.1",
8282
"preact-compat": "^3.17.0",
83+
"sass": "^1.17.0",
8384
"sass-loader": "^7.0.1",
8485
"sinon": "^2.3.4",
86+
"strip-ansi": "^5.0.0",
8587
"stylus": "^0.54.5",
8688
"stylus-loader": "^3.0.2",
8789
"ts-loader": "^5.3.0",

test/functional.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,10 @@ describe('Functional tests using webpack', function() {
524524
config.setPublicPath('/build');
525525
config.addStyleEntry('bg', './css/background_image.scss');
526526
config.addStyleEntry('font', './css/roboto_font.css');
527-
config.enableSassLoader();
527+
config.enableSassLoader(options => {
528+
// Use sass-loader instead of node-sass
529+
options.implementation = require('sass');
530+
});
528531

529532
testSetup.runWebpack(config, (webpackAssert) => {
530533
expect(config.outputPath).to.be.a.directory()

test/package-helper.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const packageHelper = require('../lib/package-helper');
1414
const path = require('path');
1515
const process = require('process');
1616
const fs = require('fs');
17+
const stripAnsi = require('strip-ansi');
1718

1819
describe('package-helper', () => {
1920
const baseCwd = process.cwd();
@@ -29,6 +30,7 @@ describe('package-helper', () => {
2930
{ name: 'foo' }, { name: 'webpack' }, { name: 'bar' }
3031
]);
3132
expect(packageRecommendations.installCommand).to.contain('yarn add foo bar');
33+
expect(stripAnsi(packageRecommendations.message)).to.contain('foo & bar');
3234
});
3335

3436
it('missing packages with package-lock.json only', () => {
@@ -37,6 +39,7 @@ describe('package-helper', () => {
3739
{ name: 'foo' }, { name: 'webpack' }, { name: 'bar' }
3840
]);
3941
expect(packageRecommendations.installCommand).to.contain('npm install foo bar');
42+
expect(stripAnsi(packageRecommendations.message)).to.contain('foo & bar');
4043
});
4144

4245
it('missing packages with yarn.lock only', () => {
@@ -45,6 +48,7 @@ describe('package-helper', () => {
4548
{ name: 'foo' }, { name: 'webpack' }, { name: 'bar' }
4649
]);
4750
expect(packageRecommendations.installCommand).to.contain('yarn add foo bar');
51+
expect(stripAnsi(packageRecommendations.message)).to.contain('foo & bar');
4852
});
4953

5054
it('missing packages with both package-lock.json and yarn.lock', () => {
@@ -53,6 +57,19 @@ describe('package-helper', () => {
5357
{ name: 'foo' }, { name: 'webpack' }, { name: 'bar' }
5458
]);
5559
expect(packageRecommendations.installCommand).to.contain('yarn add foo bar');
60+
expect(stripAnsi(packageRecommendations.message)).to.contain('foo & bar');
61+
});
62+
63+
it('missing packages with alternative packages', () => {
64+
process.chdir(path.join(__dirname, '../fixtures/package-helper/yarn'));
65+
const packageRecommendations = packageHelper.getMissingPackageRecommendations([
66+
{ name: 'foo' },
67+
[{ name: 'bar' }, { name: 'baz' }],
68+
[{ name: 'qux' }, { name: 'corge' }, { name: 'grault' }],
69+
[{ name: 'quux' }, { name: 'webpack' }],
70+
]);
71+
expect(packageRecommendations.installCommand).to.contain('yarn add foo bar qux');
72+
expect(stripAnsi(packageRecommendations.message)).to.contain('foo & bar (or baz) & qux (or corge or grault)');
5673
});
5774
});
5875

@@ -91,6 +108,16 @@ describe('package-helper', () => {
91108

92109
expect(packageRecommendations.installCommand).to.contain('yarn add foo@^8.0 bar');
93110
});
111+
112+
it('Recommends correct install with alternative packages', () => {
113+
const packageRecommendations = packageHelper.getMissingPackageRecommendations([
114+
{ name: 'foo', version: '^7.0 || ^8.0' },
115+
[{ name: 'bar' }, { name: 'baz' }],
116+
[{ name: 'qux', version: '^1.0' }, { name: 'quux', version: '^2.0' }]
117+
]);
118+
119+
expect(packageRecommendations.installCommand).to.contain('yarn add foo@^8.0 bar qux@^1.0');
120+
});
94121
});
95122

96123
describe('The getInvalidPackageVersionRecommendations correctly checks installed versions', () => {

yarn.lock

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,11 @@ ansi-regex@^3.0.0:
10821082
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
10831083
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
10841084

1085+
ansi-regex@^4.0.0:
1086+
version "4.0.0"
1087+
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9"
1088+
integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==
1089+
10851090
ansi-styles@^2.2.1:
10861091
version "2.2.1"
10871092
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -7548,6 +7553,13 @@ sass-loader@^7.0.1:
75487553
neo-async "^2.5.0"
75497554
pify "^3.0.0"
75507555

7556+
sass@^1.17.0:
7557+
version "1.17.0"
7558+
resolved "https://registry.yarnpkg.com/sass/-/sass-1.17.0.tgz#e370b9302af121c9eadad5639619127772094ae6"
7559+
integrity sha512-aFi9RQqrCYkHB2DaLKBBbdUhos1N5o3l1ke9N5JqWzgSPmYwZsdmA+ViPVatUy/RPA21uejgYVUXM7GCh8lcdw==
7560+
dependencies:
7561+
chokidar "^2.0.0"
7562+
75517563
75527564
version "0.5.8"
75537565
resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1"
@@ -8097,6 +8109,13 @@ strip-ansi@^4.0.0:
80978109
dependencies:
80988110
ansi-regex "^3.0.0"
80998111

8112+
strip-ansi@^5.0.0:
8113+
version "5.0.0"
8114+
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.0.0.tgz#f78f68b5d0866c20b2c9b8c61b5298508dc8756f"
8115+
integrity sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==
8116+
dependencies:
8117+
ansi-regex "^4.0.0"
8118+
81008119
strip-bom@^2.0.0:
81018120
version "2.0.0"
81028121
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"

0 commit comments

Comments
 (0)