@angular-devkit/build-angular
Version:
Angular Webpack Build Facade
196 lines (195 loc) • 8.68 kB
JavaScript
/**
* @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.dev/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScriptsWebpackPlugin = void 0;
const loader_utils_1 = require("loader-utils");
const path = __importStar(require("node:path"));
const webpack_1 = require("webpack");
const error_1 = require("../../../utils/error");
const webpack_diagnostics_1 = require("../../../utils/webpack-diagnostics");
const Entrypoint = require('webpack/lib/Entrypoint');
/**
* The name of the plugin provided to Webpack when tapping Webpack compiler hooks.
*/
const PLUGIN_NAME = 'scripts-webpack-plugin';
function addDependencies(compilation, scripts) {
for (const script of scripts) {
compilation.fileDependencies.add(script);
}
}
class ScriptsWebpackPlugin {
options;
_lastBuildTime;
_cachedOutput;
constructor(options) {
this.options = options;
}
async shouldSkip(compilation, scripts) {
if (this._lastBuildTime == undefined) {
this._lastBuildTime = Date.now();
return false;
}
for (const script of scripts) {
const scriptTime = await new Promise((resolve, reject) => {
compilation.fileSystemInfo.getFileTimestamp(script, (error, entry) => {
if (error) {
reject(error);
return;
}
resolve(entry && typeof entry !== 'string' ? entry.safeTime : undefined);
});
});
if (!scriptTime || scriptTime > this._lastBuildTime) {
this._lastBuildTime = Date.now();
return false;
}
}
return true;
}
_insertOutput(compilation, { filename, source }, cached = false) {
const chunk = new webpack_1.Chunk(this.options.name);
chunk.rendered = !cached;
chunk.id = this.options.name;
chunk.ids = [chunk.id];
chunk.files.add(filename);
const entrypoint = new Entrypoint(this.options.name);
entrypoint.pushChunk(chunk);
chunk.addGroup(entrypoint);
compilation.entrypoints.set(this.options.name, entrypoint);
compilation.chunks.add(chunk);
compilation.assets[filename] = source;
compilation.hooks.chunkAsset.call(chunk, filename);
}
apply(compiler) {
if (this.options.scripts.length === 0) {
return;
}
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
// Use the resolver from the compilation instead of compiler.
// Using the latter will causes a lot of `DescriptionFileUtils.loadDescriptionFile` calls.
// See: https://github.com/angular/angular-cli/issues/24634#issuecomment-1425782668
const resolver = compilation.resolverFactory.get('normal', {
preferRelative: true,
useSyncFileSystemCalls: true,
// Caching must be disabled because it causes the resolver to become async after a rebuild.
cache: false,
});
const scripts = [];
for (const script of this.options.scripts) {
try {
const resolvedPath = resolver.resolveSync({}, this.options.basePath, script);
if (resolvedPath) {
scripts.push(resolvedPath);
}
else {
(0, webpack_diagnostics_1.addError)(compilation, `Cannot resolve '${script}'.`);
}
}
catch (error) {
(0, error_1.assertIsError)(error);
(0, webpack_diagnostics_1.addError)(compilation, error.message);
}
}
compilation.hooks.additionalAssets.tapPromise(PLUGIN_NAME, async () => {
if (await this.shouldSkip(compilation, scripts)) {
if (this._cachedOutput) {
this._insertOutput(compilation, this._cachedOutput, true);
}
addDependencies(compilation, scripts);
return;
}
const sourceGetters = scripts.map((fullPath) => {
return new Promise((resolve, reject) => {
compilation.inputFileSystem.readFile(fullPath, (err, data) => {
if (err) {
reject(err);
return;
}
const content = data?.toString() ?? '';
let source;
if (this.options.sourceMap) {
// TODO: Look for source map file (for '.min' scripts, etc.)
let adjustedPath = fullPath;
if (this.options.basePath) {
adjustedPath = path.relative(this.options.basePath, fullPath);
}
source = new webpack_1.sources.OriginalSource(content, adjustedPath);
}
else {
source = new webpack_1.sources.RawSource(content);
}
resolve(source);
});
});
});
const sources = await Promise.all(sourceGetters);
const concatSource = new webpack_1.sources.ConcatSource();
sources.forEach((source) => {
concatSource.add(source);
concatSource.add('\n;');
});
const combinedSource = new webpack_1.sources.CachedSource(concatSource);
const output = { filename: this.options.filename, source: combinedSource };
this._insertOutput(compilation, output);
this._cachedOutput = output;
addDependencies(compilation, scripts);
});
compilation.hooks.processAssets.tapPromise({
name: PLUGIN_NAME,
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING,
}, async () => {
const assetName = this.options.filename;
const asset = compilation.getAsset(assetName);
if (asset) {
const interpolatedFilename = (0, loader_utils_1.interpolateName)(
// TODO: Revisit. Previously due to lack of type safety, this object
// was fine, but in practice it doesn't match the type of the loader context.
{ resourcePath: 'scripts.js' }, assetName, { content: asset.source.source() });
if (assetName !== interpolatedFilename) {
compilation.renameAsset(assetName, interpolatedFilename);
}
}
});
});
}
}
exports.ScriptsWebpackPlugin = ScriptsWebpackPlugin;
;