Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): switch to use Sass modern API
Browse files Browse the repository at this point in the history
Sass modern API provides faster compilations times when used in an async manner.

Users can temporary opt-out from using the modern API by setting `NG_BUILD_LEGACY_SASS` to `true` or `1`.

Application compilation duration | Sass API and Compiler
-- | --
60852ms | dart-sass legacy sync API
52666ms | dart-sass modern API

Note: https://github.com/johannesjo/super-productivity was used for benchmarking.

Prior art: http://docs/document/d/1CvEceWMpBoEBd8SfvksGMdVHxaZMH93b0EGS3XbR3_Q?resourcekey=0-vFm-xMspT65FZLIyX7xWFQ
  • Loading branch information
alan-agius4 committed Jul 26, 2022
1 parent 6e8808e commit 6c06936
Show file tree
Hide file tree
Showing 13 changed files with 637 additions and 186 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@
"eslint-plugin-header": "3.1.1",
"eslint-plugin-import": "2.26.0",
"express": "4.18.1",
"font-awesome": "^4.7.0",
"glob": "8.0.3",
"http-proxy": "^1.18.1",
"https-proxy-agent": "5.0.1",
Expand Down
1 change: 0 additions & 1 deletion packages/angular_devkit/build_angular/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,6 @@ LARGE_SPECS = {
"@npm//@angular/animations",
"@npm//@angular/material",
"@npm//bootstrap",
"@npm//font-awesome",
"@npm//jquery",
"@npm//popper.js",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,74 @@
* found in the LICENSE file at https://angular.io/license
*/

import type { Plugin, PluginBuild } from 'esbuild';
import type { LegacyResult } from 'sass';
import { SassWorkerImplementation } from '../../sass/sass-service';
import type { PartialMessage, Plugin, PluginBuild } from 'esbuild';
import type { CompileResult } from 'sass';
import { fileURLToPath } from 'url';

export function createSassPlugin(options: { sourcemap: boolean; includePaths?: string[] }): Plugin {
export function createSassPlugin(options: { sourcemap: boolean; loadPaths?: string[] }): Plugin {
return {
name: 'angular-sass',
setup(build: PluginBuild): void {
let sass: SassWorkerImplementation;
let sass: typeof import('sass');

build.onStart(() => {
sass = new SassWorkerImplementation();
build.onStart(async () => {
// Lazily load Sass
sass = await import('sass');
});

build.onEnd(() => {
sass?.close();
});

build.onLoad({ filter: /\.s[ac]ss$/ }, async (args) => {
const result = await new Promise<LegacyResult>((resolve, reject) => {
sass.render(
{
file: args.path,
includePaths: options.includePaths,
indentedSyntax: args.path.endsWith('.sass'),
outputStyle: 'expanded',
sourceMap: options.sourcemap,
sourceMapContents: options.sourcemap,
sourceMapEmbed: options.sourcemap,
quietDeps: true,
},
(error, result) => {
if (error) {
reject(error);
}
if (result) {
resolve(result);
}
build.onLoad({ filter: /\.s[ac]ss$/ }, (args) => {
try {
const warnings: PartialMessage[] = [];
// Use sync version as async version is slower.
const { css, sourceMap, loadedUrls } = sass.compile(args.path, {
style: 'expanded',
loadPaths: options.loadPaths,
sourceMap: options.sourcemap,
sourceMapIncludeSources: options.sourcemap,
quietDeps: true,
logger: {
warn: (text, _options) => {
warnings.push({
text,
});
},
},
);
});

return {
contents: result.css,
loader: 'css',
watchFiles: result.stats.includedFiles,
};
});

return {
loader: 'css',
contents: css + sourceMapToUrlComment(sourceMap),
watchFiles: loadedUrls.map((url) => fileURLToPath(url)),
warnings,
};
} catch (error) {
if (error instanceof sass.Exception) {
const file = error.span.url ? fileURLToPath(error.span.url) : undefined;

return {
loader: 'css',
errors: [
{
text: error.toString(),
},
],
watchFiles: file ? [file] : undefined,
};
}

throw error;
}
});
},
};
}

function sourceMapToUrlComment(sourceMap: CompileResult['sourceMap']): string {
if (!sourceMap) {
return '';
}

const urlSourceMap = Buffer.from(JSON.stringify(sourceMap), 'utf-8').toString('base64');

return `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${urlSourceMap}`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ async function bundleStylesheet(
entry: Required<Pick<BuildOptions, 'stdin'> | Pick<BuildOptions, 'entryPoints'>>,
options: BundleStylesheetOptions,
) {
const loadPaths = options.includePaths ?? [];
// Needed to resolve node packages.
loadPaths.push(path.join(options.workspaceRoot, 'node_modules'));

// Execute esbuild
const result = await bundle({
...entry,
Expand All @@ -40,9 +44,7 @@ async function bundleStylesheet(
preserveSymlinks: options.preserveSymlinks,
conditions: ['style', 'sass'],
mainFields: ['style', 'sass'],
plugins: [
createSassPlugin({ sourcemap: !!options.sourcemap, includePaths: options.includePaths }),
],
plugins: [createSassPlugin({ sourcemap: !!options.sourcemap, loadPaths })],
});

// Extract the result of the bundling from the output files
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { Architect } from '@angular-devkit/architect';
import { TestProjectHost } from '@angular-devkit/architect/testing';
import { normalize, tags } from '@angular-devkit/core';
import { dirname } from 'path';
import { browserBuild, createArchitect, host } from '../../../testing/test-utils';
Expand Down Expand Up @@ -259,7 +260,19 @@ describe('Browser Builder styles', () => {
});
});

/**
* font-awesome mock to avoid having an extra dependency.
*/
function mockFontAwesomePackage(host: TestProjectHost): void {
host.writeMultipleFiles({
'node_modules/font-awesome/scss/font-awesome.scss': `
* { color: red }
`,
});
}

it(`supports font-awesome imports`, async () => {
mockFontAwesomePackage(host);
host.writeMultipleFiles({
'src/styles.scss': `
@import "font-awesome/scss/font-awesome";
Expand All @@ -271,6 +284,7 @@ describe('Browser Builder styles', () => {
});

it(`supports font-awesome imports (tilde)`, async () => {
mockFontAwesomePackage(host);
host.writeMultipleFiles({
'src/styles.scss': `
$fa-font-path: "~font-awesome/fonts";
Expand Down
Loading

0 comments on commit 6c06936

Please sign in to comment.