Skip to content

Commit bac563e

Browse files
committed
feat(@angular-devkit/build-angular): support specifying stylesheet language for inline component styles
A new build option named `inlineStyleLanguage` has been introduced that will allow a project to define the stylesheet language used in an application's inline component styles. Inline component styles are styles defined via the `styles` property within the Angular `Component` decorator. Both JIT and AOT mode are supported. However, JIT mode requires that inline styles only be string literals (compile-time partial evaluation is not supported in JIT mode). Currently supported language options are: `CSS` (default), `Sass`, `SCSS`, and `Less`. If the option is not specified, `CSS` will be used and enables existing projects to continue to function as expected.
1 parent 3302352 commit bac563e

File tree

9 files changed

+199
-4
lines changed

9 files changed

+199
-4
lines changed

packages/angular_devkit/build_angular/src/browser/schema.json

+11
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@
4040
"$ref": "#/definitions/extraEntryPoint"
4141
}
4242
},
43+
"inlineStyleLanguage": {
44+
"description": "The stylesheet language to use for the application's inline component styles.",
45+
"type": "string",
46+
"default": "css",
47+
"enum": [
48+
"css",
49+
"less",
50+
"sass",
51+
"scss"
52+
]
53+
},
4354
"stylePreprocessorOptions": {
4455
"description": "Options to pass to style preprocessors.",
4556
"type": "object",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { buildWebpackBrowser } from '../../index';
9+
import { InlineStyleLanguage } from '../../schema';
10+
import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';
11+
12+
describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => {
13+
describe('Option: "inlineStyleLanguage"', () => {
14+
beforeEach(async () => {
15+
// Setup application component with inline style property
16+
await harness.modifyFile('src/app/app.component.ts', (content) => {
17+
return content
18+
.replace('styleUrls', 'styles')
19+
.replace('./app.component.css', '__STYLE_MARKER__');
20+
});
21+
});
22+
23+
for (const aot of [true, false]) {
24+
describe(`[${aot ? 'AOT' : 'JIT'}]`, () => {
25+
it('supports SCSS inline component styles when set to "scss"', async () => {
26+
harness.useTarget('build', {
27+
...BASE_OPTIONS,
28+
inlineStyleLanguage: InlineStyleLanguage.Scss,
29+
aot,
30+
});
31+
32+
await harness.modifyFile('src/app/app.component.ts', (content) =>
33+
content.replace(
34+
'__STYLE_MARKER__',
35+
'$primary-color: green;\\nh1 { color: $primary-color; }',
36+
),
37+
);
38+
39+
const { result } = await harness.executeOnce();
40+
41+
expect(result?.success).toBe(true);
42+
harness.expectFile('dist/main.js').content.toContain('color: green');
43+
});
44+
45+
it('supports Sass inline component styles when set to "sass"', async () => {
46+
harness.useTarget('build', {
47+
...BASE_OPTIONS,
48+
inlineStyleLanguage: InlineStyleLanguage.Sass,
49+
aot,
50+
});
51+
52+
await harness.modifyFile('src/app/app.component.ts', (content) =>
53+
content.replace(
54+
'__STYLE_MARKER__',
55+
'$primary-color: green\\nh1\\n\\tcolor: $primary-color',
56+
),
57+
);
58+
59+
const { result } = await harness.executeOnce();
60+
61+
expect(result?.success).toBe(true);
62+
harness.expectFile('dist/main.js').content.toContain('color: green');
63+
});
64+
65+
// Stylus currently does not function due to the sourcemap logic within the `stylus-loader`
66+
// which tries to read each stylesheet directly from disk. In this case, each stylesheet is
67+
// virtual and cannot be read from disk. This issue affects data URIs in general.
68+
// xit('supports Stylus inline component styles when set to "stylus"', async () => {
69+
// harness.useTarget('build', {
70+
// ...BASE_OPTIONS,
71+
// inlineStyleLanguage: InlineStyleLanguage.Stylus,
72+
// aot,
73+
// });
74+
75+
// await harness.modifyFile('src/app/app.component.ts', (content) =>
76+
// content.replace(
77+
// '__STYLE_MARKER__',
78+
// '$primary-color = green;\\nh1 { color: $primary-color; }',
79+
// ),
80+
// );
81+
82+
// const { result } = await harness.executeOnce();
83+
84+
// expect(result?.success).toBe(true);
85+
// harness.expectFile('dist/main.js').content.toContain('color: green');
86+
// });
87+
88+
it('supports Less inline component styles when set to "less"', async () => {
89+
harness.useTarget('build', {
90+
...BASE_OPTIONS,
91+
inlineStyleLanguage: InlineStyleLanguage.Less,
92+
aot,
93+
});
94+
95+
await harness.modifyFile('src/app/app.component.ts', (content) =>
96+
content.replace(
97+
'__STYLE_MARKER__',
98+
'@primary-color: green;\\nh1 { color: @primary-color; }',
99+
),
100+
);
101+
102+
const { result } = await harness.executeOnce();
103+
104+
expect(result?.success).toBe(true);
105+
harness.expectFile('dist/main.js').content.toContain('color: green');
106+
});
107+
});
108+
}
109+
});
110+
});

packages/angular_devkit/build_angular/src/karma/schema.json

