Skip to content

Commit 9438f2a

Browse files
authored
Merge pull request #256 from sass/merge
Merge branch 'main' of github.com:sass/embedded-host-node into feature.color-4
2 parents 2b88a8b + ec75b8f commit 9438f2a

30 files changed

+406
-121
lines changed

CHANGELOG.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,75 @@
1+
## 1.68.0
2+
3+
* Fix the source spans associated with the `abs-percent` deprecation.
4+
5+
### JS API
6+
7+
* Non-filesystem importers can now set the `nonCanonicalScheme` field, which
8+
declares that one or more URL schemes (without `:`) will never be used for
9+
URLs returned by the `canonicalize()` method.
10+
11+
* Add a `containingUrl` field to the `canonicalize()` and `findFileUrl()`
12+
methods of importers, which is set to the canonical URL of the stylesheet that
13+
contains the current load. For filesystem importers, this is always set; for
14+
other importers, it's set only if the current load has no URL scheme, or if
15+
its URL scheme is declared as non-canonical by the importer.
16+
17+
### Dart API
18+
19+
* Add `AsyncImporter.isNonCanonicalScheme`, which importers (async or sync) can
20+
use to indicate that a certain URL scheme will never be used for URLs returned
21+
by the `canonicalize()` method.
22+
23+
* Add `AsyncImporter.containingUrl`, which is set during calls to the
24+
`canonicalize()` method to the canonical URL of the stylesheet that contains
25+
the current load. This is set only if the current load has no URL scheme, or
26+
if its URL scheme is declared as non-canonical by the importer.
27+
28+
### Embedded Sass
29+
30+
* The `CalculationValue.interpolation` field is deprecated and will be removed
31+
in a future version. It will no longer be set by the compiler, and if the host
32+
sets it it will be treated as equivalent to `CalculationValue.string` except
33+
that `"("` and `")"` will be added to the beginning and end of the string
34+
values.
35+
36+
* Properly include TypeScript types in the `sass-embedded` package.
37+
38+
## 1.67.0
39+
40+
* All functions defined in CSS Values and Units 4 are now once again parsed as
41+
calculation objects: `round()`, `mod()`, `rem()`, `sin()`, `cos()`, `tan()`,
42+
`asin()`, `acos()`, `atan()`, `atan2()`, `pow()`, `sqrt()`, `hypot()`,
43+
`log()`, `exp()`, `abs()`, and `sign()`.
44+
45+
Unlike in 1.65.0, function calls are _not_ locked into being parsed as
46+
calculations or plain Sass functions at parse-time. This means that
47+
user-defined functions will take precedence over CSS calculations of the same
48+
name. Although the function names `calc()` and `clamp()` are still forbidden,
49+
users may continue to freely define functions whose names overlap with other
50+
CSS calculations (including `abs()`, `min()`, `max()`, and `round()` whose
51+
names overlap with global Sass functions).
52+
53+
* **Breaking change**: As a consequence of the change in calculation parsing
54+
described above, calculation functions containing interpolation are now parsed
55+
more strictly than before. However, _almost_ all interpolations that would
56+
have produced valid CSS will continue to work. The only exception is
57+
`#{$variable}%` which is not valid in Sass and is no longer valid in
58+
calculations. Instead of this, either use `$variable` directly and ensure it
59+
already has the `%` unit, or write `($variable * 1%)`.
60+
61+
* **Potentially breaking bug fix**: The importer used to load a given file is no
62+
longer used to load absolute URLs that appear in that file. This was
63+
unintented behavior that contradicted the Sass specification. Absolute URLs
64+
will now correctly be loaded only from the global importer list. This applies
65+
to the modern JS API, the Dart API, and the embedded protocol.
66+
67+
### Embedded Sass
68+
69+
* Substantially improve the embedded compiler's performance when compiling many
70+
files or files that require many importer or function call round-trips with
71+
the embedded host.
72+
173
## 1.66.1
274

375
### JS API

CONTRIBUTING.md

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
We'd love to accept your patches and contributions to this project. There are
44
just a few small guidelines you need to follow.
55

