Skip to content

Commit

Permalink
dart-sass support (#247)
Browse files Browse the repository at this point in the history
* Upgrade to typescript 3.8 so that we can use `import type` statements.
* Removes `node-sass` as a direct dependency. Eyeglass will attempt to require both `node-sass` and `sass` unless a sass engine is explicitly passed as an option. In the case where both are installed, `node-sass` is used.
* All import statements to `node-sass` are now `import type` statements which ensures we won't accidently import a concrete aspect of the library into our module's namespace.
* Testing infrastructure and updates to run tests against both implementations of Sass.
* Ameliorations for incompatibilities between node-sass and dart-sass.
  * `dart-sass` has different behavior for importers that return both `file` and `contents`. A bug has been filed at sass/dart-sass#975. Note: Custom importers provided to eyeglass from eyeglass addons or applications using eyeglass are insulated from this incompatibility.
  * `dart-sass` doesn't support functional invocation of sass value types, so a lot of the code changes were to instantiate those classes with `new` instead. (I discussed this with @nex3 a while ago and she indicated that it wasn't an API difference that she intended to support.) Note: Eyeglass addons that wish to support dart-sass will also need to make this change.
  * `dart-sass` doesn't support the constants `sass.NULL`, `sass.TRUE` and `sass.FALSE`. Instead, the code in eyeglass has been updated to use the constants that are supported by both: `sass.types.Null.NULL`, `sass.types.Boolean.TRUE`, and `sass.types.Boolean.FALSE` respectively. An issue has been filed: sass/dart-sass#977
  * `dart-sass` doesn't support the `nested` output style which was the default for node-sass. So all test output has been converted to expect the `"expanded"` output style.
  • Loading branch information
