Issues with independently using @ngtools/webpack to load loader.
grapehunter opened this issue · comments
Command
build
Description
Issues with @ngtools/webpack
I found some drawbacks when using additional loaders independently with @ngtools/webpack, detached from angular-cli. Please see the minimal example using ifdef-loader (https://stackblitz.com/~/github.com/grapehunter/ngtool_webpack_show):
webpack.config.json
module.exports = (env) => {
...
module: {
rules: [
{
test: /\.[cm]?[tj]sx?$/,
resolve: { fullySpecified: false },
exclude:
/[\\/]node_modules[/\\](?:core-js|@babel|tslib|web-animations-js|web-streams-polyfill|whatwg-url)[/\\]/,
use: [
{ loader: "ifdef-loader", options: { DEBUG: !isProduction } },
{ loader: path.resolve(__dirname, "./scripts/trace-loader.cjs") }, // log source code in pipeline
{
loader: "@ngtools/webpack",
},
],
},
],
},
...
}
index.ts
...
/// #if DEBUG
alert("Running in development mode!");
/// #else
alert("Running in production mode!");
/// #endif
@Component({
selector: "app-root",
standalone: true,
/// #if DEBUG
template: ` <h1>Hello from development!</h1> `,
/// #else
// @ts-ignore
template: ` <h1>Hello from production!</h1> `,
/// #endif
})
export class App {}
bootstrapApplication(App, {
providers: [provideExperimentalZonelessChangeDetection()],
});
- If the example code is executed first by @ngtools/webpack (AngularWebpackPlugin), then the code processed by @ngtools/webpack is printed out as follows through trace-loader.cjs:
/// #if DEBUG
alert("Running in development mode!");
/// #else
alert("Running in production mode!");
/// #endif
export class App {
}
App.ɵfac = function App_Factory(t) { return new (t || App)(); };
App.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: App, selectors: [["app-root"]], standalone: true, features: [i0.ɵɵStandaloneFeature], decls: 2, vars: 0, template: function App_Template(rf, ctx) { if (rf & 1) {
i0.ɵɵelementStart(0, "h1");
i0.ɵɵtext(1, "Hello from production!");
i0.ɵɵelementEnd();
} }, encapsulation: 2 });
bootstrapApplication(App, {
providers: [provideExperimentalZonelessChangeDetection()],
});
As you can see, after @ngtools/webpack compilation, the commented code within the @component scope disappears. Thus, when ifdef-loader receives the code, it cannot process the second /// #if DEBUG in main.ts, leading to incorrect page output (Hello from production!
instead of the correct Hello from development!
).
- If { loader: "ifdef-loader", options: { DEBUG: !isProduction } } is placed below @ngtools/webpack, i.e., ifdef-loader is executed first and then @ngtools/webpack. Since @ngtools/webpack internally loads source files from the path, not through the loader pipeline, the final compiled result will ignore any changes made by ifdef-loader to the code.
Here is an example of an incorrectly executed project: https://stackblitz.com/~/github.com/grapehunter/ngtool_webpack_show
Describe the solution you'd like
The modification method I am currently using
I currently preprocess the code by patching the AngularWebpackPlugin configuration parameters of @ngtools/webpack to add a sourceModifier.
The patch file is as follows:
diff --git a/node_modules/@ngtools/webpack/src/ivy/plugin.js b/node_modules/@ngtools/webpack/src/ivy/plugin.js
index 9c970d0..8c00f1f 100644
--- a/node_modules/@ngtools/webpack/src/ivy/plugin.js
+++ b/node_modules/@ngtools/webpack/src/ivy/plugin.js
@@ -139,7 +139,7 @@ class AngularWebpackPlugin {
// Webpack lacks an InputFileSytem type definition with sync functions
- compiler.inputFileSystem, (0, paths_1.normalizePath)(compiler.context));
+ compiler.inputFileSystem, (0, paths_1.normalizePath)(compiler.context), this.pluginOptions.sourceModifier ?? []);
const host = ts.createIncrementalCompilerHost(compilerOptions, system);
// Setup source file caching and reuse cache from previous compilation if present
let cache = this.sourceFileCache;
diff --git a/node_modules/@ngtools/webpack/src/ivy/system.js b/node_modules/@ngtools/webpack/src/ivy/system.js
index d86681b..4ba03b6 100644
--- a/node_modules/@ngtools/webpack/src/ivy/system.js
+++ b/node_modules/@ngtools/webpack/src/ivy/system.js
@@ -36,7 +36,7 @@ const paths_1 = require("./paths");
function shouldNotWrite() {
throw new Error('Webpack TypeScript System should not write.');
}
-function createWebpackSystem(input, currentDirectory) {
+function createWebpackSystem(input, currentDirectory, sourceModifiers) {
// Webpack's CachedInputFileSystem uses the default directory separator in the paths it uses
// for keys to its cache. If the keys do not match then the file watcher will not purge outdated
// files and cause stale data to be used in the next rebuild. TypeScript always uses a `/` (POSIX)
@@ -58,7 +58,14 @@ function createWebpackSystem(input, currentDirectory) {
if (data.length > 3 && data[0] === 0xef && data[1] === 0xbb && data[2] === 0xbf) {
start = 3;
}
- return data.toString('utf8', start);
+ // return data.toString('utf8', start);
+ let source = data.toString('utf8', start);
+ for (const sourceModifier of sourceModifiers) {
+ if (typeof sourceModifier.filter === 'function' && sourceModifier.filter(source, path)) {
+ source = sourceModifier.modifier(source, path);
+ }
+ }
+ return source;
},
Modifications to webpack.config.js are required for use.
module: {
rules: [
{
test: /\.[cm]?[tj]sx?$/,
resolve: { fullySpecified: false },
exclude:
/[\\/]node_modules[/\\](?:core-js|@babel|tslib|web-animations-js|web-streams-polyfill|whatwg-url)[/\\]/,
use: [
// { loader: "ifdef-loader", options: { DEBUG: !isProduction } }, // comment ifdef-loader here
{ loader: path.resolve(__dirname, "./scripts/trace-loader.cjs") }, // log source code in pipeline
{
loader: "@ngtools/webpack",
},
],
},
],
plugins: [
new AngularWebpackPlugin({
tsConfigPath: "tsconfig.json",
jitMode: false,
....
sourceModifier: [
{
filter: (source, path) => !/node_modules/.test(path),
modifier: (source, path) => {
const { parse } = require("ifdef-loader/preprocessor");
return parse(source, {
DEBUG: !isProduction,
});
},
},
],
}),
]
...
In this way, the code given to the TypeScript compiler internally by @ngtools/webpack has already been processed by ifdef-loader, so the final page output is correct.
Here is the modified project example: https://stackblitz.com/~/github.com/grapehunter/ngtool_webpack_show_fix
Describe alternatives you've considered
Questions I would like to ask
- The modification method I am currently using is quite troublesome. Each time I upgrade the @ngtools/webpack version, I have to re-edit the patch file. So I would like to know if there are other ways to achieve the same goal.
- If not, does this repo accept such modifications? If so, I can submit a pull request.