[go: nahoru, domu]

Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): add Sass file support to experim…
Browse files Browse the repository at this point in the history
…ental esbuild-based builder

This change adds support for using Sass stylesheets within an application built with the experimental
esbuild-based browser application builder. Global stylesheets (`styles` build option) and component
stylesheets (`@Component({ styleUrls: [...], ...})`) with Sass can now be used.
The `stylePreprocessorOptions.includePaths` option is also available for Sass stylesheets.
Both the default format (`.scss`) and the indented format (`.sass`) are supported.
Inline component stylesheet support is not yet available with the esbuild-based builder.
  • Loading branch information
clydin authored and dgp1130 committed Jul 11, 2022
1 parent 6693459 commit 248860a
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 69 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ jobs:
name: Execute CLI E2E Tests Subset with esbuild builder
command: |
mkdir /mnt/ramdisk/e2e-esbuild
node ./tests/legacy-cli/run_e2e --nb-shards=${CIRCLE_NODE_TOTAL} --shard=${CIRCLE_NODE_INDEX} <<# parameters.snapshots >>--ng-snapshots<</ parameters.snapshots >> --esbuild --tmpdir=/mnt/ramdisk/e2e-esbuild --glob="{tests/basic/**,tests/build/prod-build.ts,tests/commands/add/add-pwa.ts}" --ignore="tests/basic/{environment,rebuild,serve,scripts-array}.ts"
node ./tests/legacy-cli/run_e2e --nb-shards=${CIRCLE_NODE_TOTAL} --shard=${CIRCLE_NODE_INDEX} <<# parameters.snapshots >>--ng-snapshots<</ parameters.snapshots >> --esbuild --tmpdir=/mnt/ramdisk/e2e-esbuild --glob="{tests/basic/**,tests/build/prod-build.ts,tests/build/styles/scss.ts,tests/build/styles/include-paths.ts,tests/commands/add/add-pwa.ts}" --ignore="tests/basic/{environment,rebuild,serve,scripts-array}.ts"
- fail_fast

test-browsers:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import ts from 'typescript';
import angularApplicationPreset from '../../babel/presets/application';
import { requiresLinking } from '../../babel/webpack-loader';
import { loadEsmModule } from '../../utils/load-esm';
import { BundleStylesheetOptions, bundleStylesheetText } from './stylesheets';
import { BundleStylesheetOptions, bundleStylesheetFile, bundleStylesheetText } from './stylesheets';

interface EmitFileResult {
content?: string;
Expand Down Expand Up @@ -191,17 +191,27 @@ export function createCompilerPlugin(
// Create TypeScript compiler host
const host = ts.createIncrementalCompilerHost(compilerOptions);

// Temporarily add a readResource hook to allow for a transformResource hook.
// Once the AOT compiler allows only a transformResource hook this can be removed.
(host as CompilerHost).readResource = function (fileName) {
// Provide same no file found behavior as @ngtools/webpack
return this.readFile(fileName) ?? '';
// Temporarily process external resources via readResource.
// The AOT compiler currently requires this hook to allow for a transformResource hook.
// Once the AOT compiler allows only a transformResource hook, this can be reevaluated.
(host as CompilerHost).readResource = async function (fileName) {
// Template resources (.html) files are not bundled or transformed
if (fileName.endsWith('.html')) {
return this.readFile(fileName) ?? '';
}

const { contents, errors, warnings } = await bundleStylesheetFile(fileName, styleOptions);

(result.errors ??= []).push(...errors);
(result.warnings ??= []).push(...warnings);

return contents;
};

// Add an AOT compiler resource transform hook
(host as CompilerHost).transformResource = async function (data, context) {
// Only style resources are transformed currently
if (context.type !== 'style') {
// Only inline style resources are transformed separately currently
if (context.resourceFile || context.type !== 'style') {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,9 @@ export async function buildEsbuildBrowser(
{ virtualName: `angular:style/global;${name}`, resolvePath: workspaceRoot },
{
optimization: !!optimizationOptions.styles.minify,
sourcemap: !!sourcemapOptions.styles,
sourcemap: !!sourcemapOptions.styles && (sourcemapOptions.hidden ? 'external' : true),
outputNames: noInjectNames.includes(name) ? { media: outputNames.media } : outputNames,
includePaths: options.stylePreprocessorOptions?.includePaths,
},
);

Expand Down Expand Up @@ -334,6 +335,7 @@ async function bundleCode(
// of sourcemap processing.
!!sourcemapOptions.styles && (sourcemapOptions.hidden ? false : 'inline'),
outputNames,
includePaths: options.stylePreprocessorOptions?.includePaths,
},
),
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* 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';

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

build.onStart(() => {
sass = new SassWorkerImplementation();
});

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);
}
},
);
});