chriseppstein authored Mar 29, 2020
1 parent d9b1844 commit 6e1d933
Show file tree
Hide file tree
Showing 48 changed files with 2,968 additions and 2,742 deletions.
4 changes: 2 additions & 2 deletions packages/broccoli-eyeglass/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
"lodash.merge": "^4.6.1",
"lodash.sortby": "^4.7.0",
"mkdirp": "^0.5.1",
"node-sass": "^4.11.0",
"rsvp": "^4.8.3",
"sync-disk-cache": "^1.3.2",
"systeminformation": "^4.1.4",
Expand Down Expand Up @@ -89,9 +88,10 @@
"eslint-plugin-prettier": "^3.0.0",
"fixturify": "^0.3.4",
"mocha": "^5.2.0",
"node-sass": "^4.11.0",
"prettier": "^1.14.3",
"rimraf": "^2.6.2",
"typescript": "~3.7.0"
"typescript": "~3.8.0"
},
"volta": {
"node": "10.18.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/ember-cli-eyeglass/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"eslint": "^5.12.1",
"eyeglass": "^2.5.0",
"mocha": "^5.2.0",
"typescript": "~3.7.0"
"typescript": "~3.8.0"
},
"keywords": [
"ember-addon",
Expand Down
29 changes: 23 additions & 6 deletions packages/eyeglass/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
# 3.0.0

*Breaking Changes:*

* The `deasync` library is no longer a direct dependency of Eyeglass. It is now
an optional dependency. Applications that use `sass.renderSync()` might need
to install the `deasync` library if they use eyeglass modules that publish
asynchronous sass functions.
## New Features

* Eyeglass now works with the pure javascript version of `dart-sass`, which is
reference implementation of the Sass language. This allows users of Eyeglass
to use the latest and greatest language features of Sass and to avoid libraries
that use native extensions.

To use: Add `sass` as a dependency of your project. Eyeglass will
automatically use dart sass when `node-sass` isn't installed. If, for some
reason you need to have both installed, just set the
`eyeglass.engines.sass` option to the value returned by `require('sass')`.

## Breaking Changes

* `node-sass` is no longer a direct dependency of Eyeglass. Applications using
Eyeglass must now install the sass implementation and version that they
prefer. Eyeglass will attempt to require both `node-sass` and `sass` unless
a sass engine is explicitly passed as an option. In the case where both are
installed, `node-sass` is used.
* The `deasync` library is no longer a direct dependency of Eyeglass.
Applications that use `sass.renderSync()` might need to install the
`deasync` library if they use eyeglass modules that publish asynchronous
sass functions.
* Manually specified modules no longer take precedence over modules discovered
via node package dependencies. [More Information](https://github.com/linkedin/eyeglass/commit/9d9500abd90414ea9bec7c60465f2bdd42e496ef).
* The following deprecated APIs have been removed:
Expand Down
27 changes: 21 additions & 6 deletions packages/eyeglass/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,28 @@

## Getting some npm in your Sass

eyeglass is a node-sass ([github](https://github.com/sass/node-sass)) extension manager built on top of npm. Using eyeglass, you can bring the power of node modules to your Sass files.
Eyeglass is a sass ([github](https://github.com/sass/sass)) extension manager built on top of npm. Using eyeglass, you can bring the power of node modules to your Sass files.

Eyeglass works with [`node-sass`](https://github.com/sass/node-sass) as well as [`dart-sass`](https://github.com/sass/dart-sass). Eyeglass has no direct dependency on a sass implementation and it will use whichever sass implementation that you have installed in your project. If necessary, you can pass a sass implementation to eyeglass using the `eyeglass.engines.sass` option.

# Installing eyeglass

```
# for programatic functionality
npm install eyeglass --save-dev
npm install --save-dev eyeglass
```

Additionally, you must install a compatible Sass implementation.

If you want to use `node-sass`:

```
npm install --save-dev node-sass
```

If you want to use `dart-sass`:

```
npm install --save-dev sass
```

# Adding eyeglass modules to your project
Expand All @@ -19,8 +34,8 @@ eyeglass modules are regular npm modules. Install them into your project just li
`npm install my_eyeglass_module --save-dev`

Once installed via npm, an eyeglass module can:
* Provide stylesheets that you can import with special node_module syntax.
* Add additional custom functions to Sass that are written in javascript.
* Make stylesheets in the npm module accessible to the project via Sass's `@import` or `@use` directives.
* Expose custom functions and importers written in javascript to the Sass compiler.

If your build-tool is [eyeglass-aware](#building-sass-files-with-eyeglass-support), you can reference the eyeglass module with standard Sass import syntax: `@import "my_eyeglass_module/file";`. The `my_eyeglass_module` will be resolved to the correct directory in your node modules, and the file will then resolve using the standard import rules for Sass.

Expand Down Expand Up @@ -516,7 +531,7 @@ module.exports = function(eyeglass, sass) {
return {
functions: {
"greetings-hello($name: 'World')": function(name, done) {
done(sass.types.String("Hello, " + name.getValue()));
done(new sass.types.String("Hello, " + name.getValue()));
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions packages/eyeglass/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@
"lodash.includes": "^4.3.0",
"lodash.merge": "^4.6.1",
"lru-cache": "^5.1.1",
"node-sass": "^4.0.0 || ^3.10.1",
"node-sass-utils": "^1.1.2",
"node-sass-utils": "^1.1.3",
"semver": "^5.6.0"
},
"devDependencies": {
Expand All @@ -71,12 +70,14 @@
"grunt-release": "^0.14.0",
"handlebars": "^4.0.12",
"mocha": "^5.2.0",
"node-sass": "^4.0.0 || ^3.10.1",
"nyc": "^13.1.0",
"proxyquire": "^2.1.3",
"sass": "^1.26.3",
"should": "^13.2.3",
"source-map-support": "^0.5.10",
"ts-node": "^7.0.1",
"typescript": "~3.7.0"
"typescript": "~3.8.0"
},
"files": [
"lib",
Expand Down
2 changes: 1 addition & 1 deletion packages/eyeglass/sass/assets.scss
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,5 @@ $eg-registered-assets: () !default;
$url: if($module, $module + "/" + $url, $url);
$asset-urls: append($asset-urls, quote($url), comma);
}
@return $asset-urls;
@return if(length($asset-urls) == 0, null, $asset-urls);
}
6 changes: 3 additions & 3 deletions packages/eyeglass/site-src/docs/eyeglass_modules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Currently, the `eyeglass` section contains the following properties:

* **needs**: A semantic version string. This defines the version of eyeglass you are compatible with. When developing, you can set the string to `"*"`, but before releasing the module to the public, you'll probably want to set it to a minimum compatible version. We usually recommend people take the Major + Minor approach, much like npm. This means if eyeglass is currently `1.0.6`, you can put a `^` in front and write your `needs` as `^1.0.6`. This means you'll work with eyeglass versions 1.0.6 and newer, but not versions so new that APIs you depend on are broken.
* **exports**: An optional string. When you already have a `main` property (common for existing npm projects) and you just want to add support for eyeglass, you can specify an alternate property here. Eyeglass will look for `eyeglass.exports` before checking `main`, so that we always include the right thing. This property can be set to `false` which will cause the `main` of the project to be ignored -- in
this case, the `sassDir` property must be set.
this case, the `sassDir` property must be set.
* *sassDir*: Optional. A relative path to the directory containing your sass files. When omittied, this should be specified as part of eyeglass exports.

# eyeglass-exports.js
Expand Down Expand Up @@ -77,7 +77,7 @@ It's easier to explain with an example.
```js
functions: {
'hello($name: "World")': function(name, done) {
done(sass.types.String("Hello, " + name.getValue() + "!"));
done(new sass.types.String("Hello, " + name.getValue() + "!"));
}
}
```
Expand All @@ -94,7 +94,7 @@ The **callback** associated with the function takes **N** arguments, where "N" w
The **sass.types** collection comes from that second argument in our `module.exports` function.

```js
done(sass.types.String("Hello, " + name.getValue() + "!"));
done(new sass.types.String("Hello, " + name.getValue() + "!"));
```

* **sass.types.TYPE**: All things we pass back to `node-sass` have a `TYPE`. This tells Sass what we're dealing with, and also allows for cool Sass features like applying math to px/em/rem and having it _just work_. A full list is in the [node-sass documentation](https://github.com/sass/node-sass#functions--v300---experimental), but the three most common are `sass.types.Number(amount, unit)`, `sass.types.String(value)`, and `sass.types.Color(red, green, blue, alpha)`. If you don't want to return anything, you can return `sass.types.Null()`.
Expand Down
3 changes: 1 addition & 2 deletions packages/eyeglass/src/Eyeglass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import deprecator, { DeprecateFn } from "./util/deprecator";
import semverChecker from "./util/semverChecker";
import * as fs from "fs-extra";
import { IEyeglass } from "./IEyeglass";
import { SassFunction } from "node-sass";
import type { SassFunction, AsyncImporter } from "node-sass";
import { SassImplementation, helpers as sassHelpers, isSassImplementation } from "./util/SassImplementation";
import { AsyncImporter } from "node-sass";
import { UnsafeDict, Dict } from "./util/typescriptUtils";
import heimdall = require("heimdalljs");
import { SimpleCache } from "./util/SimpleCache";
Expand Down
4 changes: 2 additions & 2 deletions packages/eyeglass/src/assets/Assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { IEyeglass } from "../IEyeglass";
import * as debug from "../util/debug";
import { AssetSourceOptions } from "../util/Options";
import { isType, SassImplementation, SassTypeError, isSassMap, isSassString } from "../util/SassImplementation";
import * as sass from "node-sass";
import type * as sass from "node-sass";
import { URI } from "../util/URI";

import AssetsCollection from "./AssetsCollection";
Expand Down Expand Up @@ -268,7 +268,7 @@ export default class Assets implements Resolves, Installs {
let size = $registeredAssetsMap.getLength();
for (let i = 0; i < size; i++) {
let k = $registeredAssetsMap.getKey(i);
if (k === this.sassImpl.NULL) {
if (k === this.sassImpl.types.Null.NULL) {
let v = $registeredAssetsMap.getValue(i);
if (isSassMap(this.sassImpl, v)) {
appAssets = v;
Expand Down
2 changes: 1 addition & 1 deletion packages/eyeglass/src/functions/EyeglassFunctions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IEyeglass } from "../IEyeglass";
import { SassImplementation } from "../util/SassImplementation";
import { FunctionDeclarations } from "node-sass";
import type { FunctionDeclarations } from "node-sass";

export type EyeglassFunctions =
(eyeglass: IEyeglass, sass: SassImplementation) => FunctionDeclarations;
12 changes: 6 additions & 6 deletions packages/eyeglass/src/functions/asset-uri.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IEyeglass } from "../IEyeglass";
import { SassImplementation, typeError, isType, isSassList, isSassError } from "../util/SassImplementation";
import { SassFunctionCallback, FunctionDeclarations } from "node-sass";
import * as nodeSass from "node-sass";
import type { SassFunctionCallback, FunctionDeclarations } from "node-sass";
import type * as nodeSass from "node-sass";
import { EyeglassFunctions } from "./EyeglassFunctions";
import { isPresent } from "../util/typescriptUtils";
import { errorMessageFor } from "../util/errorFor";
Expand All @@ -15,7 +15,7 @@ function(eyeglass: IEyeglass, sass: SassImplementation): FunctionDeclarations {
return done(typeError(sass, "map", $assets));
}
if (isSassList(sass, $assets)) {
$assetMap = sass.types.Map(0);
$assetMap = new sass.types.Map(0);
} else {
$assetMap = $assets;
}
Expand All @@ -25,12 +25,12 @@ function(eyeglass: IEyeglass, sass: SassImplementation): FunctionDeclarations {
if (isSassError(sass, error)) {
result = error;
} else if (isPresent(error)) {
result = sass.types.Error(errorMessageFor(error));
result = new sass.types.Error(errorMessageFor(error));
} else {
result = sass.types.Error("[internal error] A uri was not returned");
result = new sass.types.Error("[internal error] A uri was not returned");
}
} else {
result = sass.types.String(assetUri);
result = new sass.types.String(assetUri);
}
done(result);
});
Expand Down
26 changes: 13 additions & 13 deletions packages/eyeglass/src/functions/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import * as path from "path";
import glob from "glob";
import { IEyeglass } from "../IEyeglass";
import { SassImplementation, isSassString, typeError } from "../util/SassImplementation";
import { SassFunctionCallback, FunctionDeclarations } from "node-sass";
import * as nodeSass from "node-sass";
import type { SassFunctionCallback, FunctionDeclarations } from "node-sass";
import type * as nodeSass from "node-sass";
import { unreachable } from "../util/assertions";
import { EyeglassFunctions } from "./EyeglassFunctions";
import { realpathSync } from "../util/perf";
Expand All @@ -22,7 +22,7 @@ const fsFunctions: EyeglassFunctions = function(eyeglass: IEyeglass, sass: SassI
let sassUtils = require("node-sass-utils")(sass);

function accessViolation(location: string): nodeSass.types.Error {
return sass.types.Error("Security violation: Cannot access " + location);
return new sass.types.Error("Security violation: Cannot access " + location);
}

function inSandbox(fsPath: string): boolean {
Expand Down Expand Up @@ -93,15 +93,15 @@ const fsFunctions: EyeglassFunctions = function(eyeglass: IEyeglass, sass: SassI
if (expandedPath) {
segments.unshift(expandedPath);
let resolved = path.resolve.apply(null, segments);
done(sass.types.String(resolved));
done(new sass.types.String(resolved));
} else {
done(sass.types.Error(`No path is registered for ${pathId}`));
done(new sass.types.Error(`No path is registered for ${pathId}`));
}
},
"eyeglass-fs-join($segments...)": function(segments: nodeSass.types.Value, done: SassFunctionCallback) {
let jsSegments = sassUtils.castToJs(segments);
let joined = path.join.apply(null, jsSegments);
done(sass.types.String(joined));
done(new sass.types.String(joined));
},
"eyeglass-fs-exists($absolute-path)": function(fsAbsolutePath: nodeSass.types.Value, done: SassFunctionCallback) {
if (!isSassString(sass, fsAbsolutePath)) {
Expand All @@ -110,16 +110,16 @@ const fsFunctions: EyeglassFunctions = function(eyeglass: IEyeglass, sass: SassI
let absolutePath = fsAbsolutePath.getValue();
if (inSandbox(absolutePath)) {
if (existsSync(absolutePath)) {
done(sass.TRUE);
done(sass.types.Boolean.TRUE);
} else {
done(sass.FALSE);
done(sass.types.Boolean.FALSE);
}
} else {
done(accessViolation(absolutePath));
}
},
"eyeglass-fs-path-separator()": function(done: SassFunctionCallback) {
done(sass.types.String(path.sep));
done(new sass.types.String(path.sep));
},
"eyeglass-fs-list-files($directory, $glob: '*')": function($directory: nodeSass.types.Value, $globPattern: nodeSass.types.Value, done: SassFunctionCallback) {
if (!isSassString(sass, $directory)) {
Expand Down Expand Up @@ -164,7 +164,7 @@ const fsFunctions: EyeglassFunctions = function(eyeglass: IEyeglass, sass: SassI
fs.stat(filename, function(err, stats) {
/* istanbul ignore if - we do not need to simulate an fs error here */
if (err) {
done(sass.types.Error(err.message));
done(new sass.types.Error(err.message));
} else {
try {
let realpath = realpathSync(filename);
Expand All @@ -181,7 +181,7 @@ const fsFunctions: EyeglassFunctions = function(eyeglass: IEyeglass, sass: SassI
);
} catch (e) {
/* istanbul ignore next - we do not need to simulate an fs error here */
done(sass.types.Error(e.message));
done(new sass.types.Error(e.message));
}
}
});
Expand All @@ -199,9 +199,9 @@ const fsFunctions: EyeglassFunctions = function(eyeglass: IEyeglass, sass: SassI
fs.readFile(filename, function(err, contents) {
/* istanbul ignore if - we do not need to simulate an fs error here */
if (err) {
done(sass.types.Error(err.message));
done(new sass.types.Error(err.message));
} else {
done(sass.types.String(contents.toString()));
done(new sass.types.String(contents.toString()));
}
});
} else {
Expand Down
14 changes: 7 additions & 7 deletions packages/eyeglass/src/functions/normalize-uri.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { URI } from "../util/URI";
import { IEyeglass } from "../IEyeglass";
import { SassImplementation, isSassString, typeError, isSassMap } from "../util/SassImplementation";
import { SassFunctionCallback, FunctionDeclarations } from "node-sass";
import * as nodeSass from "node-sass";
import type { SassFunctionCallback, FunctionDeclarations } from "node-sass";
import type * as nodeSass from "node-sass";
const IS_WINDOWS = /win32/.test(require("os").platform());

const normalizeURI = function(_eyeglass: IEyeglass, sass: SassImplementation): FunctionDeclarations {
Expand All @@ -14,7 +14,7 @@ const normalizeURI = function(_eyeglass: IEyeglass, sass: SassImplementation): F
let uri = $uri.getValue();
// decorate the uri
uri = URI.preserve(uri);
done(sass.types.String(uri));
done(new sass.types.String(uri));
},
"eyeglass-uri-restore($uri)": function($uri: nodeSass.types.Value, done: SassFunctionCallback) {
if (!isSassString(sass, $uri)) {
Expand All @@ -23,13 +23,13 @@ const normalizeURI = function(_eyeglass: IEyeglass, sass: SassImplementation): F
let uri = $uri.getValue();
// restore the uri
uri = URI.restore(uri);
done(sass.types.String(uri));
done(new sass.types.String(uri));
}
};

if (IS_WINDOWS || process.env.EYEGLASS_NORMALIZE_PATHS) {
let $web = nodeSass.types.String("web");
let $system = nodeSass.types.String("system");
let $web = new sass.types.String("web");
let $system = new sass.types.String("system");
let egNormalizeUri = function($uri: nodeSass.types.Value, $type: nodeSass.types.Value): nodeSass.types.String {
if (!isSassString(sass, $type)) {
throw typeError(sass, "string", $type);
Expand All @@ -41,7 +41,7 @@ const normalizeURI = function(_eyeglass: IEyeglass, sass: SassImplementation): F
let uri = $uri.getValue();
// normalize the uri for the given type
uri = URI[type](uri);
return sass.types.String(uri);
return new sass.types.String(uri);
};
methods["-eyeglass-normalize-uri($uri, $type: web)"] = function($uri: nodeSass.types.Value, $type: nodeSass.types.Value, done: SassFunctionCallback) {
try {
Expand Down
Loading

0 comments on commit 6e1d933

Please sign in to comment.