Setting up PrimeNG project with Webpack
Webpack (https://webpack.js.org) is a de facto standard bundler for single-page applications. It analyzes dependencies between JavaScript modules, assets (styles, icons, and images) as well as other files in your application and bundles everything together. In Webpack, everything is a module . You can, for example, import a CSS file like a JavaScript file using require('./myfile.css')
or import './myfile.css'
.
Webpack can figure out the right processing strategy for imported files by means of the file extension and associated loader. It is not always reasonable to build one big bundle file. Webpack has various plugins to split your code and generate multiple bundle files. It can also load parts of your application asynchronously on demand (lazy loading). All these features make it a power tool. In this section, we will give a high-level overview of Webpack 2 core concepts and show essential steps for creating a Webpack-based Angular, PrimeNG application.
Note
The complete seed project with PrimeNG and Webpack is available on GitHub athttps://github.com/ova2/angular-development-with-primeng/tree/master/chapter1/primeng-webpack-setup. The project structure was kept the same as in the SystemJS-based setup.
Entry point and output
JavaScript and other files imported into each other are closely interwoven. Webpack creates a graph of all such dependencies. The starting point of this graph is called entry point. An entry point tells Webpack where to start to resolve all dependencies and creates a bundle. Entry points are created in the Webpack configuration file using the entry
property. In the seed project on GitHub, we have two configuration files, one for the development mode (webpack.dev.js
) and one for the production (webpack.prod.js
) mode, each with two entry points.
In the development mode, we use the main entry point for JIT compilation. The main.jit.ts
file contains quite normally bootstrapping code. The second entry point combines files from core-js
(Polyfills for modern ECMAScript features) and zone.js
(the basis for Angular's change detection):
entry: { 'main': './main.jit.ts', 'polyfill': './polyfill.ts' }
In the production mode, we use the main entry point for AOT compilation. JIT and AOT were mentioned in the Angular modularity and lifecycle hooks section:
entry: { 'main': './main.aot.ts', 'polyfill': './polyfill.ts' }
The output
property tells Webpack where to bundle your application. You can use placeholders such as [name]
and [chunkhash]
to define what the names of output files look like. The [name]
placeholder will be replaced by the name defined in the entry
property. The [chunkhash]
placeholder will be replaced by the hash of the file content at project build time. The chunkFilename
option determines the names of on-demand (lazy) loaded chunks--files loaded by System.import()
. In the development mode, we don't use [chunkhash]
because of performance issues during hash generation:
output: { filename: '[name].js', chunkFilename: '[name].js' }
The [chunkhash]
placeholder is used in the production mode to achieve so called long term caching--every file gets cached in the browser and will be automatically invalidated and reloaded when the hash changes:
output: { filename: '[name].[chunkhash].js', chunkFilename: '[name].[chunkhash].js' }
Note
A hash in the filename changes every compilation when the file content is changed. That means, files with hashes in names can not be included manually in the HTML file (index.html
). HtmlWebpackPlugin
(https://github.com/jantimon/html-webpack-plugin) helps us to include generated bundles with <script>
or <link>
tags in the HTML. The seed project makes use of this plugin.
Loaders and plugins
Webpack only understands JavaScript files as modules. Every other file (.css
, .scss
, .json
, .jpg
, and many more) can be transformed into a module while importing. Loaders transform these files and add them to the dependency graph. Loader configuration should be done under module.rules
. There are two main options in the loader configuration:
- The
test
property with a regular expression for testing files the loader should be applied to loader
oruse
property with the concrete loader name
module: { rules: [ {test: /.json$/, loader: 'json-loader'}, {test: /.html$/, loader: 'raw-loader'}, ... ] }
Note that loaders should be registered in package.json
so that they can be installed under node_modules
. Webpack homepage has a good overview of some popular loaders (https://webpack.js.org/loaders). For TypeScript files, it is recommended to use the following sequence of loaders in the development mode:
{test: /.ts$/, loaders: ['awesome-typescript-loader', 'angular2-template-loader']}
Multiple loaders are applied from right to left. The angular2-template-loader
searches for templateUrl
and styleUrls
declarations and inlines HTML and styles inside of the @Component
decorator. The awesome-typescript-loader
is mostly for speeding up the compilation process. For AOT compilation (production mode), another configuration is required:
{test: /.ts$/, loader: '@ngtools/webpack'}
Webpack has not only loaders, but also plugins which take responsibility for custom tasks beyond loaders. Custom tasks could be the compression of assets, extraction of CSS into a separate file, generation of a source map, definition of constants configured at compile time, and so on. One of the helpful plugins used in the seed project is the CommonsChunkPlugin
. It generates chunks of common modules shared between entry points and splits them into separate bundles. This results in page speed optimizations as the browser can quickly serve the shared code from cache. In the seed project, we moved Webpack's runtime code to a separate manifest
file in order to support long-term caching. This will avoid hash recreation for vendor files when only application files are changed:
plugins: [ new CommonsChunkPlugin({ name: 'manifest', minChunks: Infinity }), ... ]
As you can see, configuration of plugins is done in the plugins
option. There are two plugins for production configuration yet to be mentioned here. The AotPlugin
enables AOT compilation. It needs to know the path of tsconfig.json
and the path with module class used for bootstrapping:
new AotPlugin({ tsConfigPath: './tsconfig.json', entryModule: path.resolve(__dirname, '..') + '/src/app/app.module#AppModule' })
UglifyJsPlugin
is used for code minification:
new UglifyJsPlugin({ compress: { dead_code: true, unused: true, warnings: false, screw_ie8: true }, ... })
Adding PrimeNG, CSS, and SASS
It's time to finish the setup. First, make sure that you have PrimeNG and FontAwesome dependencies in the package.json
file. For example:
"primeng": "~2.0.2", "font-awesome": "~4.7.0"
Second, bundle all CSS files into one file. This task is accomplished by ExtractTextPlugin
, which is needed for loaders and plugin configuration:
{test: /.css$/, loader: ExtractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" }) }, {test: /.scss/, loader: ExtractTextPlugin.extract({ fallback: "style-loader", use: ['css-loader', 'sass-loader'] }), exclude: /^_.*.scss/ } ... plugins: [ new ExtractTextPlugin({ filename: "[name].css" // file name of the bundle }), ... ]
Note
For production, you should set the filename to "[name].[chunkhash].css"
. The bundled CSS file gets automatically included into index.html
by HtmlWebpackPlugin
.
We prefer not to use styleUrls
in the components. The seed project imports a CSS und SASS files in one place--inside of main.scss
file located under src/assets/css
:
// vendor files (imported from node_modules) @import "~primeng/resources/themes/bootstrap/theme.css"; @import "~primeng/resources/primeng.min.css"; @import "~font-awesome/css/font-awesome.min.css"; // base project stuff (common settings) @import "global"; // specific styles for components @import "../../app/app.component"; @import "../../app/section/section.component";
Note that the tilde ~
points to the node_modules
. More precisely the Sass preprocessor interprets it as the node_modules
folder. Sass is explained in Chapter 2, Theming Concepts and Layouts. The main.scss
file should be imported in the entry points main.jit.ts
and main.aot.ts
:
import './assets/css/main.scss';
Webpack takes care of the rest. There are more goodies from Webpack--a development server with live reloading webpack-dev-server
(https://webpack.js.org/configuration/dev-server). It detects changes made to files and recompiles automatically. You can start it with npm start
or npm run start:prod
. These commands represent npm scripts:
"start": webpack-dev-server --config config/webpack.dev.js --inline --open "start:prod": webpack-dev-server --config config/webpack.prod.js --inline --open
Note
When running webpack-dev-server
, the compiled output is served from memory. This means, the application being served is not located on disk in the dist
folder.
That's all. More configuration options for unit and end-to-end testing will be added in Chapter 10, Creating Robust Applications.