return {
contents: result.css,
loader: 'css',
watchFiles: result.stats.includedFiles,
};
});
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
import type { BuildOptions, OutputFile } from 'esbuild';
import * as path from 'path';
import { DEFAULT_OUTDIR, bundle } from './esbuild';
import { createSassPlugin } from './sass-plugin';

export interface BundleStylesheetOptions {
workspaceRoot?: string;
optimization: boolean;
preserveSymlinks?: boolean;
sourcemap: boolean | 'external' | 'inline';
outputNames?: { bundles?: string; media?: string };
includePaths?: string[];
}

async function bundleStylesheet(
Expand All @@ -39,7 +41,7 @@ async function bundleStylesheet(
conditions: ['style'],
mainFields: ['style'],
plugins: [
// TODO: preprocessor plugins
createSassPlugin({ sourcemap: !!options.sourcemap, includePaths: options.includePaths }),
],
});

Expand Down
124 changes: 66 additions & 58 deletions tests/legacy-cli/e2e/tests/build/styles/include-paths.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,86 @@
import { getGlobalVariable } from '../../../utils/env';
import { writeMultipleFiles, expectFileToMatch, replaceInFile, createDir } from '../../../utils/fs';
import { ng } from '../../../utils/process';
import { updateJsonFile } from '../../../utils/project';

export default function () {
return (
Promise.resolve()
.then(() => createDir('src/style-paths'))
.then(() =>
writeMultipleFiles({
'src/style-paths/_variables.scss': '$primary-color: red;',
'src/styles.scss': `
export default async function () {
// esbuild currently only supports Sass
const esbuild = getGlobalVariable('argv')['esbuild'];

await createDir('src/style-paths');
await writeMultipleFiles({
'src/style-paths/_variables.scss': '$primary-color: red;',
'src/styles.scss': `
@import 'variables';
h1 { color: $primary-color; }
`,
'src/app/app.component.scss': `
`,
'src/app/app.component.scss': `
@import 'variables';
h2 { background-color: $primary-color; }
`,
'src/style-paths/variables.styl': '$primary-color = green',
'src/styles.styl': `
`,
'src/style-paths/variables.styl': '$primary-color = green',
'src/styles.styl': `
@import 'variables'
h3
color: $primary-color
`,
'src/app/app.component.styl': `
`,
'src/app/app.component.styl': `
@import 'variables'
h4
background-color: $primary-color
`,
'src/style-paths/variables.less': '@primary-color: #ADDADD;',
'src/styles.less': `
`,
'src/style-paths/variables.less': '@primary-color: #ADDADD;',
'src/styles.less': `
@import 'variables';
h5 { color: @primary-color; }
`,
'src/app/app.component.less': `
`,
'src/app/app.component.less': `
@import 'variables';
h6 { color: @primary-color; }
`,
}),
)
.then(() =>
replaceInFile(
'src/app/app.component.ts',
`'./app.component.css\'`,
`'./app.component.scss', './app.component.styl', './app.component.less'`,
),
)
.then(() =>
updateJsonFile('angular.json', (workspaceJson) => {
const appArchitect = workspaceJson.projects['test-project'].architect;
appArchitect.build.options.styles = [
{ input: 'src/styles.scss' },
{ input: 'src/styles.styl' },
{ input: 'src/styles.less' },
];
appArchitect.build.options.stylePreprocessorOptions = {
includePaths: ['src/style-paths'],
};
}),
)
// files were created successfully
.then(() => ng('build', '--configuration=development'))
.then(() => expectFileToMatch('dist/test-project/styles.css', /h1\s*{\s*color: red;\s*}/))
.then(() => expectFileToMatch('dist/test-project/main.js', /h2.*{.*color: red;.*}/))
.then(() => expectFileToMatch('dist/test-project/styles.css', /h3\s*{\s*color: #008000;\s*}/))
.then(() => expectFileToMatch('dist/test-project/main.js', /h4.*{.*color: #008000;.*}/))
.then(() => expectFileToMatch('dist/test-project/styles.css', /h5\s*{\s*color: #ADDADD;\s*}/))
.then(() => expectFileToMatch('dist/test-project/main.js', /h6.*{.*color: #ADDADD;.*}/))
.then(() => ng('build', '--aot', '--configuration=development'))
.then(() => expectFileToMatch('dist/test-project/styles.css', /h1\s*{\s*color: red;\s*}/))
.then(() => expectFileToMatch('dist/test-project/main.js', /h2.*{.*color: red;.*}/))
.then(() => expectFileToMatch('dist/test-project/styles.css', /h3\s*{\s*color: #008000;\s*}/))
.then(() => expectFileToMatch('dist/test-project/main.js', /h4.*{.*color: #008000;.*}/))
.then(() => expectFileToMatch('dist/test-project/styles.css', /h5\s*{\s*color: #ADDADD;\s*}/))
.then(() => expectFileToMatch('dist/test-project/main.js', /h6.*{.*color: #ADDADD;.*}/))
`,
});

await replaceInFile(
'src/app/app.component.ts',
`'./app.component.css\'`,
`'./app.component.scss'` + (esbuild ? '' : `, './app.component.styl', './app.component.less'`),
);

await updateJsonFile('angular.json', (workspaceJson) => {
const appArchitect = workspaceJson.projects['test-project'].architect;
appArchitect.build.options.styles = [{ input: 'src/styles.scss' }];
if (!esbuild) {
appArchitect.build.options.styles.push(
{ input: 'src/styles.styl' },
{ input: 'src/styles.less' },
);
}
appArchitect.build.options.stylePreprocessorOptions = {
includePaths: ['src/style-paths'],
};
});

await ng('build', '--configuration=development');

expectFileToMatch('dist/test-project/styles.css', /h1\s*{\s*color: red;\s*}/);
expectFileToMatch('dist/test-project/main.js', /h2.*{.*color: red;.*}/);
if (!esbuild) {
// These checks are for the less and stylus files
expectFileToMatch('dist/test-project/styles.css', /h3\s*{\s*color: #008000;\s*}/);
expectFileToMatch('dist/test-project/main.js', /h4.*{.*color: #008000;.*}/);
expectFileToMatch('dist/test-project/styles.css', /h5\s*{\s*color: #ADDADD;\s*}/);
expectFileToMatch('dist/test-project/main.js', /h6.*{.*color: #ADDADD;.*}/);
}

// esbuild currently only supports AOT and not JIT mode
if (!esbuild) {
ng('build', '--no-aot', '--configuration=development');

expectFileToMatch('dist/test-project/styles.css', /h1\s*{\s*color: red;\s*}/);
expectFileToMatch('dist/test-project/main.js', /h2.*{.*color: red;.*}/);
expectFileToMatch('dist/test-project/styles.css', /h3\s*{\s*color: #008000;\s*}/);
expectFileToMatch('dist/test-project/main.js', /h4.*{.*color: #008000;.*}/);
expectFileToMatch('dist/test-project/styles.css', /h5\s*{\s*color: #ADDADD;\s*}/);
expectFileToMatch('dist/test-project/main.js', /h6.*{.*color: #ADDADD;.*}/);
}
}

0 comments on commit 248860a

Please sign in to comment.