Open KhoaSydney opened 7 years ago
I have not tested it with ngtools/webpack
but I guess it should work, you only need to chain it properly, e.g.:
rules: [
{
test: /\.ts$/,
loader: ['@ngtools/webpack', 'ifdef-loader?${q}']
}
]
hi @nippur72 ,
It looks like it is working with ngtools/webpack. I run across a strange issue with lazy loaded module. ngtools/webpack compile typescripts with AoT (Angular). For AoT to work, codes should be statically analyzable. Will the code below able to pick up by ngtool/webpack?
@NgModule({
imports: [
CommonModule,
BrowserModule,
BrowserAnimationsModule,
FormsModule,
HttpModule,
ControlsModule,
HomeModule,
AdvancedAdminModule,
ClientModule,
SettingsModule,
MidwinterModule,
ReportingModule,
ModellingModule,
PracticeManagementModule,
UserManagementModule,
PortfolioBookNg2Module,
/// #if PRODUCTION
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }),
/// #else
RouterModule.forRoot(routes),
/// #endif
UpgradeModule
],
How does your loader work in relation to ngtools/webpack? does it feed the codes (let say this is production) into ngtools/webpack like below?
@NgModule({
imports: [
CommonModule,
BrowserModule,
BrowserAnimationsModule,
FormsModule,
HttpModule,
ControlsModule,
HomeModule,
AdvancedAdminModule,
ClientModule,
SettingsModule,
MidwinterModule,
ReportingModule,
ModellingModule,
PracticeManagementModule,
UserManagementModule,
PortfolioBookNg2Module,
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }),
UpgradeModule
]```
yes it will feed the code with
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }),
when the flag PRODUCTION
is on.
Going a bit off-topic, if your scenario is a simple PRODUCTION/DEVELOPMENT environment you might not need ifdef-loader
altogether. Just use simple if
expressions and when building for PRODUCTION let uglify-js
(in webpack) strip out the debug code for you. It's much cleaner and fits better with TypeScript syntax (if you use it).
Example:
serManagementModule,
PortfolioBookNg2Module,
PRODUCTION ? RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }) : RouterModule.forRoot(routes),
UpgradeModule
and then use this plugin in webpack:
const uglify = new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
global_defs: {
PRODUCTION: true /* or false */,
}
}
});
hI @nippur72 ,
It will not work for AoT build though. I get the following error: ERROR in Error encountered resolving symbol values statically. Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler (position 46:20 in the original .ts file), resolving symbol PRODUCTION.
I am not sure, but if you are using TypeScript maybe you need to declare the PRODUCTION
and DEVELOPMENT
variables that are externally injected by uglify-js:
// somewhere in a .ts
declare global {
export var PRODUCTION;
export var DEVELOPMENT;
}
Also, you need to manually set the above two variables when not using uglify-js (that is, during DEVELOPMENT):
if(PRODUCTION === undefined) {
window["PRODUCTION"] = false;
window["DEVELOPMENT"] = true;
}
// note that the above code will erased in production
This was working for me for @ngtools/webpack
, however after the last update of 1.8.0+ e.g. AngularCompilerPlugin
instead of AoTPlugin
it stopped working.
Tried to update this lib to 2.x cause I was still on 1.x and its not working either.
This is how im doing it now
const ifDefOpts = querystring.encode({
DEV: isDevBuild,
});
test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/, use: isDevBuild
? ["awesome-typescript-loader?silent=true", "angular2-template-loader", `ifdef-loader?${ifDefOpts}`]
: ["@ngtools/webpack", `ifdef-loader?${ifDefOpts}`]
@stephenlautier you can use the echo-loader to debug exactly what is happening in the chain of your loaders.
For instance you can put it after ifdef-loader
and see if it is actually eliminating code:
test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/, use: isDevBuild
? ["awesome-typescript-loader?silent=true", "angular2-template-loader", "echo-loader?msg=dump", `ifdef-loader?${ifDefOpts}`]
: ["@ngtools/webpack", "echo-loader?msg=dump", `ifdef-loader?${ifDefOpts}`]
(The dump
option will cause the file to be dumped on the console)
@nippur72 thanks for the info! im not a webpack guru so it definitely helps 👍
i will try and give that a shot
hmm.. its very strange, i tested out with @nippur72 and the code is not there, so not sure how its giving an error 😕
import "reflect-metadata";
import "zone.js";
import "hammerjs";
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
// todo: get this back working
/// #if DEV
// import "./app/app-light.theme.scss";
import "./app/app-dark.theme.scss";
/// #endif
import { AppModule } from "./app/app.module.browser";
dump: boot.browser.ts
*************
import "reflect-metadata";
import "zone.js";
import "hammerjs";
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
// todo: get this back working
///////////
///////////////////////////////////////
///////////////////////////////////
//////////
import { AppModule } from "./app/app.module.browser";
so it is actually getting removed, not sure then why/how its blowing and from where its resolving it, this is the error
ERROR in ./ClientApp/app/app-dark.theme.scss
Module parse failed: Unexpected character '@' (1:0)
You may need an appropriate loader to handle this file type.
| @import "themes/dark.theme";
| @import "settings";
@ ./ClientApp/boot.browser.ts 8:0-35
and if i comment out manually the scss import, it works
For some reason webpack is picking the unprocessed .ts
file, perhaps with another loader. Can you share your webpack.config.js
?
Sure, this is all of it, and it happens when run prod mode, so isDevBuild
if false
const path = require("path");
const fs = require("fs");
const querystring = require("querystring");
const chalk = require("chalk");
const webpack = require("webpack");
const merge = require("webpack-merge");
const AngularCompilerPlugin = require("@ngtools/webpack").AngularCompilerPlugin;
const CheckerPlugin = require("awesome-typescript-loader").CheckerPlugin;
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = (env) => {
const isDevBuild = !(env && env.prod);
const srcRoot = "ClientApp";
console.log(chalk`{cyan.bold Build environment isDev: ${isDevBuild}}`);
const ifDefOpts = querystring.encode({
DEV: isDevBuild,
});
const extractSass = new ExtractTextPlugin({
filename: "[name].css",
disable: isDevBuild
});
const sassConfig = extractSass.extract({
use: [{
loader: "css-loader", // translates CSS into CommonJS
options: {
minimize: !isDevBuild
}
}, {
loader: "sass-loader", // compiles Sass to CSS
options: {
includePaths: [
`./${srcRoot}/assets/styles`,
`./${srcRoot}/libraries`
]
}
}],
// use style-loader in development
fallback: "style-loader" // creates style nodes from JS strings
});
// Configuration in common to both client-side and server-side bundles
const sharedConfig = {
stats: { modules: false },
context: __dirname,
resolve: { extensions: [".js", ".ts"] },
output: {
filename: "[name].js",
publicPath: "dist/" // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
rules: [
{
test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/, use: isDevBuild
? ["awesome-typescript-loader?silent=true", "angular2-template-loader", `ifdef-loader?${ifDefOpts}`]
: ["@ngtools/webpack", `ifdef-loader?${ifDefOpts}`]
},
{ test: /\.html$/, use: "html-loader?minimize=false" },
{ test: /\.css$/, use: ["to-string-loader", isDevBuild ? "css-loader" : "css-loader?minimize"] },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: "url-loader?limit=1000000" }
].concat(isDevBuild ? [
// Plugins that apply in development builds only
{ test: /\.scss$/, use: sassConfig },
{ test: /\.js$/, use: ["source-map-loader"], enforce: "pre" }
] : [])
},
plugins: [
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV || isDevBuild ? "development" : "production")
}),
new CheckerPlugin(),
extractSass
]
};
// Configuration for client-side bundle suitable for running in browsers
const clientBundleOutputDir = "./wwwroot/dist";
const clientBundleConfig = merge(sharedConfig, {
entry: { "main-client": `./${srcRoot}/boot.browser.ts` },
output: { path: path.join(__dirname, clientBundleOutputDir) },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require("./wwwroot/dist/vendor-manifest.json")
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: "[file].map", // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(clientBundleOutputDir, "[resourcePath]") // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin(),
new AngularCompilerPlugin({
tsConfigPath: "./tsconfig.browser.json",
entryModule: path.join(__dirname, "ClientApp/app/app.module.browser#AppModule"),
compilerOptions: {
noUnusedParameters: false,
},
})
])
});
let sassBundleConfig;
if (!isDevBuild) {
const themes = getThemes(`./${srcRoot}/app`);
sassBundleConfig = {
stats: { modules: false },
context: __dirname,
output: {
filename: "[name].css",
path: path.join(__dirname, clientBundleOutputDir),
publicPath: "/dist/" // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
entry: themes,
module: {
rules: [
{ test: /\.scss$/, use: sassConfig }
]
},
plugins: [extractSass]
};
}
// Configuration for server-side (prerendering) bundle suitable for running in Node
let serverBundleConfig = merge(sharedConfig, {
entry: { "main-server": `./${srcRoot}/boot.server.ts` },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require(`./${srcRoot}/dist/vendor-manifest.json`),
sourceType: "commonjs2",
name: "./vendor"
})
].concat(isDevBuild ? [] : [
// Plugins that apply in production builds only
new AngularCompilerPlugin({
tsConfigPath: "./tsconfig.server.json",
entryModule: path.join(__dirname, "ClientApp/app/app.module.server#AppModule"),
compilerOptions: {
noUnusedParameters: false,
}
})
]),
output: {
libraryTarget: "commonjs",
path: path.join(__dirname, `./${srcRoot}/dist`)
},
target: "node",
devtool: "inline-source-map"
});
if (isDevBuild) {
// todo: try to change back to "main" only, as it reduces file size drastically - with it @odin pkgs are being loaded twice duplicated (umd/esm).
serverBundleConfig = merge(serverBundleConfig, {
resolve: { mainFields: ["main"] },
});
}
return [
clientBundleConfig,
serverBundleConfig,
sassBundleConfig,
].filter(x => x);
};
// todo: move to build-tools
function getThemes(themePath) {
let themes = {};
fs.readdirSync(themePath).forEach(fileName => {
const fileNameWithPath = path.resolve(themePath, fileName);
const stat = fs.lstatSync(fileNameWithPath);
if (stat.isDirectory()) return;
if (!/\.theme\.scss$/.test(fileName)) return;
const nameWithoutExt = path.basename(fileName, ".scss");
themes[nameWithoutExt] = fileNameWithPath;
});
return themes;
};
sorry I was not able to locate the issue, I tried to replicate your env but got lost along the way 😞
If the problem is in any of the loaders you can debug which one is actually being triggered, again with the echo-loader
, e.g:
{ test: /\.html$/, use: ["html-loader?minimize=false","echo-loader?msg=html_loader"] },
With this method I once discovered a file being picked twice.
thanks for trying! if i get some time I have a simpler project which is similar because that's based on a template, I will update it and get it with the same issue, and you can try there if you'd like
According to this bug, @ngtools/webpack
is cheating: https://github.com/angular/angular-cli/issues/8870. It reads input files from disk instead of receiving them through webpack's loader chain, which breaks pre-processing.
I had some luck reversing the order: i.e. running ifdef-loader
on the output of @ngtools/webpack
rather than the input to it. This appears to work since @ngtools/webpack
preserves comments. Here's an example.
Input file:
let AuthServiceProvider: Provider = AuthService;
/// #if MODE === 'mock'
console.log('using mock AuthService');
AuthServiceProvider = require('./auth.service.mock').MockAuthServiceProvider;
/// #endif
webpack config:
{
rules: [ {
test: /\.ts$/,
use: [
{ loader: 'echo-loader?msg=dump' }, // runs last
{ loader: '@ngtools/webpack' },
{ loader: 'ifdef-loader' }, // runs first
]
}],
}
Output:
var AuthServiceProvider = AuthService;
/// #if MODE === 'mock' <--- WRONG: the if block was not evaluated
console.log('using mock AuthService');
AuthServiceProvider = require('./auth.service.mock').MockAuthServiceProvider;
/// #endif
When I reverse the order of @ngtools/webpack
and ifdef-loader
in my webpack config, I see the condition gets evaluated:
false
var AuthServiceProvider = AuthService;
///////////////////////
//////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
//////////
true
var AuthServiceProvider = AuthService;
///////////////////////
console.log('using mock AuthService');
AuthServiceProvider = require('./auth.service.mock').MockAuthServiceProvider;
//////////
But this approach is fragile since it relies on an implementation detail of @ngtools/webpack
.
@mamacdon thanks i managed to get this working.
Just another issue we encountered is the following
This wasnt working
/// #if DEV
import "./app/app-dark.theme.scss";
/// #endif
import { AppModule } from "./app/app.module.browser";
This works
import { AppModule } from "./app/app.module.browser";
/// #if DEV
import "./app/app-dark.theme.scss";
/// #endif
For some reason the first one was stripping the /// #endif
Hi,
Instead of ts-loader, I have use ngtools/webpack which compile and perform AoT. does ifdef-loader work with ngtools/webpack? cheers, Khoa