From ce46ecae011595c86fea265e121ea313bb3cb030 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Mon, 3 Apr 2023 11:40:26 -0400 Subject: [PATCH] feat(@angular-devkit/build-angular): support module resolution with less stylesheets in esbuild builder When using the esbuild-based browser application builder with Less stylesheets, import rules will now attempt to perform node package resolution if the import cannot be found as a relative path. Built-in Less resolution is performed first to both avoid unnecessary node module resolution overhead when not needed and also ensure Less relative import semantics continue to be supported. --- .../builders/browser-esbuild/less-plugin.ts | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/less-plugin.ts b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/less-plugin.ts index 4071bb07df5c..6b37f69bea65 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser-esbuild/less-plugin.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser-esbuild/less-plugin.ts @@ -47,14 +47,14 @@ export function createLessPlugin(options: LessPluginOptions): Plugin { const [, , filePath] = args.path.split(';', 3); - return compileString(data, filePath, options); + return compileString(data, filePath, options, build.resolve.bind(build)); }); // Add a load callback to support files from disk build.onLoad({ filter: /\.less$/ }, async (args) => { const data = await readFile(args.path, 'utf-8'); - return compileString(data, args.path, options); + return compileString(data, args.path, options, build.resolve.bind(build)); }); }, }; @@ -64,13 +64,59 @@ async function compileString( data: string, filename: string, options: LessPluginOptions, + resolver: PluginBuild['resolve'], ): Promise { const less = (lessPreprocessor ??= (await import('less')).default); + const resolverPlugin: Less.Plugin = { + install({ FileManager }, pluginManager): void { + const resolverFileManager = new (class extends FileManager { + override supportsSync(): boolean { + return false; + } + + override supports(): boolean { + return true; + } + + override async loadFile( + filename: string, + currentDirectory: string, + options: Less.LoadFileOptions, + environment: Less.Environment, + ): Promise { + // Attempt direct loading as a relative path to avoid resolution overhead + const directResult = this.loadFileSync(filename, currentDirectory, options, environment); + if ('contents' in directResult) { + return directResult; + } + + // Attempt a full resolution if not found + const fullResult = await resolver(filename, { + kind: 'import-rule', + resolveDir: currentDirectory, + }); + if (fullResult.path) { + return { + filename: fullResult.path, + contents: await readFile(fullResult.path, 'utf-8'), + }; + } + + // Otherwise error by throwing the failing direct result + throw directResult.error; + } + })(); + + pluginManager.addFileManager(resolverFileManager); + }, + }; + try { const result = await less.render(data, { filename, paths: options.includePaths, + plugins: [resolverPlugin], rewriteUrls: 'all', sourceMap: options.sourcemap ? {