+11
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@
4444
"$ref": "#/definitions/extraEntryPoint"
4545
}
4646
},
47+
"inlineStyleLanguage": {
48+
"description": "The stylesheet language to use for the application's inline component styles.",
49+
"type": "string",
50+
"default": "css",
51+
"enum": [
52+
"css",
53+
"less",
54+
"sass",
55+
"scss"
56+
]
57+
},
4758
"stylePreprocessorOptions": {
4859
"description": "Options to pass to style preprocessors",
4960
"type": "object",

packages/angular_devkit/build_angular/src/server/schema.json

+11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@
1313
"default": "tsconfig.app.json",
1414
"description": "The name of the TypeScript configuration file."
1515
},
16+
"inlineStyleLanguage": {
17+
"description": "The stylesheet language to use for the application's inline component styles.",
18+
"type": "string",
19+
"default": "css",
20+
"enum": [
21+
"css",
22+
"less",
23+
"sass",
24+
"scss"
25+
]
26+
},
1627
"stylePreprocessorOptions": {
1728
"description": "Options to pass to style preprocessors",
1829
"type": "object",

packages/angular_devkit/build_angular/src/utils/build-options.ts

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
ExtraEntryPoint,
1616
I18NMissingTranslation,
1717
IndexUnion,
18+
InlineStyleLanguage,
1819
Localize,
1920
SourceMapClass,
2021
} from '../browser/schema';
@@ -67,6 +68,7 @@ export interface BuildOptions {
6768
stylePreprocessorOptions?: { includePaths: string[] };
6869
platform?: 'browser' | 'server';
6970
fileReplacements: NormalizedFileReplacement[];
71+
inlineStyleLanguage?: InlineStyleLanguage;
7072

7173
allowedCommonJsDependencies?: string[];
7274

packages/angular_devkit/build_angular/src/webpack/configs/styles.ts

+34-1
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,8 @@ export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuratio
264264
use: [],
265265
},
266266
{
267-
extensions: ['sass', 'scss'],
267+
extensions: ['scss'],
268+
mimetype: 'text/x-scss',
268269
use: [
269270
{
270271
loader: require.resolve('resolve-url-loader'),
@@ -291,8 +292,39 @@ export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuratio
291292
},
292293
],
293294
},
295+
{
296+
extensions: ['sass'],
297+
mimetype: 'text/x-sass',
298+
use: [
299+
{
300+
loader: require.resolve('resolve-url-loader'),
301+
options: {
302+
sourceMap: cssSourceMap,
303+
},
304+
},
305+
{
306+
loader: require.resolve('sass-loader'),
307+
options: {
308+
implementation: sassImplementation,
309+
sourceMap: true,
310+
sassOptions: {
311+
indentedSyntax: true,
312+
// bootstrap-sass requires a minimum precision of 8
313+
precision: 8,
314+
includePaths,
315+
// Use expanded as otherwise sass will remove comments that are needed for autoprefixer
316+
// Ex: /* autoprefixer grid: autoplace */
317+
// tslint:disable-next-line: max-line-length
318+
// See: https://github.com/webpack-contrib/sass-loader/blob/45ad0be17264ceada5f0b4fb87e9357abe85c4ff/src/getSassOptions.js#L68-L70
319+
outputStyle: 'expanded',
320+
},
321+
},
322+
},
323+
],
324+
},
294325
{
295326
extensions: ['less'],
327+
mimetype: 'text/x-less',
296328
use: [
297329
{
298330
loader: require.resolve('less-loader'),
@@ -309,6 +341,7 @@ export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuratio
309341
},
310342
{
311343
extensions: ['styl'],
344+
mimetype: 'text/x-stylus',
312345
use: [
313346
{
314347
loader: require.resolve('stylus-loader'),

packages/angular_devkit/build_angular/src/webpack/configs/typescript.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,30 @@ function createIvyPlugin(
5151
}
5252
}
5353

54+
let inlineStyleMimeType;
55+
switch (buildOptions.inlineStyleLanguage) {
56+
case 'less':
57+
inlineStyleMimeType = 'text/x-less';
58+
break;
59+
case 'sass':
60+
inlineStyleMimeType = 'text/x-sass';
61+
break;
62+
case 'scss':
63+
inlineStyleMimeType = 'text/x-scss';
64+
break;
65+
case 'css':
66+
default:
67+
inlineStyleMimeType = 'text/css';
68+
break;
69+
}
70+
5471
return new AngularWebpackPlugin({
5572
tsconfig,
5673
compilerOptions,
5774
fileReplacements,
5875
jitMode: !aot,
5976
emitNgModuleScope: !optimize,
60-
inlineStyleMimeType: 'text/css',
77+
inlineStyleMimeType,
6178
});
6279
}
6380

tests/legacy-cli/e2e/tests/build/differential-loading-sri.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export default async function () {
5656
'--output-path=dist/second',
5757
);
5858

59-
const chunkId = '751';
59+
const chunkId = '730';
6060
const codeHashES5 = createHash('sha384')
6161
.update(await readFile(`dist/first/${chunkId}-es5.js`))
6262
.digest('base64');

tests/legacy-cli/e2e/tests/build/worker.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export default async function () {
3535
await expectFileToMatch('dist/test-project/main-es2017.js', 'src_app_app_worker_ts');
3636

3737
await ng('build', '--output-hashing=none');
38-
const chunkId = '283';
38+
const chunkId = '137';
3939
await expectFileToExist(`dist/test-project/${chunkId}-es5.js`);
4040
await expectFileToMatch('dist/test-project/main-es5.js', chunkId);
4141
await expectFileToExist(`dist/test-project/${chunkId}-es2017.js`);

0 commit comments

Comments
 (0)