{"record":{"id":"f100f3d7d9a97356","repo":"angular/angular-cli","slug":"the-loader-filename-didn-t-return-a-string","errorCode":null,"errorMessage":"The loader \"${filename}\" didn't return a string.","messagePattern":"The loader \"(.+?)\" didn't return a string\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/ngtools/webpack/src/resource_loader.ts","lineNumber":341,"sourceCode":"      btoa(input) {\n        return Buffer.from(input).toString('base64');\n      },\n    };\n\n    try {\n      vm.runInNewContext(source, context, { filename });\n    } catch {\n      // Error are propagated through the child compilation.\n      return null;\n    }\n\n    if (typeof context.resource === 'string') {\n      return context.resource;\n    } else if (typeof context.resource?.default === 'string') {\n      return context.resource.default;\n    }\n\n    throw new Error(`The loader \"${filename}\" didn't return a string.`);\n  }\n\n  async get(filePath: string): Promise<string> {\n    const normalizedFile = normalizePath(filePath);\n    let compilationResult = this.fileCache?.get(normalizedFile);\n\n    if (compilationResult === undefined) {\n      // cache miss so compile resource\n      compilationResult = await this._compile(filePath);\n\n      // Only cache if compilation was successful\n      if (this.fileCache && compilationResult.success) {\n        this.fileCache.set(normalizedFile, compilationResult);\n      }\n    }\n\n    return compilationResult.content;\n  }","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/angular/angular-cli/blob/bb72145f9ab45aee29f523236b3a25cd0813a841/packages/ngtools/webpack/src/resource_loader.ts#L323-L359","documentation":"After running a webpack loader chain in the child compilation, the loader evaluates the result (`_evaluate`) and expects the module's export to be a string (or a `{ default: string }` ES-module wrapper). If the final loader output is neither, it throws — the loader chain did not produce the expected stringified resource.","triggerScenarios":"A custom/alternative loader for a template or style returns an object, Buffer, promise, or undefined instead of a string, e.g. a misconfigured raw-loader/html-loader emitting `{ html: ... }` or a loader doing `module.exports = {...}`. Raised from `output()` invoked via `get()`.","commonSituations":"Upgrading or swapping loaders (e.g. replacing raw-loader) where the new loader returns structured metadata; custom loaders forgetting `this.callback(null, content.toString())`; a loader returning a Buffer that isn't converted.","solutions":["Ensure the LAST loader in the chain returns a string (call `.toString()` on Buffers)","Check the matching rule for templates/styles in webpack config and fix/remove loaders emitting objects","If the loader exports an object with a string default, make sure it is exposed as `module.exports.default` or `module.exports = 'string'`","Pin/align loader versions after upgrades (e.g. html-loader major bumps) and re-test"],"exampleFix":"// before\nmodule.exports = { html: compiledHtml }; // loader returns an object\n// after\nmodule.exports = compiledHtml; // or module.exports.default = compiledHtml;","handlingStrategy":"type-guard","validationCode":"function loaderReturnsString(loaderChain) {\n  // ensure the final loader converts Buffers and exports plain strings\n  return loaderChain.every((l) => !l.emitsObjects);\n}\n// in the custom loader: this.callback(null, content.toString());","typeGuard":"function isStringResource(v) {\n  return typeof v === 'string' || (v != null && typeof v.default === 'string');\n}","tryCatchPattern":"try {\n  const content = await loader.get(filePath);\n  if (!isStringResource(content)) throw new TypeError('loader produced non-string resource');\n} catch (e) {\n  if (e.message.includes(\"didn't return a string\")) {\n    console.error('Check the last loader in the chain for', filePath);\n  }\n  throw e;\n}","preventionTips":["Guarantee the final loader in each template/style rule returns a string","Call .toString() on Buffers before returning loader output","After swapping/upgrading loaders, run a smoke build covering templates and styles"],"tags":["webpack","loader","angular","output"],"backgroundTag":"loader-output-type-mismatch","analyzedSha":"bb72145f9ab45aee29f523236b3a25cd0813a841","analyzedAt":"2026-08-30T02:47:34.745Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}