Giter Club home page Giter Club logo

Comments (15)

mamacdon avatar mamacdon commented on August 22, 2024 2

According to this bug, @ngtools/webpack is cheating: angular/angular-cli#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.

from ifdef-loader.

nippur72 avatar nippur72 commented on August 22, 2024

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}']
      }
    ]

from ifdef-loader.

KhoaSydney avatar KhoaSydney commented on August 22, 2024

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
   
]```

from ifdef-loader.

nippur72 avatar nippur72 commented on August 22, 2024

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 */,
         }
      }
   });

from ifdef-loader.

KhoaSydney avatar KhoaSydney commented on August 22, 2024

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.

from ifdef-loader.

nippur72 avatar nippur72 commented on August 22, 2024

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

from ifdef-loader.

stephenlautier avatar stephenlautier commented on August 22, 2024

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}`]

from ifdef-loader.

nippur72 avatar nippur72 commented on August 22, 2024

@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)

from ifdef-loader.

stephenlautier avatar stephenlautier commented on August 22, 2024

@nippur72 thanks for the info! im not a webpack guru so it definitely helps 👍

i will try and give that a shot

from ifdef-loader.

stephenlautier avatar stephenlautier commented on August 22, 2024

hmm.. its very strange, i tested out with @nippur72 and the code is not there, so not sure how its giving an error 😕

source

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 after

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

from ifdef-loader.

nippur72 avatar nippur72 commented on August 22, 2024

For some reason webpack is picking the unprocessed .ts file, perhaps with another loader. Can you share your webpack.config.js ?

from ifdef-loader.

stephenlautier avatar stephenlautier commented on August 22, 2024

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;
};

from ifdef-loader.

nippur72 avatar nippur72 commented on August 22, 2024

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.

from ifdef-loader.

stephenlautier avatar stephenlautier commented on August 22, 2024

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

from ifdef-loader.

stephenlautier avatar stephenlautier commented on August 22, 2024

@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

from ifdef-loader.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.