Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@ngtools/webpack #4

Open
KhoaSydney opened this issue Jul 18, 2017 · 15 comments
Open

@ngtools/webpack #4

KhoaSydney opened this issue Jul 18, 2017 · 15 comments

Comments

@KhoaSydney
Copy link

KhoaSydney commented Jul 18, 2017

Hi,

Instead of ts-loader, I have use ngtools/webpack which compile and perform AoT. does ifdef-loader work with ngtools/webpack?
cheers,
Khoa

@nippur72
Copy link
Owner

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

@KhoaSydney
Copy link
Author

KhoaSydney commented Jul 19, 2017

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

@nippur72
Copy link
Owner

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

@KhoaSydney
Copy link
Author

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.

@nippur72
Copy link
Owner

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

@stephenlautier
Copy link
Contributor

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

@nippur72
Copy link
Owner

nippur72 commented Dec 1, 2017

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

@stephenlautier
Copy link
Contributor

stephenlautier commented Dec 1, 2017

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

i will try and give that a shot

@stephenlautier
Copy link
Contributor

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

@nippur72
Copy link
Owner

nippur72 commented Dec 2, 2017

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

@stephenlautier
Copy link
Contributor

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

@nippur72
Copy link
Owner

nippur72 commented Dec 2, 2017

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.

@stephenlautier
Copy link
Contributor

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

@mamacdon
Copy link

mamacdon commented Jun 18, 2018

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.

@stephenlautier
Copy link
Contributor

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants