Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): support specifying stylesheet la…
Browse files Browse the repository at this point in the history
…nguage 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.
  • Loading branch information
clydin committed Apr 14, 2021
1 parent 3302352 commit bac563e
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 4 deletions.
11 changes: 11 additions & 0 deletions packages/angular_devkit/build_angular/src/browser/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@
"$ref": "#/definitions/extraEntryPoint"
}
},
"inlineStyleLanguage": {
"description": "The stylesheet language to use for the application's inline component styles.",
"type": "string",
"default": "css",
"enum": [
"css",
"less",
"sass",
"scss"
]
},
"stylePreprocessorOptions": {
"description": "Options to pass to style preprocessors.",
"type": "object",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* @license
* Copyright Google Inc. 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 { buildWebpackBrowser } from '../../index';
import { InlineStyleLanguage } from '../../schema';
import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';

describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => {
describe('Option: "inlineStyleLanguage"', () => {
beforeEach(async () => {
// Setup application component with inline style property
await harness.modifyFile('src/app/app.component.ts', (content) => {
return content
.replace('styleUrls', 'styles')
.replace('./app.component.css', '__STYLE_MARKER__');
});
});

for (const aot of [true, false]) {
describe(`[${aot ? 'AOT' : 'JIT'}]`, () => {
it('supports SCSS inline component styles when set to "scss"', async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
inlineStyleLanguage: InlineStyleLanguage.Scss,
aot,
});

await harness.modifyFile('src/app/app.component.ts', (content) =>
content.replace(
'__STYLE_MARKER__',
'$primary-color: green;\\nh1 { color: $primary-color; }',
),
);

const { result } = await harness.executeOnce();

expect(result?.success).toBe(true);
harness.expectFile('dist/main.js').content.toContain('color: green');
});

it('supports Sass inline component styles when set to "sass"', async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
inlineStyleLanguage: InlineStyleLanguage.Sass,
aot,
});

await harness.modifyFile('src/app/app.component.ts', (content) =>
content.replace(
'__STYLE_MARKER__',
'$primary-color: green\\nh1\\n\\tcolor: $primary-color',
),
);

const { result } = await harness.executeOnce();

expect(result?.success).toBe(true);
harness.expectFile('dist/main.js').content.toContain('color: green');
});

// Stylus currently does not function due to the sourcemap logic within the `stylus-loader`
// which tries to read each stylesheet directly from disk. In this case, each stylesheet is
// virtual and cannot be read from disk. This issue affects data URIs in general.
// xit('supports Stylus inline component styles when set to "stylus"', async () => {
// harness.useTarget('build', {
// ...BASE_OPTIONS,
// inlineStyleLanguage: InlineStyleLanguage.Stylus,
// aot,
// });

// await harness.modifyFile('src/app/app.component.ts', (content) =>
// content.replace(
// '__STYLE_MARKER__',
// '$primary-color = green;\\nh1 { color: $primary-color; }',
// ),
// );

// const { result } = await harness.executeOnce();

// expect(result?.success).toBe(true);
// harness.expectFile('dist/main.js').content.toContain('color: green');
// });

it('supports Less inline component styles when set to "less"', async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
inlineStyleLanguage: InlineStyleLanguage.Less,
aot,
});

await harness.modifyFile('src/app/app.component.ts', (content) =>
content.replace(
'__STYLE_MARKER__',
'@primary-color: green;\\nh1 { color: @primary-color; }',
),
);

const { result } = await harness.executeOnce();