6+
* [Contributor License Agreement](#contributor-license-agreement)
7+
* [Code Reviews](#code-reviews)
8+
* [Large Language Models](#large-language-models)
9+
* [Release Process](#release-process)
10+
* [Keeping in Sync With Other Packages](#keeping-in-sync-with-other-packages)
11+
* [Local Development](#local-development)
12+
* [Continuous Integration](#continuous-integration)
13+
* [Release](#release)
14+
615
## Contributor License Agreement
716

817
Contributions to this project must be accompanied by a Contributor License
@@ -15,13 +24,24 @@ You generally only need to submit a CLA once, so if you've already submitted one
1524
(even if it was for a different project), you probably don't need to do it
1625
again.
1726

18-
## Code reviews
27+
## Code Reviews
1928

2029
All submissions, including submissions by project members, require review. We
2130
use GitHub pull requests for this purpose. Consult
2231
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
2332
information on using pull requests.
2433

34+
## Large Language Models
35+
36+
Do not submit any code or prose written or modified by large language models or
37+
"artificial intelligence" such as GitHub Copilot or ChatGPT to this project.
38+
These tools produce code that looks plausible, which means that not only is it
39+
likely to contain bugs those bugs are likely to be difficult to notice on
40+
review. In addition, because these models were trained indiscriminately and
41+
non-consensually on open-source code with a variety of licenses, it's not
42+
obvious that we have the moral or legal right to redistribute code they
43+
generate.
44+
2545
## Release process
2646

2747
Because this package's version remains in lockstep with the current version of
@@ -36,7 +56,7 @@ such, manual commits should never:
3656
* Update the `package.json`'s `"compiler-version"` field to a non-`-dev` number.
3757
Changing it from non-`-dev` to dev when using a new feature is fine.
3858

39-
# Keeping in Sync With Other Packages
59+
## Keeping in Sync With Other Packages
4060

4161
The embedded host depends on several different components which come from
4262
different repositories:
@@ -51,12 +71,12 @@ different repositories:
5171

5272
These dependencies are made available in different ways depending on context.
5373

54-
## Local Development
74+
### Local Development
5575

5676
When developing locally, you can download all of these dependencies by running
57-
`npm run init`. This provides the following options for `compiler` (for the
58-
embedded compiler), `protocol` (for the embedded protocol), and `api` (for the
59-
JS API):
77+
`npm install` and then `npm run init`. This provides the following options for
78+
`compiler` (for the embedded compiler), `protocol` (for the embedded protocol),
79+
and `api` (for the JS API):
6080

6181
* `--<type>-path`: The local filesystem path of the package to use. This is
6282
useful when doing local development on both the host and its dependencies at
@@ -65,6 +85,9 @@ JS API):
6585
* `--<type>-ref`: A Git reference for the GitHub repository of the package to
6686
clone.
6787

88+
If developing locally, you will need to specify both the compiler and language.
89+
For example: `npm run init -- --compiler-path=dart-sass --language-path=language`.
90+
6891
By default:
6992

7093
* This uses the version of the embedded protocol and compiler specified by
@@ -77,14 +100,14 @@ By default:
77100
* This uses the Dart Sass version from the latest revision on GitHub, unless the
78101
`--compiler-path` was passed in which case it uses that version of Dart Sass.
79102

80-
## Continuous Integration
103+
### Continuous Integration
81104

82105
CI tests also use `npm run init`, so they use the same defaults as local
83106
development. However, if the pull request description includes a link to a pull
84107
request for Dart Sass, the embedded protocol, or the JS API, this will check out
85108
that version and run tests against it instead.
86109

87-
## Release
110+
### Release
88111

89112
When this package is released to npm, it downloads the embedded protocol version
90113
that matches `protocol-version` in `package.json`. It downloads the latest JS

lib/index.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const SassBoolean = sass.SassBoolean;
1313
export const SassCalculation = sass.SassCalculation
1414
export const SassColor = sass.SassColor;
1515
export const SassFunction = sass.SassFunction;
16+
export const SassMixin = sass.SassMixin;
1617
export const SassList = sass.SassList;
1718
export const SassMap = sass.SassMap;
1819
export const SassNumber = sass.SassNumber;
@@ -95,6 +96,10 @@ export default {
9596
defaultExportDeprecation();
9697
return sass.SassFunction;
9798
},
99+
get SassMixin() {
100+
defaultExportDeprecation();
101+
return sass.SassMixin;
102+
},
98103
get SassList() {
99104
defaultExportDeprecation();
100105
return sass.SassList;

lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export {sassFalse, sassTrue} from './src/value/boolean';
1212
export {SassColor} from './src/value/color';
1313
export {SassFunction} from './src/value/function';
1414
export {SassMap} from './src/value/map';
15+
export {SassMixin} from './src/value/mixin';
1516
export {SassNumber} from './src/value/number';
1617
export {SassString} from './src/value/string';
1718
export {Value} from './src/value';

lib/src/compile.ts

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,32 @@ import {MessageTransformer} from './message-transformer';
1818
import {PacketTransformer} from './packet-transformer';
1919
import {SyncEmbeddedCompiler} from './sync-compiler';
2020
import {deprotofySourceSpan} from './deprotofy-span';
21-
import {legacyImporterProtocol} from './legacy/importer';
21+
import {
22+
removeLegacyImporter,
23+
removeLegacyImporterFromSpan,
24+
legacyImporterProtocol,
25+
} from './legacy/utils';
26+
27+
/// Allow the legacy API to pass in an option signaling to the modern API that
28+
/// it's being run in legacy mode.
29+
///
30+
/// This is not intended for API users to pass in, and may be broken without
31+
/// warning in the future.
32+
type OptionsWithLegacy<sync extends 'sync' | 'async'> = Options<sync> & {
33+
legacy?: boolean;
34+
};
35+
36+
/// Allow the legacy API to pass in an option signaling to the modern API that
37+
/// it's being run in legacy mode.
38+
///
39+
/// This is not intended for API users to pass in, and may be broken without
40+
/// warning in the future.
41+
type StringOptionsWithLegacy<sync extends 'sync' | 'async'> =
42+
StringOptions<sync> & {legacy?: boolean};
2243

2344
export function compile(
2445
path: string,
25-
options?: Options<'sync'>
46+
options?: OptionsWithLegacy<'sync'>
2647
): CompileResult {
2748
const importers = new ImporterRegistry(options);
2849
return compileRequestSync(
@@ -34,7 +55,7 @@ export function compile(
3455

3556
export function compileString(
3657
source: string,
37-
options?: StringOptions<'sync'>
58+
options?: StringOptionsWithLegacy<'sync'>
3859
): CompileResult {
3960
const importers = new ImporterRegistry(options);
4061
return compileRequestSync(
@@ -46,7 +67,7 @@ export function compileString(
4667

4768
export function compileAsync(
4869
path: string,
49-
options?: Options<'async'>
70+
options?: OptionsWithLegacy<'async'>
5071
): Promise<CompileResult> {
5172
const importers = new ImporterRegistry(options);
5273
return compileRequestAsync(
@@ -58,7 +79,7 @@ export function compileAsync(
5879

5980
export function compileStringAsync(
6081
source: string,
61-
options?: StringOptions<'async'>
82+
options?: StringOptionsWithLegacy<'async'>
6283
): Promise<CompileResult> {
6384
const importers = new ImporterRegistry(options);
6485
return compileRequestAsync(
@@ -151,7 +172,7 @@ function newCompileRequest(
151172
async function compileRequestAsync(
152173
request: proto.InboundMessage_CompileRequest,
153174
importers: ImporterRegistry<'async'>,
154-
options?: Options<'async'>
175+
options?: OptionsWithLegacy<'async'> & {legacy?: boolean}
155176
): Promise<CompileResult> {
156177
const functions = new FunctionRegistry(options?.functions);
157178
const embeddedCompiler = new AsyncEmbeddedCompiler();
@@ -197,7 +218,7 @@ async function compileRequestAsync(
197218
function compileRequestSync(
198219
request: proto.InboundMessage_CompileRequest,
199220
importers: ImporterRegistry<'sync'>,
200-
options?: Options<'sync'>
221+
options?: OptionsWithLegacy<'sync'>
201222
): CompileResult {
202223
const functions = new FunctionRegistry(options?.functions);
203224
const embeddedCompiler = new SyncEmbeddedCompiler();
@@ -272,33 +293,40 @@ function createDispatcher<sync extends 'sync' | 'async'>(
272293

273294
/** Handles a log event according to `options`. */
274295
function handleLogEvent(
275-
options: Options<'sync' | 'async'> | undefined,
296+
options: OptionsWithLegacy<'sync' | 'async'> | undefined,
276297
event: proto.OutboundMessage_LogEvent
277298
): void {
299+
let span = event.span ? deprotofySourceSpan(event.span) : null;
300+
if (span && options?.legacy) span = removeLegacyImporterFromSpan(span);
301+
let message = event.message;
302+
if (options?.legacy) message = removeLegacyImporter(message);
303+
let formatted = event.formatted;
304+
if (options?.legacy) formatted = removeLegacyImporter(formatted);
305+
278306
if (event.type === proto.LogEventType.DEBUG) {
279307
if (options?.logger?.debug) {
280-
options.logger.debug(event.message, {
281-
span: deprotofySourceSpan(event.span!),
308+
options.logger.debug(message, {
309+
span: span!,
282310
});
283311
} else {
284-
console.error(event.formatted);
312+
console.error(formatted);
285313
}
286314
} else {
287315
if (options?.logger?.warn) {
288316
const params: {deprecation: boolean; span?: SourceSpan; stack?: string} =
289317
{
290318
deprecation: event.type === proto.LogEventType.DEPRECATION_WARNING,
291319
};
292-
293-
const spanProto = event.span;
294-
if (spanProto) params.span = deprotofySourceSpan(spanProto);
320+
if (span) params.span = span;
295321

296322
const stack = event.stackTrace;
297-
if (stack) params.stack = stack;
323+
if (stack) {
324+
params.stack = options?.legacy ? removeLegacyImporter(stack) : stack;
325+
}
298326

299-
options.logger.warn(event.message, params);
327+
options.logger.warn(message, params);
300328
} else {
301-
console.error(event.formatted);
329+
console.error(formatted);
302330
}
303331
}
304332
}

lib/src/importer-registry.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export class ImporterRegistry<sync extends 'sync' | 'async'> {
4545
register(
4646
importer: Importer<sync> | FileImporter<sync>
4747
): proto.InboundMessage_CompileRequest_Importer {
48-
const response = new proto.InboundMessage_CompileRequest_Importer();
48+
const message = new proto.InboundMessage_CompileRequest_Importer();
4949
if ('canonicalize' in importer) {
5050
if ('findFileUrl' in importer) {
5151
throw new Error(
@@ -54,14 +54,18 @@ export class ImporterRegistry<sync extends 'sync' | 'async'> {
5454
);
5555
}
5656

57-
response.importer = {case: 'importerId', value: this.id};
57+
message.importer = {case: 'importerId', value: this.id};
58+
message.nonCanonicalScheme =
59+
typeof importer.nonCanonicalScheme === 'string'
60+
? [importer.nonCanonicalScheme]
61+
: importer.nonCanonicalScheme ?? [];
5862
this.importersById.set(this.id, importer);
5963
} else {
60-
response.importer = {case: 'fileImporterId', value: this.id};
64+
message.importer = {case: 'fileImporterId', value: this.id};
6165
this.fileImportersById.set(this.id, importer);
6266
}
6367
this.id += 1;
64-
return response;
68+
return message;
6569
}
6670

6771
/** Handles a canonicalization request. */
@@ -78,6 +82,9 @@ export class ImporterRegistry<sync extends 'sync' | 'async'> {
7882
return thenOr(
7983
importer.canonicalize(request.url, {
8084
fromImport: request.fromImport,
85+
containingUrl: request.containingUrl
86+
? new URL(request.containingUrl)
87+
: null,
8188
}),
8289
url =>
8390
new proto.InboundMessage_CanonicalizeResponse({
@@ -157,6 +164,9 @@ export class ImporterRegistry<sync extends 'sync' | 'async'> {
157164
return thenOr(
158165
importer.findFileUrl(request.url, {
159166
fromImport: request.fromImport,
167+
containingUrl: request.containingUrl
168+
? new URL(request.containingUrl)
169+
: null,
160170
}),
161171
url => {
162172
if (!url) return new proto.InboundMessage_FileImportResponse();

0 commit comments

Comments
 (0)