name | culture | description | engines | author | contributors | translators | keywords | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Setup with Webpack |
en-US |
If you're interested in getting setup with Webpack to build projects, this article will take you through setting up both your machine and a production quality starter project. |
|
|
|
Let's start by getting you set up with a great set of tools that you can use to build modern ${context.language.name} applications. All our tooling is built on Node.js. If you have that installed already, great! If not, you should go to the official web site, download and install it. Everything else we need will be installed via Node's package manager (npm). If you already have npm installed, make sure you've got the latest version to avoid any issues with the other tools.
Info For command-line operations, we recommend Windows users to use Git Bash or Git Shell.
We'll begin by downloading a skeleton. We've got several versions available for you based on your language and tooling preferences. Please download the latest skeletons now.
Once the download has completed, unzip it and look inside. The readme file contained therein will explain the various options available to you. Please select one of the skeletons and copy it to the location on your file system that you wish your code to reside. Be sure to rename the folder to appropriately represent the app you want to build.
You will now find everything you need inside the folder, including a basic build, package configuration, styles and more. With all this in place, let's run some commands.
- Open a console and change directory into your app's directory.
- Execute the following command to install the dependencies listed in the dependencies and devDependencies sections of the package manifest:
npm install
Everything we've done so far is standard Node.js build and package management procedures. It doesn't have anything specific to do with Aurelia itself. We're just walking you through setting up a modern ${context.language.name} project and build configuration from scratch.
Info Bootstrap and Font-Awesome are not dependencies of Aurelia. We only leverage them as part of the starter kit in order to help you quickly achieve a decent look out-of-the-box. You can easily replace them with whatever your favorite CSS framework and/or icon library is. Similarly, Bluebird is recommended, but not required. Note however that the
Promise
implementation in certain Microsoft Edge versions are extremely slow, thus keeping an alternativePromise
implementation is recommended.
If you've followed along this far, you now have all the libraries, build configuration and tools you need to create amazing ${context.language.name} apps with Aurelia. The next thing to do is run the sample app. To see this in action, on your console, use the following command to build and launch the server:
npm start
You can now browse to http://localhost:9000/ to see the app.
Info The Skeleton App uses Webpack's Development Server for automated page refreshes on code/markup changes, meaning you do not need to restart the command every time you make a change.
To run the unit tests, first ensure that you have followed the steps above in order to install all dependencies and successfully build the library. Once you have done that, you may run the tests with the following command:
npm test
Integration tests are performed with Protractor.
-
Place your E2E-Tests into the folder
test/e2e/src
-
Run the tests by invoking
npm run e2e
- Make sure your app runs and is accessible:
WEBPACK_PORT=19876 npm start
- Once bundle is ready, run the E2E-Tests in another console:
npm run e2e:start
-
Dependencies
- After downloading skeleton-esnext-webpack from Aurelia github,
we replace any reference to
@easy-webpack
with the normal webpack modules.
Inpackage.json
, remove all modules that start with@easy-webpack
indevDependencies
:
"@easy-webpack/config-aurelia": "^2.0.1", "@easy-webpack/config-babel": "^2.0.2", "@easy-webpack/config-common-chunks-simple": "^2.0.1", "@easy-webpack/config-copy-files": "^1.0.0", "@easy-webpack/config-css": "^2.3.2", "@easy-webpack/config-env-development": "^2.1.1", "@easy-webpack/config-env-production": "^2.1.0", "@easy-webpack/config-external-source-maps": "^2.0.1", "@easy-webpack/config-fonts-and-images": "^1.2.1", "@easy-webpack/config-generate-index-html": "^2.0.1", "@easy-webpack/config-global-bluebird": "^1.2.0", "@easy-webpack/config-global-jquery": "^1.2.0", "@easy-webpack/config-global-regenerator": "^1.2.0", "@easy-webpack/config-html": "^2.0.2", "@easy-webpack/config-json": "^2.0.2", "@easy-webpack/config-test-coverage-istanbul": "^2.0.2", "@easy-webpack/config-uglify": "^2.1.0", "@easy-webpack/core": "^1.3.2",
with the following:
"aurelia-webpack-plugin": "^1.1.0", "copy-webpack-plugin": "^3.0.1", "html-webpack-plugin": "^2.22.0", "babel-core": "^6.17.0", "babel-loader": "^6.2.5", "babel-polyfill": "^6.16.0", "css-loader": "^0.25.0", "file-loader": "^0.9.0", "sourcemap-istanbul-instrumenter-loader": "^0.2.0", "style-loader": "^0.13.1", "url-loader": "^0.5.7", "html-loader": "^0.4.4"
Also, change bundler (webpack) to the latest version by replacing:
"webpack": "^2.1.0-beta.22"
with
"webpack": "^2.1.0-beta.25"
- After downloading skeleton-esnext-webpack from Aurelia github,
we replace any reference to
-
Basic Configuration
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const AureliaWebpackPlugin = require('aurelia-webpack-plugin');
const project = require('./package.json');
module.exports = {
entry: {
'app': [], // <-- this array will be filled by the aurelia-webpack-plugin
'aurelia': Object.keys(project.dependencies).filter(dep => dep.startsWith('aurelia-'))
},
output: {
path: path.resolve('dist'),
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'stage-1'],
plugins: ['transform-decorators-legacy']
}
},
{
test: /\.html$/,
exclude: /index\.html$/, // index.html will be taken care by HtmlWebpackPlugin
use: 'html-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(png|jpe?g|gif|svg|eot|woff|woff2|ttf)$/,
use: 'url-loader'
}
]
},
plugins: [
new webpack.ProvidePlugin({
regeneratorRuntime: 'regenerator-runtime', // to support await/async syntax
Promise: 'bluebird', // because Edge browser has slow native Promise object
$: 'jquery', // because 'bootstrap' by Twitter depends on this
jQuery: 'jquery', // just an alias
}),
new HtmlWebpackPlugin({
template: 'index.html'
}),
new AureliaWebpackPlugin({
root: path.resolve(),
src: path.resolve('src'),
baseUrl: '/'
}),
new webpack.optimize.CommonsChunkPlugin({
name: ['aurelia']
})
]
};
- Change our
index.html
to
<!DOCTYPE html>
<html>
<head>
<title>Aurelia Navigation Skeleton</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<base href="/">
<!-- imported CSS are concatenated and added automatically -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="format-detection" content="telephone=no">
</head>
<body aurelia-app="main">
<div class="splash">
<div class="message">Aurelia Navigation Skeleton</div>
<i class="fa fa-spinner fa-spin"></i>
</div>
<!-- Uncomment below line for Webpack Dev Server auto reload -->
<!--<script src="/webpack-dev-server.js"></script>-->
</body>
</html>
- Install dependencies
npm install
-
Adjust development tasks
Modify the dev task so it doesn't throw an error, by replacing old dev task inpackage.json
with the slightly different version (without--progress
)
Replace:"server:dev": "cross-env NODE_ENV=development node ./node_modules/webpack-dev-server/bin/webpack-dev-server --inline --progress --profile --watch",
With:
"server:dev": "cross-env NODE_ENV=development node ./node_modules/webpack-dev-server/bin/webpack-dev-server --inline --profile --watch",
-
Start development
npm start
-
Template optimization
-
Additional dependencies for handling template, as Aurelia templates need to be optimized to reduce bundle size:
"raw-loader": "^0.5.1", "html-minifier": "^3.1.0", "html-minifier-loader": "^1.3.3",
-
IMPORTANT: Start from
webpack@2.1.0-beta.23
(current version:@beta.25
), custom properties are no longer allowed on base config object, So we will be usingwebpack.LoaderOptionsPlugin
to provide some config options forhtml-minifier-loader
. -
Modify our config in
webpack.config.js
by replacing:{ test: /\.html$/, exclude: /index\.html$/, // index.html will be taken care by HtmlWebpackPlugin use: 'html-loader' }
With:
{ test: /\.html$/, exclude: /index\.html$/, // index.html will be taken care by HtmlWebpackPlugin use: [ 'raw-loader', 'html-minifier-loader' ] }
Also add to
plugins
config:new webpack.LoaderOptionsPlugin({ options: { context: __dirname, 'html-minifier-loader': { removeComments: true, // remove all comments collapseWhitespace: true, // collapse white space between block elements (div, header, footer, p etc...) collapseInlineTagWhitespace: true, // collapse white space between inline elements (button, span, i, b, a etc...) collapseBooleanAttributes: true, // <input required="required"/> => <input required /> removeAttributeQuotes: true, // <input class="abcd" /> => <input class=abcd /> minifyCSS: true, // <input style="display: inline-block; width: 50px;" /> => <input style="display:inline-block;width:50px;"/> minifyJS: true, // same with CSS but for javascript removeScriptTypeAttributes: true, // <script type="text/javascript"> => <script> removeStyleLinkTypeAttributes: true // <link type="text/css" /> => <link /> } } })
-
-
Using CSS pre-processor:
less
,sass
andstylus
-
IMPORTANT: You can't require style with different extension than
css
like this:<!-- invalid require --> <require from='./style.less'></require>
So we have to require our style (
less
,sass
orstyl
) in our javascript like this:import './style.less';
-
Based on the choice of pre-processor, use corresponding dependencies:
less
:
npm install --save-dev less less-loader
sass
:
npm install --save-dev node-sass sass-loader
styl
:
npm install --save-dev stylus stylus-loader
-
Modify style loading rule by adding chosen loader and file extension, it should look like this for
less
:{ test: /\.(less|css)$/, // <--- This was /\.css$/ for only css use: [ { loader: 'style-loader', options: { singleton: true } }, 'css-loader', 'less-loader' // <--- This was added to enable "import 'style.less'" ] },
- Javascript optimization.
-
To deliver a smaller bundle for production. Add to your
plugins
in the configuration:new webpack.optimize.UglifyJsPlugin({ mangle: { screw_ie8: true, keep_fnames: true}, dead_code: true, unused: true, comments: true, compress: { screw_ie8: true, keep_fnames: true, drop_debugger: false, dead_code: false, unused: false, warnings: false } })
-
Pass some extra configuration to our
babel-loader
to enable simpler transformation and tree-shaking to remove unused codes by:
Replacing:{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', query: { presets: ['es2015', 'stage-1'], plugins: ['transform-decorators-legacy'] } },
With
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', query: { presets: [ [ 'es2015', { loose: true, // this helps simplify javascript transformation module: false // this helps enable tree shaking for webpack 2 }], 'stage-1' ], plugins: ['transform-decorators-legacy'] } }
- Using plugins
- Plugins registry repository: Plugins
- To bundle plugins' dependencies properly, all sub modules of a plugin have to be put in
aurelia.build.resources
in either that plugin'spackage.json
or your project'spackage.json
This is crucial but not all aurelia plugins were aware of this matter / or built before this standard configuration. If you want to use a plugin, follow these steps:
- Install a plugin like normal. Ex.
npm install aurelia-dialog --save
- Go to your project
package.json
, look for path"aurelia.build.resources"
, add plugin's module name (ex. "aurelia-dialog") toresources
array - Start your project to check if the plugin is properly configured
- If webpack doesn't complain, plugin is good
- If it does, peek to plugin source directory in
node_modules
, Exnode_modules/aurelia-dialog
- Have a look at
package.json
to see if"main"
points to the right file. (As the time of this writing, plugin"aurelia-async"
pointed to the wrong entry filename) - Have a look at
dist/commonjs
folder (all aurelia plugins are built in this standard) - Put all the module names (if any), without extension into your project
package.json
"aurelia.build.resources"
, with plugin name as prefix (ex."aurelia-dialog"
) - Rerun
npm start
- Have a look at
- A good example of how to know if a plugin has proper configs is to look into
aurelia-dialog
'spackage.json
->aurelia.build.resources
- Example for
"aurelia-clean-bindings"
plugin:-
This plugin doesn't have sub modules dependencies configured properly, as it is distributed with following module structure:
dist └───commonjs │ │ clean-bindings.js │ │ index.js
-
In this plugin's
package.json
, there is no"aurelia.build.resources"
path with value:["aurelia-clean-bindings/clean-bindings"]
, so if you only add value"aurelia-clean-bindings"
to yourpackage.json
's"aurelia.build.resources"
value:"aurelia-clean-bindings"
, it won't work -
Fix:
-
Add to your
package.json
path"aurelia.build.resources"
value:["aurelia-clean-bindings", "aurelia-clean-bindings/clean-bindings"]
-
It should look like this in your
package.json
:"aurelia": { "build": { "resources": [ "aurelia-other-plugin...", [ "aurelia-clean-bindings", "aurelia-clean-bindings/clean-bindings" ], "aurelia-other-plugin..." ] } }
-
Create an issue to inform plugin author
-
Happy adding plugins
-
-
-
Suggested Production Setup
-
NOTE: following configuration is for
less
. Change style loader section accordingly to your choice of css pre-processorconst path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const AureliaWebpackPlugin = require('aurelia-webpack-plugin'); const project = require('./package.json'); const ENV = process.env.NODE_ENV && process.env.NODE_ENV.toLowerCase() || 'development'; const DEBUG = ENV !== 'production'; const metadata = { port: process.env.WEBPACK_PORT || 9000, host: process.env.WEBPACK_HOST || 'localhost', ENV: ENV, HMR: process.argv.join('').indexOf('hot') >= 0 || !!process.env.WEBPACK_HMR }; const outDir = path.resolve('dist'); const aureliaModules = [ 'aurelia-bootstrapper-webpack', 'aurelia-binding', 'aurelia-dependency-injection', 'aurelia-event-aggregator', 'aurelia-framework', 'aurelia-history', 'aurelia-history-browser', 'aurelia-loader', 'aurelia-loader-webpack', 'aurelia-logging', 'aurelia-logging-console', 'aurelia-metadata', 'aurelia-pal', 'aurelia-pal-browser', 'aurelia-path', 'aurelia-polyfills', 'aurelia-route-recognizer', 'aurelia-router', 'aurelia-task-queue', 'aurelia-templating', 'aurelia-templating-binding', 'aurelia-templating-router', 'aurelia-templating-resources' ]; module.exports = { entry: { app: ['./src/main'], // <-- this array will be filled by the aurelia-webpack-plugin aurelia: aureliaModules }, output: { path: outDir, filename: DEBUG ? '[name].bundle.js' : '[name].[chunkhash].bundle.js', sourceMapFilename: DEBUG ? '[name].bundle.map' : '[name].[chunkhash].bundle.map', chunkFilename: DEBUG ? '[id].chunk.js' : '[id].[chunkhash].chunk.js' }, resolve: { modules: [path.resolve(), 'node_modules'] }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, // or include: path.resolve('src'), loader: 'babel-loader', query: { presets: [ [ 'es2015', { loose: true, // this helps simplify javascript transformation module: false // this helps enable tree shaking for webpack 2 }], 'stage-1' ], plugins: ['transform-decorators-legacy'] } }, { test: /\.html$/, exclude: /index\.html$/, // index.html will be taken care by HtmlWebpackPlugin use: [ 'raw-loader', 'html-minifier-loader' ] }, { test: /\.(less|css)$/, // <--- This was /\.css$/ for only css use: [ { loader: 'style-loader', query: { singleton: !DEBUG } }, { loader: 'css-loader', query: { minimize: !DEBUG // <--- Enable style minification if production } }, 'less-loader' // <--- This was added to enable "import 'style.less'" ] }, { test: /\.(png|jpe?g|gif|svg|eot|woff|woff2|ttf)$/, loader: 'url-loader', query: { limit: 10000, name: '[name].[ext]' } } ] }, plugins: [ new webpack.LoaderOptionsPlugin({ debug: DEBUG, devtool: 'source-map', options: { context: __dirname, 'html-minifier-loader': { removeComments: true, // remove all comments collapseWhitespace: true, // collapse white space between block elements (div, header, footer, p etc...) collapseInlineTagWhitespace: true, // collapse white space between inline elements (button, span, i, b, a etc...) collapseBooleanAttributes: true, // <input required="required"/> => <input required /> removeAttributeQuotes: true, // <input class="abcd" /> => <input class=abcd /> minifyCSS: true, // <input style="display: inline-block; width: 50px;" /> => <input style="display:inline-block;width:50px;"/> minifyJS: true, // same with CSS but for javascript removeScriptTypeAttributes: true, // <script type="text/javascript"> => <script> removeStyleLinkTypeAttributes: true // <link type="text/css" /> => <link /> } } }), new webpack.ProvidePlugin({ regeneratorRuntime: 'regenerator-runtime', // to support await/async syntax Promise: 'bluebird', // because Edge browser has slow native Promise object jQuery: 'jquery', // because 'bootstrap' by Twitter depends on jQuery $: 'jquery' // just an alias }), new AureliaWebpackPlugin({ root: path.resolve(), src: path.resolve('src') }), new HtmlWebpackPlugin({ template: 'index.html', inject: 'head' }), new webpack.optimize.CommonsChunkPlugin({ // to eliminate code duplication across bundles name: ['aurelia'] }) ].concat(DEBUG ? [ ] : [ /** * Plugin: DedupePlugin * Description: Prevents the inclusion of duplicate code into your bundle * and instead applies a copy of the function at runtime. * * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin * See: https://github.com/webpack/docs/wiki/optimization#deduplication */ new webpack.optimize.DedupePlugin(), new webpack.optimize.UglifyJsPlugin({ mangle: { screw_ie8: true, keep_fnames: true }, dead_code: true, unused: true, comments: true, compress: { screw_ie8: true, keep_fnames: true, drop_debugger: false, dead_code: false, unused: false, warnings: false } }) ]), devServer: { port: metadata.port, host: metadata.host, historyApiFallback: true, watchOptions: { aggregateTimeout: 300, poll: 1000 }, progress: true, outputPath: outDir } };
-