expect(result?.success).toBe(true);
harness.expectFile('dist/main.js').content.toContain('color: green');
});
});
}
});
});
11 changes: 11 additions & 0 deletions packages/angular_devkit/build_angular/src/karma/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@
"$ref": "#/definitions/extraEntryPoint"
}
},
"inlineStyleLanguage": {
"description": "The stylesheet language to use for the application's inline component styles.",
"type": "string",
"default": "css",
"enum": [
"css",
"less",
"sass",
"scss"
]
},
"stylePreprocessorOptions": {
"description": "Options to pass to style preprocessors",
"type": "object",
Expand Down
11 changes: 11 additions & 0 deletions packages/angular_devkit/build_angular/src/server/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@
"default": "tsconfig.app.json",
"description": "The name of the TypeScript configuration file."
},
"inlineStyleLanguage": {
"description": "The stylesheet language to use for the application's inline component styles.",
"type": "string",
"default": "css",
"enum": [
"css",
"less",
"sass",
"scss"
]
},
"stylePreprocessorOptions": {
"description": "Options to pass to style preprocessors",
"type": "object",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ExtraEntryPoint,
I18NMissingTranslation,
IndexUnion,
InlineStyleLanguage,
Localize,
SourceMapClass,
} from '../browser/schema';
Expand Down Expand Up @@ -67,6 +68,7 @@ export interface BuildOptions {
stylePreprocessorOptions?: { includePaths: string[] };
platform?: 'browser' | 'server';
fileReplacements: NormalizedFileReplacement[];
inlineStyleLanguage?: InlineStyleLanguage;

allowedCommonJsDependencies?: string[];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuratio
use: [],
},
{
extensions: ['sass', 'scss'],
extensions: ['scss'],
mimetype: 'text/x-scss',
use: [
{
loader: require.resolve('resolve-url-loader'),
Expand All @@ -291,8 +292,39 @@ export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuratio
},
],
},
{
extensions: ['sass'],
mimetype: 'text/x-sass',
use: [
{
loader: require.resolve('resolve-url-loader'),
options: {
sourceMap: cssSourceMap,
},
},
{
loader: require.resolve('sass-loader'),
options: {
implementation: sassImplementation,
sourceMap: true,
sassOptions: {
indentedSyntax: true,
// bootstrap-sass requires a minimum precision of 8
precision: 8,
includePaths,
// Use expanded as otherwise sass will remove comments that are needed for autoprefixer
// Ex: /* autoprefixer grid: autoplace */
// tslint:disable-next-line: max-line-length
// See: https://github.com/webpack-contrib/sass-loader/blob/45ad0be17264ceada5f0b4fb87e9357abe85c4ff/src/getSassOptions.js#L68-L70
outputStyle: 'expanded',
},
},
},
],
},
{
extensions: ['less'],
mimetype: 'text/x-less',
use: [
{
loader: require.resolve('less-loader'),
Expand All @@ -309,6 +341,7 @@ export function getStylesConfig(wco: WebpackConfigOptions): webpack.Configuratio
},
{
extensions: ['styl'],
mimetype: 'text/x-stylus',
use: [
{
loader: require.resolve('stylus-loader'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,30 @@ function createIvyPlugin(
}
}

let inlineStyleMimeType;
switch (buildOptions.inlineStyleLanguage) {
case 'less':
inlineStyleMimeType = 'text/x-less';
break;
case 'sass':
inlineStyleMimeType = 'text/x-sass';
break;
case 'scss':
inlineStyleMimeType = 'text/x-scss';
break;
case 'css':
default:
inlineStyleMimeType = 'text/css';
break;
}

return new AngularWebpackPlugin({
tsconfig,
compilerOptions,
fileReplacements,
jitMode: !aot,
emitNgModuleScope: !optimize,
inlineStyleMimeType: 'text/css',
inlineStyleMimeType,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export default async function () {
'--output-path=dist/second',
);

const chunkId = '751';
const chunkId = '730';
const codeHashES5 = createHash('sha384')
.update(await readFile(`dist/first/${chunkId}-es5.js`))
.digest('base64');
Expand Down
2 changes: 1 addition & 1 deletion tests/legacy-cli/e2e/tests/build/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default async function () {
await expectFileToMatch('dist/test-project/main-es2017.js', 'src_app_app_worker_ts');

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

0 comments on commit bac563e

Please sign in to comment.