@angular/build
Version:
Official build system for Angular
335 lines • 16 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;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createVitestConfigPlugin = createVitestConfigPlugin;
exports.createVitestPlugins = createVitestPlugins;
const node_assert_1 = __importDefault(require("node:assert"));
const promises_1 = require("node:fs/promises");
const node_module_1 = require("node:module");
const node_os_1 = require("node:os");
const node_path_1 = __importDefault(require("node:path"));
const assets_middleware_1 = require("../../../../tools/vite/middlewares/assets-middleware");
const path_1 = require("../../../../utils/path");
async function findTestEnvironment(projectResolver) {
try {
projectResolver('happy-dom');
return 'happy-dom';
}
catch {
// happy-dom is not installed, fallback to jsdom
return 'jsdom';
}
}
async function createVitestConfigPlugin(options) {
const { include, browser, projectName, reporters, setupFiles, projectPlugins, projectSourceRoot, } = options;
const { mergeConfig } = await Promise.resolve().then(() => __importStar(require('vitest/config')));
return {
name: 'angular:vitest-configuration',
async config(config) {
const testConfig = config.test;
if (testConfig?.projects?.length) {
this.warn('The "test.projects" option in the Vitest configuration file is not supported. ' +
'The Angular CLI Test system will construct its own project configuration.');
delete testConfig.projects;
}
if (testConfig?.include) {
this.warn('The "test.include" option in the Vitest configuration file is not supported. ' +
'The Angular CLI Test system will manage test file discovery.');
delete testConfig.include;
}
// Merge user-defined plugins from the Vitest config with the CLI's internal plugins.
if (config.plugins) {
const userPlugins = config.plugins.filter((plugin) =>
// Only inspect objects with a `name` property as these would be the internal injected plugins
!plugin ||
typeof plugin !== 'object' ||
!('name' in plugin) ||
(!plugin.name.startsWith('angular:') && !plugin.name.startsWith('vitest')));
if (userPlugins.length > 0) {
projectPlugins.push(...userPlugins);
}
delete config.plugins;
}
// Add browser source map support
if (browser || testConfig?.browser?.enabled) {
projectPlugins.unshift(createSourcemapSupportPlugin());
setupFiles.unshift('virtual:source-map-support');
}
const projectResolver = (0, node_module_1.createRequire)(projectSourceRoot + '/').resolve;
const projectDefaults = {
test: {
setupFiles,
globals: true,
// Default to `false` to align with the Karma/Jasmine experience.
isolate: false,
sequence: { setupFiles: 'list' },
},
optimizeDeps: {
noDiscovery: true,
include: options.optimizeDepsInclude,
},
resolve: {
mainFields: ['es2020', 'module', 'main'],
conditions: ['es2015', 'es2020', 'module', ...(browser ? ['browser'] : [])],
},
};
const { optimizeDeps, resolve } = config;
const projectOverrides = {
test: {
name: projectName,
include,
// CLI provider browser options override, if present
...(browser ? { browser } : {}),
// If the user has not specified an environment, use a smart default.
...(!testConfig?.environment
? { environment: await findTestEnvironment(projectResolver) }
: {}),
},
plugins: projectPlugins,
optimizeDeps,
resolve,
};
const projectBase = mergeConfig(projectDefaults, testConfig ? { test: testConfig } : {});
const projectConfig = mergeConfig(projectBase, projectOverrides);
return {
test: {
coverage: await generateCoverageOption(options.coverage, testConfig?.coverage, projectName),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...(reporters ? { reporters } : {}),
projects: [projectConfig],
},
};
},
};
}
async function loadResultFile(file) {
if (file.origin === 'memory') {
return new TextDecoder('utf-8').decode(file.contents);
}
return (0, promises_1.readFile)(file.inputPath, 'utf-8');
}
function createVitestPlugins(pluginOptions) {
const { workspaceRoot, buildResultFiles, testFileToEntryPoint } = pluginOptions;
const isWindows = (0, node_os_1.platform)() === 'win32';
return [
{
name: 'angular:test-in-memory-provider',
enforce: 'pre',
resolveId: (id, importer) => {
// Fast path for test entry points.
if (testFileToEntryPoint.has(id)) {
return id;
}
// Workaround for Vitest in Windows when a fully qualified absolute path is provided with
// a superfluous leading slash. This can currently occur with the `@vitest/coverage-v8` provider
// when it uses `removeStartsWith(url, FILE_PROTOCOL)` to convert a file URL resulting in
// `/D:/tmp_dir/...` instead of `D:/tmp_dir/...`.
if (id[0] === '/' && isWindows) {
const slicedId = id.slice(1);
if (node_path_1.default.isAbsolute(slicedId)) {
return slicedId;
}
}
if (importer && (id[0] === '.' || id[0] === '/')) {
let fullPath;
if (testFileToEntryPoint.has(importer)) {
fullPath = (0, path_1.toPosixPath)(node_path_1.default.join(workspaceRoot, id));
}
else {
fullPath = (0, path_1.toPosixPath)(node_path_1.default.join(node_path_1.default.dirname(importer), id));
}
const relativePath = node_path_1.default.relative(workspaceRoot, fullPath);
if (buildResultFiles.has((0, path_1.toPosixPath)(relativePath))) {
return fullPath;
}
}
// Determine the base directory for resolution.
let baseDir;
if (importer) {
// If the importer is a test entry point, resolve relative to the workspace root.
// Otherwise, resolve relative to the importer's directory.
baseDir = testFileToEntryPoint.has(importer) ? workspaceRoot : node_path_1.default.dirname(importer);
}
else {
// If there's no importer, assume the id is relative to the workspace root.
baseDir = workspaceRoot;
}
// Construct the full, absolute path and normalize it to POSIX format.
const fullPath = (0, path_1.toPosixPath)(node_path_1.default.join(baseDir, id));
if (testFileToEntryPoint.has(fullPath)) {
return fullPath;
}
// Check if the resolved path corresponds to a known build artifact.
const relativePath = node_path_1.default.relative(workspaceRoot, fullPath);
if (buildResultFiles.has((0, path_1.toPosixPath)(relativePath))) {
return fullPath;
}
// If the module cannot be resolved from the build artifacts, let other plugins handle it.
return undefined;
},
load: async (id) => {
(0, node_assert_1.default)(buildResultFiles.size > 0, 'buildResult must be available for in-memory loading.');
// Attempt to load as a source test file.
const entryPoint = testFileToEntryPoint.get(id);
let outputPath;
if (entryPoint) {
outputPath = entryPoint + '.js';
// To support coverage exclusion of the actual test file, the virtual
// test entry point only references the built and bundled intermediate file.
return {
code: `import "./${outputPath}";`,
};
}
else {
// Attempt to load as a built artifact.
const relativePath = node_path_1.default.relative(workspaceRoot, id);
outputPath = (0, path_1.toPosixPath)(relativePath);
}
const outputFile = buildResultFiles.get(outputPath);
if (outputFile) {
const code = await loadResultFile(outputFile);
const sourceMapPath = outputPath + '.map';
const sourceMapFile = buildResultFiles.get(sourceMapPath);
const sourceMapText = sourceMapFile ? await loadResultFile(sourceMapFile) : undefined;
// Vitest will include files in the coverage report if the sourcemap contains no sources.
// For builder-internal generated code chunks, which are typically helper functions,
// a virtual source is added to the sourcemap to prevent them from being incorrectly
// included in the final coverage report.
const map = sourceMapText ? JSON.parse(sourceMapText) : undefined;
if (map) {
if (!map.sources?.length && !map.sourcesContent?.length && !map.mappings) {
map.sources = ['virtual:builder'];
}
}
return {
code,
map,
};
}
},
configureServer: (server) => {
server.middlewares.use((0, assets_middleware_1.createBuildAssetsMiddleware)(server.config.base, buildResultFiles));
},
},
{
name: 'angular:html-index',
transformIndexHtml: () => {
// Add all global stylesheets
if (buildResultFiles.has('styles.css')) {
return [
{
tag: 'link',
attrs: { href: 'styles.css', rel: 'stylesheet' },
injectTo: 'head',
},
];
}
return [];
},
},
];
}
function createSourcemapSupportPlugin() {
return {
name: 'angular:source-map-support',
enforce: 'pre',
resolveId(source) {
if (source.includes('virtual:source-map-support')) {
return '\0source-map-support';
}
},
async load(id) {
if (id !== '\0source-map-support') {
return;
}
const packageResolve = (0, node_module_1.createRequire)(__filename).resolve;
const supportPath = packageResolve('source-map-support/browser-source-map-support.js');
const content = await (0, promises_1.readFile)(supportPath, 'utf-8');
// The `source-map-support` library currently relies on `this` being defined in the global scope.
// However, when running in an ESM environment, `this` is undefined.
// To workaround this, we patch the library to use `globalThis` instead of `this`.
return (content.replaceAll(/this\.(define|sourceMapSupport|base64js)/g, 'globalThis.$1') +
'\n;globalThis.sourceMapSupport.install();');
},
};
}
async function generateCoverageOption(optionsCoverage, configCoverage, projectName) {
let defaultExcludes = [];
if (optionsCoverage.exclude) {
try {
const vitestConfig = await Promise.resolve().then(() => __importStar(require('vitest/config')));
defaultExcludes = vitestConfig.coverageConfigDefaults.exclude;
}
catch { }
}
return {
excludeAfterRemap: true,
reportsDirectory: configCoverage?.reportsDirectory ?? (0, path_1.toPosixPath)(node_path_1.default.join('coverage', projectName)),
...(optionsCoverage.enabled !== undefined ? { enabled: optionsCoverage.enabled } : {}),
// Vitest performs a pre-check and a post-check for sourcemaps.
// The pre-check uses the bundled files, so specific bundled entry points and chunks need to be included.
// The post-check uses the original source files, so the user's include is used.
...(optionsCoverage.include
? { include: ['spec-*.js', 'chunk-*.js', ...optionsCoverage.include] }
: {}),
thresholds: optionsCoverage.thresholds,
watermarks: optionsCoverage.watermarks,
// Special handling for `exclude`/`reporters` due to an undefined value causing upstream failures
...(optionsCoverage.exclude
? {
exclude: [
// Augment the default exclude https://vitest.dev/config/#coverage-exclude
// with the user defined exclusions
...optionsCoverage.exclude,
...defaultExcludes,
],
}
: {}),
...(optionsCoverage.reporters
? { reporter: optionsCoverage.reporters }
: {}),
};
}
//# sourceMappingURL=plugins.js.map