Skip to content

Commit d38f850

Browse files
committed
Implement sass --embedded in pure JS mode
1 parent 12022cc commit d38f850

File tree

7 files changed

+106
-74
lines changed

7 files changed

+106
-74
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,7 @@ jobs:
117117
working-directory: sass-spec
118118

119119
- name: Compile
120-
run: |
121-
npm run compile
122-
if [[ "$RUNNER_OS" == "Windows" ]]; then
123-
# Avoid copying the entire Dart Sass build directory on Windows,
124-
# since it may contain symlinks that cp will choke on.
125-
mkdir -p dist/lib/src/vendor/dart-sass/
126-
cp {`pwd`/,dist/}lib/src/vendor/dart-sass/sass.bat
127-
cp {`pwd`/,dist/}lib/src/vendor/dart-sass/sass.snapshot
128-
else
129-
ln -s {`pwd`/,dist/}lib/src/vendor/dart-sass
130-
fi
120+
run: npm run compile
131121

132122
- name: Run tests
133123
run: npm run js-api-spec -- --sassPackage .. --sassSassRepo ../language

bin/sass.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env node
22

33
import * as child_process from 'child_process';
4+
import * as path from 'path';
45
import {compilerCommand} from '../lib/src/compiler-path';
56

67
// TODO npm/cmd-shim#152 and yarnpkg/berry#6422 - If and when the package
@@ -12,6 +13,10 @@ try {
1213
compilerCommand[0],
1314
[...compilerCommand.slice(1), ...process.argv.slice(2)],
1415
{
16+
// Node blocks launching .bat and .cmd without a shell due to CVE-2024-27980
17+
shell: ['.bat', '.cmd'].includes(
18+
path.extname(compilerCommand[0]).toLowerCase(),
19+
),
1520
stdio: 'inherit',
1621
windowsHide: true,
1722
},

lib/src/compiler-module.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2025 Google LLC. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import * as p from 'path';
6+
import {getElfInterpreter} from './elf';
7+
8+
/**
9+
* Detect if the given binary is linked with musl libc by checking if
10+
* the interpreter basename starts with "ld-musl-"
11+
*/
12+
function isLinuxMusl(path: string): boolean {
13+
try {
14+
const interpreter = getElfInterpreter(path);
15+
return p.basename(interpreter).startsWith('ld-musl-');
16+
} catch (error) {
17+
console.warn(
18+
`Warning: Failed to detect linux-musl, fallback to linux-gnu: ${error.message}`,
19+
);
20+
return false;
21+
}
22+
}
23+
24+
/** The module name for the embedded compiler executable. */
25+
export const compilerModule = (() => {
26+
const platform =
27+
process.platform === 'linux' && isLinuxMusl(process.execPath)
28+
? 'linux-musl'
29+
: (process.platform as string);
30+
31+
const arch = process.arch;
32+
33+
return `sass-embedded-${platform}-${arch}`;
34+
})();

lib/src/compiler-path.ts

Lines changed: 25 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,52 @@
22
// MIT-style license that can be found in the LICENSE file or at
33
// https://opensource.org/licenses/MIT.
44

5-
import * as fs from 'fs';
65
import * as p from 'path';
7-
import {getElfInterpreter} from './elf';
8-
import {isErrnoException} from './utils';
9-
10-
/**
11-
* Detect if the given binary is linked with musl libc by checking if
12-
* the interpreter basename starts with "ld-musl-"
13-
*/
14-
function isLinuxMusl(path: string): boolean {
15-
try {
16-
const interpreter = getElfInterpreter(path);
17-
return p.basename(interpreter).startsWith('ld-musl-');
18-
} catch (error) {
19-
console.warn(
20-
`Warning: Failed to detect linux-musl, fallback to linux-gnu: ${error.message}`,
21-
);
22-
return false;
23-
}
24-
}
6+
import {compilerModule} from './compiler-module';
257

268
/** The full command for the embedded compiler executable. */
279
export const compilerCommand = (() => {
28-
const platform =
29-
process.platform === 'linux' && isLinuxMusl(process.execPath)
30-
? 'linux-musl'
31-
: (process.platform as string);
32-
33-
const arch = process.arch;
34-
35-
// find for development
36-
for (const path of ['vendor', '../../../lib/src/vendor']) {
37-
const executable = p.resolve(
38-
__dirname,
39-
path,
40-
`dart-sass/sass${platform === 'win32' ? '.bat' : ''}`,
41-
);
42-
43-
if (fs.existsSync(executable)) return [executable];
44-
}
45-
4610
try {
4711
return [
4812
require.resolve(
49-
`sass-embedded-${platform}-${arch}/dart-sass/src/dart` +
50-
(platform === 'win32' ? '.exe' : ''),
51-
),
52-
require.resolve(
53-
`sass-embedded-${platform}-${arch}/dart-sass/src/sass.snapshot`,
13+
`${compilerModule}/dart-sass/src/dart` +
14+
(process.platform === 'win32' ? '.exe' : ''),
5415
),
16+
require.resolve(`${compilerModule}/dart-sass/src/sass.snapshot`),
5517
];
56-
} catch (ignored) {
57-
// ignored
18+
} catch (e) {
19+
if (e.code !== 'MODULE_NOT_FOUND') {
20+
throw e;
21+
}
5822
}
5923

6024
try {
6125
return [
6226
require.resolve(
63-
`sass-embedded-${platform}-${arch}/dart-sass/sass` +
64-
(platform === 'win32' ? '.bat' : ''),
27+
`${compilerModule}/dart-sass/sass` +
28+
(process.platform === 'win32' ? '.bat' : ''),
6529
),
6630
];
67-
} catch (e: unknown) {
68-
if (!(isErrnoException(e) && e.code === 'MODULE_NOT_FOUND')) {
31+
} catch (e) {
32+
if (e.code !== 'MODULE_NOT_FOUND') {
33+
throw e;
34+
}
35+
}
36+
37+
try {
38+
return [
39+
process.execPath,
40+
p.join(p.dirname(require.resolve('sass')), 'sass.js'),
41+
];
42+
} catch (e) {
43+
if (e.code !== 'MODULE_NOT_FOUND') {
6944
throw e;
7045
}
7146
}
7247

7348
throw new Error(
7449
"Embedded Dart Sass couldn't find the embedded compiler executable. " +
75-
'Please make sure the optional dependency ' +
76-
`sass-embedded-${platform}-${arch} is installed in ` +
77-
'node_modules.',
50+
`Please make sure the optional dependency ${compilerModule} or sass is ` +
51+
'installed in node_modules.',
7852
);
7953
})();

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"test": "jest"
3939
},
4040
"optionalDependencies": {
41+
"sass": "1.85.1",
4142
"sass-embedded-android-arm": "1.85.1",
4243
"sass-embedded-android-arm64": "1.85.1",
4344
"sass-embedded-android-ia32": "1.85.1",

tool/get-embedded-compiler.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
// MIT-style license that can be found in the LICENSE file or at
33
// https://opensource.org/licenses/MIT.
44

5+
import {promises as fs} from 'fs';
56
import * as p from 'path';
67
import * as shell from 'shelljs';
78

9+
import {compilerModule} from '../lib/src/compiler-module';
810
import * as utils from './utils';
911

1012
/**
@@ -14,7 +16,7 @@ import * as utils from './utils';
1416
* at `path`. By default, checks out the latest revision from GitHub.
1517
*/
1618
export async function getEmbeddedCompiler(
17-
outPath: string,
19+
js?: boolean,
1820
options?: {ref: string} | {path: string},
1921
): Promise<void> {
2022
const repo = 'dart-sass';
@@ -41,21 +43,43 @@ export async function getEmbeddedCompiler(
4143
await utils.link(languageInHost, languageInCompiler);
4244
}
4345

44-
buildDartSassEmbedded(source);
45-
await utils.link(p.join(source, 'build'), p.join(outPath, repo));
46+
buildDartSassEmbedded(source, js ?? false);
47+
48+
const jsModulePath = p.resolve('node_modules/sass');
49+
const dartModulePath = p.resolve(p.join('node_modules', compilerModule));
50+
if (js) {
51+
await fs.rm(dartModulePath, {force: true, recursive: true});
52+
await utils.link(p.join(source, 'build/npm'), jsModulePath);
53+
} else {
54+
await fs.rm(jsModulePath, {force: true, recursive: true});
55+
await utils.link(p.join(source, 'build'), p.join(dartModulePath, repo));
56+
}
4657
}
4758

4859
// Builds the Embedded Dart Sass executable from the source at `repoPath`.
49-
function buildDartSassEmbedded(repoPath: string): void {
60+
function buildDartSassEmbedded(repoPath: string, js: boolean): void {
5061
console.log("Downloading Dart Sass's dependencies.");
5162
shell.exec('dart pub upgrade', {
5263
cwd: repoPath,
5364
silent: true,
5465
});
5566

56-
console.log('Building the Dart Sass executable.');
57-
shell.exec('dart run grinder protobuf pkg-standalone-dev', {
58-
cwd: repoPath,
59-
env: {...process.env, UPDATE_SASS_PROTOCOL: 'false'},
60-
});
67+
if (js) {
68+
shell.exec('npm install', {
69+
cwd: repoPath,
70+
silent: true,
71+
});
72+
73+
console.log('Building the Dart Sass npm package.');
74+
shell.exec('dart run grinder protobuf pkg-npm-dev', {
75+
cwd: repoPath,
76+
env: {...process.env, UPDATE_SASS_PROTOCOL: 'false'},
77+
});
78+
} else {
79+
console.log('Building the Dart Sass executable.');
80+
shell.exec('dart run grinder protobuf pkg-standalone-dev', {
81+
cwd: repoPath,
82+
env: {...process.env, UPDATE_SASS_PROTOCOL: 'false'},
83+
});
84+
}
6185
}

tool/init.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ const argv = yargs(process.argv.slice(2))
1818
type: 'string',
1919
description: 'Build the Embedded Dart Sass binary from this Git ref.',
2020
})
21+
.option('compiler-js', {
22+
type: 'boolean',
23+
description: 'Build the Embedded Dart Sass with dart2js.',
24+
})
2125
.option('skip-compiler', {
2226
type: 'boolean',
2327
description: "Don't Embedded Dart Sass at all.",
@@ -55,15 +59,15 @@ void (async () => {
5559

5660
if (!argv['skip-compiler']) {
5761
if (argv['compiler-ref']) {
58-
await getEmbeddedCompiler(outPath, {
62+
await getEmbeddedCompiler(argv['compiler-js'], {
5963
ref: argv['compiler-ref'],
6064
});
6165
} else if (argv['compiler-path']) {
62-
await getEmbeddedCompiler(outPath, {
66+
await getEmbeddedCompiler(argv['compiler-js'], {
6367
path: argv['compiler-path'],
6468
});
6569
} else {
66-
await getEmbeddedCompiler(outPath);
70+
await getEmbeddedCompiler(argv['compiler-js']);
6771
}
6872
}
6973

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy