Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { Schema as BrowserBuilderOptions } from '../browser/schema';
const UNSUPPORTED_OPTIONS: Array<keyof BrowserBuilderOptions> = [
'budgets',
'progress',
'scripts',

// * i18n support
'localize',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* @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.io/license
*/

import type { BuildOptions } from 'esbuild';
import MagicString, { Bundle } from 'magic-string';
import assert from 'node:assert';
import { readFile } from 'node:fs/promises';
import { NormalizedBrowserOptions } from './options';

/**
* Create an esbuild 'build' options object for all global scripts defined in the user provied
* build options.
* @param options The builder's user-provider normalized options.
* @returns An esbuild BuildOptions object.
*/
export function createGlobalScriptsBundleOptions(options: NormalizedBrowserOptions): BuildOptions {
const {
globalScripts,
optimizationOptions,
outputNames,
preserveSymlinks,
sourcemapOptions,
workspaceRoot,
} = options;

const namespace = 'angular:script/global';
const entryPoints: Record<string, string> = {};
for (const { name } of globalScripts) {
entryPoints[name] = `${namespace}:${name}`;
}

return {
absWorkingDir: workspaceRoot,
bundle: false,
splitting: false,
entryPoints,
entryNames: outputNames.bundles,
assetNames: outputNames.media,
mainFields: ['script', 'browser', 'main'],
conditions: ['script'],
resolveExtensions: ['.mjs', '.js'],
logLevel: options.verbose ? 'debug' : 'silent',
metafile: true,
minify: optimizationOptions.scripts,
outdir: workspaceRoot,
sourcemap: sourcemapOptions.scripts && (sourcemapOptions.hidden ? 'external' : true),
write: false,
platform: 'neutral',
preserveSymlinks,
plugins: [
{
name: 'angular-global-scripts',
setup(build) {
build.onResolve({ filter: /^angular:script\/global:/ }, (args) => {
if (args.kind !== 'entry-point') {
return null;
}

return {
// Add the `js` extension here so that esbuild generates an output file with the extension
path: args.path.slice(namespace.length + 1) + '.js',
namespace,
};
});
// All references within a global script should be considered external. This maintains the runtime
// behavior of the script as if it were added directly to a script element for referenced imports.
build.onResolve({ filter: /./, namespace }, ({ path }) => {
return {
path,
external: true,
};
});
build.onLoad({ filter: /./, namespace }, async (args) => {
const files = globalScripts.find(({ name }) => name === args.path.slice(0, -3))?.files;
assert(files, `Invalid operation: global scripts name not found [${args.path}]`);

// Global scripts are concatenated using magic-string instead of bundled via esbuild.
const bundleContent = new Bundle();
for (const filename of files) {
const resolveResult = await build.resolve(filename, {
kind: 'entry-point',
resolveDir: workspaceRoot,
});

if (resolveResult.errors.length) {
// Remove resolution failure notes about marking as external since it doesn't apply
// to global scripts.
resolveResult.errors.forEach((error) => (error.notes = []));

return {
errors: resolveResult.errors,
warnings: resolveResult.warnings,
};
}

const fileContent = await readFile(resolveResult.path, 'utf-8');
bundleContent.addSource(new MagicString(fileContent, { filename }));
}

return {
contents: bundleContent.toString(),
loader: 'js',
};
});
},
},
],
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { checkCommonJSModules } from './commonjs-checker';
import { SourceFileCache, createCompilerPlugin } from './compiler-plugin';
import { BundlerContext, logMessages } from './esbuild';
import { logExperimentalWarnings } from './experimental-warnings';
import { createGlobalScriptsBundleOptions } from './global-scripts';
import { extractLicenses } from './license-extractor';
import { NormalizedBrowserOptions, normalizeOptions } from './options';
import { shutdownSassWorkerPool } from './sass-plugin';
Expand Down Expand Up @@ -124,17 +125,28 @@ async function execute(
createGlobalStylesBundleOptions(options, target, browsers),
);

const [codeResults, styleResults] = await Promise.all([
const globalScriptsBundleContext = new BundlerContext(
workspaceRoot,
!!options.watch,
createGlobalScriptsBundleOptions(options),
);

const [codeResults, styleResults, scriptResults] = await Promise.all([
// Execute esbuild to bundle the application code
codeBundleContext.bundle(),
// Execute esbuild to bundle the global stylesheets
globalStylesBundleContext.bundle(),
globalScriptsBundleContext.bundle(),
]);

// Log all warnings and errors generated during bundling
await logMessages(context, {
errors: [...(codeResults.errors || []), ...(styleResults.errors || [])],
warnings: [...codeResults.warnings, ...styleResults.warnings],
errors: [
...(codeResults.errors || []),
...(styleResults.errors || []),
...(scriptResults.errors || []),
],
warnings: [...codeResults.warnings, ...styleResults.warnings, ...scriptResults.warnings],
});

const executionResult = new ExecutionResult(
Expand All @@ -144,7 +156,7 @@ async function execute(
);

// Return if the bundling has errors
if (codeResults.errors || styleResults.errors) {
if (codeResults.errors || styleResults.errors || scriptResults.errors) {
return executionResult;
}

Expand All @@ -154,13 +166,29 @@ async function execute(
);

// Combine the bundling output files
const initialFiles: FileInfo[] = [...codeResults.initialFiles, ...styleResults.initialFiles];
executionResult.outputFiles.push(...codeResults.outputFiles, ...styleResults.outputFiles);
const initialFiles: FileInfo[] = [
...codeResults.initialFiles,
...styleResults.initialFiles,
...scriptResults.initialFiles,
];
executionResult.outputFiles.push(
...codeResults.outputFiles,
...styleResults.outputFiles,
...scriptResults.outputFiles,
);

// Combine metafiles used for the stats option as well as bundle budgets and console output
const metafile = {
inputs: { ...codeResults.metafile?.inputs, ...styleResults.metafile?.inputs },
outputs: { ...codeResults.metafile?.outputs, ...styleResults.metafile?.outputs },
inputs: {
...codeResults.metafile?.inputs,
...styleResults.metafile?.inputs,
...scriptResults.metafile?.inputs,
},
outputs: {
...codeResults.metafile?.outputs,
...styleResults.metafile?.outputs,
...scriptResults.metafile?.outputs,
},
};

// Check metafile for CommonJS module usage if optimizing scripts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { normalizeAssetPatterns, normalizeOptimization, normalizeSourceMaps } fr
import { normalizeCacheOptions } from '../../utils/normalize-cache';
import { generateEntryPoints } from '../../utils/package-chunk-sort';
import { getIndexInputFile, getIndexOutputFile } from '../../utils/webpack-browser-config';
import { normalizeGlobalStyles } from '../../webpack/utils/helpers';
import { globalScriptsByBundleName, normalizeGlobalStyles } from '../../webpack/utils/helpers';
import { Schema as BrowserBuilderOptions, OutputHashing } from './schema';

export type NormalizedBrowserOptions = Awaited<ReturnType<typeof normalizeOptions>>;
Expand Down Expand Up @@ -88,6 +88,13 @@ export async function normalizeOptions(
}
}

const globalScripts: { name: string; files: string[]; initial: boolean }[] = [];
if (options.scripts?.length) {
for (const { bundleName, paths, inject } of globalScriptsByBundleName(options.scripts)) {
globalScripts.push({ name: bundleName, files: paths, initial: inject });
}
}

let tailwindConfiguration: { file: string; package: string } | undefined;
const tailwindConfigurationPath = findTailwindConfigurationFile(workspaceRoot, projectRoot);
if (tailwindConfigurationPath) {
Expand Down Expand Up @@ -186,6 +193,7 @@ export async function normalizeOptions(
outputNames,
fileReplacements,
globalStyles,
globalScripts,
serviceWorkerOptions,
indexHtmlOptions,
tailwindConfiguration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,7 @@
},
{
"type": "string",
"description": "The file to include.",
"pattern": "\\.[cm]?jsx?$"
"description": "The JavaScript/TypeScript file or package containing the file to include."
}
]
}
Expand Down
Loading