Skip to content

Commit 7a6a4ec

Browse files
committed
refactor(@ngtools/webpack): use Webpack inline resource matching for inline resources
Webpack provides a method to construct a special request string that will remap a loader chain to appear to be from a specific resource path. This requires a custom loader, however, it allows rule matching to leverage the existing file extension based approach and allows providing a path to other loaders which simplifies relative request handling.
1 parent 861a695 commit 7a6a4ec

File tree

6 files changed

+73
-5
lines changed

6 files changed

+73
-5
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC 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+
9+
import { Compilation, LoaderContext } from 'webpack';
10+
11+
export const InlineAngularResourceSymbol = Symbol();
12+
13+
export interface CompilationWithInlineAngularResource extends Compilation {
14+
[InlineAngularResourceSymbol]: string;
15+
}
16+
17+
export default function (this: LoaderContext<{ data?: string }>) {
18+
const callback = this.async();
19+
const { data } = this.getOptions();
20+
21+
if (data) {
22+
callback(undefined, Buffer.from(data, 'base64').toString());
23+
} else {
24+
const content = (this._compilation as CompilationWithInlineAngularResource)[
25+
InlineAngularResourceSymbol
26+
];
27+
callback(undefined, content);
28+
}
29+
}

packages/ngtools/webpack/src/ivy/host.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ import { normalizePath } from './paths';
1919
export function augmentHostWithResources(
2020
host: ts.CompilerHost,
2121
resourceLoader: WebpackResourceLoader,
22-
options: { directTemplateLoading?: boolean; inlineStyleMimeType?: string } = {},
22+
options: {
23+
directTemplateLoading?: boolean;
24+
inlineStyleMimeType?: string;
25+
inlineStyleFileExtension?: string;
26+
} = {},
2327
) {
2428
const resourceHost = host as CompilerHost;
2529

@@ -57,10 +61,11 @@ export function augmentHostWithResources(
5761
return null;
5862
}
5963

60-
if (options.inlineStyleMimeType) {
64+
if (options.inlineStyleMimeType || options.inlineStyleFileExtension) {
6165
const content = await resourceLoader.process(
6266
data,
6367
options.inlineStyleMimeType,
68+
options.inlineStyleFileExtension,
6469
context.type,
6570
context.containingFile,
6671
);

packages/ngtools/webpack/src/ivy/plugin.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export interface AngularWebpackPluginOptions {
5555
emitNgModuleScope: boolean;
5656
jitMode: boolean;
5757
inlineStyleMimeType?: string;
58+
inlineStyleFileExtension?: string;
5859
}
5960

6061
// Add support for missing properties in Webpack types as well as the loader's file emitter
@@ -241,6 +242,7 @@ export class AngularWebpackPlugin {
241242
augmentHostWithResources(host, resourceLoader, {
242243
directTemplateLoading: this.pluginOptions.directTemplateLoading,
243244
inlineStyleMimeType: this.pluginOptions.inlineStyleMimeType,
245+
inlineStyleFileExtension: this.pluginOptions.inlineStyleFileExtension,
244246
});
245247

246248
// Setup source file adjustment options

packages/ngtools/webpack/src/ivy/transformation.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ export function createAotTransformers(
3636

3737
export function createJitTransformers(
3838
builder: ts.BuilderProgram,
39-
options: { directTemplateLoading?: boolean; inlineStyleMimeType?: string },
39+
options: {
40+
directTemplateLoading?: boolean;
41+
inlineStyleMimeType?: string;
42+
inlineStyleFileExtension?: string;
43+
},
4044
): ts.CustomTransformers {
4145
const getTypeChecker = () => builder.getProgram().getTypeChecker();
4246

@@ -47,6 +51,7 @@ export function createJitTransformers(
4751
getTypeChecker,
4852
options.directTemplateLoading,
4953
options.inlineStyleMimeType,
54+
options.inlineStyleFileExtension,
5055
),
5156
constructorParametersDownlevelTransform(builder.getProgram()),
5257
],

packages/ngtools/webpack/src/resource_loader.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import { createHash } from 'crypto';
1010
import * as path from 'path';
1111
import * as vm from 'vm';
1212
import { Asset, Compilation, EntryPlugin, NormalModule, library, node, sources } from 'webpack';
13+
import {
14+
CompilationWithInlineAngularResource,
15+
InlineAngularResourceSymbol,
16+
} from './inline-data-loader';
1317
import { normalizePath } from './ivy/paths';
1418

1519
interface CompilationOutput {
@@ -30,6 +34,8 @@ export class WebpackResourceLoader {
3034
private modifiedResources = new Set<string>();
3135
private outputPathCounter = 1;
3236

37+
private readonly inlineDataLoaderPath = require.resolve('./inline-data-loader');
38+
3339
constructor(shouldCache: boolean) {
3440
if (shouldCache) {
3541
this.fileCache = new Map();
@@ -98,6 +104,7 @@ export class WebpackResourceLoader {
98104
filePath?: string,
99105
data?: string,
100106
mimeType?: string,
107+
fileExtension?: string,
101108
resourceType?: 'style' | 'template',
102109
hash?: string,
103110
containingFile?: string,
@@ -107,7 +114,12 @@ export class WebpackResourceLoader {
107114
}
108115

109116
// Create a special URL for reading the resource from memory
110-
const entry = data ? `angular-resource:${resourceType},${hash}` : filePath;
117+
const entry =
118+
filePath ||
119+
(resourceType
120+
? `${containingFile}-${this.outputPathCounter}.${fileExtension}!=!${this.inlineDataLoaderPath}!${containingFile}`
121+
: `angular-resource:${resourceType},${hash}`);
122+
111123
if (!entry) {
112124
throw new Error(`"filePath" or "data" must be specified.`);
113125
}
@@ -166,6 +178,8 @@ export class WebpackResourceLoader {
166178
NormalModule.getCompilationHooks(compilation)
167179
.readResourceForScheme.for('angular-resource')
168180
.tap('angular-compiler', () => data);
181+
182+
(compilation as CompilationWithInlineAngularResource)[InlineAngularResourceSymbol] = data;
169183
}
170184

171185
compilation.hooks.additionalAssets.tap('angular-compiler', () => {
@@ -314,7 +328,8 @@ export class WebpackResourceLoader {
314328

315329
async process(
316330
data: string,
317-
mimeType: string,
331+
mimeType: string | undefined,
332+
fileExtension: string | undefined,
318333
resourceType: 'template' | 'style',
319334
containingFile?: string,
320335
): Promise<string> {
@@ -330,6 +345,7 @@ export class WebpackResourceLoader {
330345
undefined,
331346
data,
332347
mimeType,
348+
fileExtension,
333349
resourceType,
334350
cacheKey,
335351
containingFile,

packages/ngtools/webpack/src/transformers/replace_resources.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88

99
import * as ts from 'typescript';
1010

11+
const inlineDataLoaderPath = require.resolve('../inline-data-loader');
12+
1113
export function replaceResources(
1214
shouldTransform: (fileName: string) => boolean,
1315
getTypeChecker: () => ts.TypeChecker,
1416
directTemplateLoading = false,
1517
inlineStyleMimeType?: string,
18+
inlineStyleFileExtension?: string,
1619
): ts.TransformerFactory<ts.SourceFile> {
1720
if (inlineStyleMimeType && !/^text\/[-.\w]+$/.test(inlineStyleMimeType)) {
1821
throw new Error('Invalid inline style MIME type.');
@@ -36,6 +39,7 @@ export function replaceResources(
3639
resourceImportDeclarations,
3740
moduleKind,
3841
inlineStyleMimeType,
42+
inlineStyleFileExtension,
3943
)
4044
: node,
4145
);
@@ -87,6 +91,7 @@ function visitDecorator(
8791
resourceImportDeclarations: ts.ImportDeclaration[],
8892
moduleKind?: ts.ModuleKind,
8993
inlineStyleMimeType?: string,
94+
inlineStyleFileExtension?: string,
9095
): ts.Decorator {
9196
if (!isComponentDecorator(node, typeChecker)) {
9297
return node;
@@ -117,6 +122,7 @@ function visitDecorator(
117122
resourceImportDeclarations,
118123
moduleKind,
119124
inlineStyleMimeType,
125+
inlineStyleFileExtension,
120126
)
121127
: node,
122128
);
@@ -150,6 +156,7 @@ function visitComponentMetadata(
150156
resourceImportDeclarations: ts.ImportDeclaration[],
151157
moduleKind?: ts.ModuleKind,
152158
inlineStyleMimeType?: string,
159+
inlineStyleFileExtension?: string,
153160
): ts.ObjectLiteralElementLike | undefined {
154161
if (!ts.isPropertyAssignment(node) || ts.isComputedPropertyName(node.name)) {
155162
return node;
@@ -198,6 +205,10 @@ function visitComponentMetadata(
198205
if (inlineStyleMimeType) {
199206
const data = Buffer.from(node.text).toString('base64');
200207
url = `data:${inlineStyleMimeType};charset=utf-8;base64,${data}`;
208+
} else if (inlineStyleFileExtension) {
209+
const data = Buffer.from(node.text).toString('base64');
210+
const containingFile = node.getSourceFile().fileName;
211+
url = `${containingFile}.${inlineStyleFileExtension}!=!${inlineDataLoaderPath}?data=${data}!${containingFile}`;
201212
} else {
202213
return nodeFactory.createStringLiteral(node.text);
203214
}

0 commit comments

Comments
 (0)