Angular2 - 與 Webpack 整合環境設定 - 3
前言
前篇我們已經可以透過 webpack + Angular2 跑出 Hot Reload ,但其實我們還有許多事情要繼續整合處理, 例如我們希望可以透過 npm start 進行打包 ( webpack-dev-server 是不會產出實體檔案的.. ) 或是能分開 webpack.config 的設定等等,所以,這篇我們會繼續針對這些繼續設定下去。
注意事項
- 此文章的版本為 angular2 beta 15 ,未來應該也不會跟著版本更新...請看的朋友注意
- 裡面 Webpack 版本為 1.x , 2.x 可能會有幅度變動,請直接參考 angular2-webpack-stater
- 此文章壟長,且未必能 100% 成功 ( 因 Package 相依性、Webpack 版本、angular2 版本 可能會造成差異 )
- 有興趣的朋友,可以直接參考 angular2-webpack-stater Source Code
- 文章內容的所有 Soucre 來源為 angular2-webpack-stater ,小弟只是針對內容進行解析紀錄
- 文章開頭會以 Quickstart 為起始
- 官方 Source Code 裡面有很多的註解,但本篇文章,因為篇幅關係,小弟會把註解拿掉
分離 webpack.config.js
在原始的 source code 裡面,大神是將 webpack.config.js 進行分離,分別有
- webpack.common.js - 負責共用的設定
- webpack.dev.js - 負責開發用的設定
- webpack.prod.js - 負責正式環境的設定
- webpack.test.js - 負責測試環境的設定
而我們這邊,會先來看看,如何拆出 webpack.common.js 和 webpack.dev.js。
安裝 webpack-merge
基本上,webpack 沒有提供這種合併多個 config 的功能,所以我們要透過 webpack-merge package 來達到此需求。
同樣的,我們直接看 package.json ,這樣大家才可以得知,目前這篇文章的所有 package 版本與目前安裝狀況。
這邊我們多增加了 "webpack-merge": "^0.8.4" 。
完整 package.json
{
"name": "angular2-quickstart",
"version": "1.0.0",
"scripts": {
"start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
"tsc": "tsc",
"tsc:w": "tsc -w",
"lite": "lite-server",
"typings": "typings",
"postinstall": "typings install"
},
"license": "ISC",
"dependencies": {
"angular2": "2.0.0-beta.15",
"core-js": "^2.2.2",
"es6-shim": "^0.35.0",
"es7-reflect-metadata": "^1.6.0",
"rxjs": "5.0.0-beta.2",
"zone.js": "0.6.10"
},
"devDependencies": {
"typescript": "^1.8.10",
"typings":"^0.7.12",
"webpack": "^1.13.0",
"webpack-dev-server": "^1.14.1",
"copy-webpack-plugin": "^1.1.1",
"html-webpack-plugin": "^2.15.0",
"awesome-typescript-loader": "~0.16.2",
"source-map-loader": "^0.1.5",
"json-loader": "^0.5.4",
"raw-loader": "0.5.1",
"angular2-hmr": "~0.5.5",
"webpack-merge": "^0.8.4"
}
}
完成後,執行 npm install 安裝。
分離 webpack.config.js
接下來,其實沒甚麼技巧可言,大家可以依據自己的需求,將常共用的 webpack.config 移到 webpack.common.js 裡面, 但我們這邊依照大神的 source 來做這件事情。
首先我們先把 webpack.config.js 拷貝一份到 config 目錄底下,並且改名為 webpack.common.js 如下圖:
因為是 common ,所以我們移除
const DefinePlugin = require('webpack/lib/DefinePlugin');
既然 DefinePlugin require 移除了,那 plugins 底下的 DefinePlugin 自然也移除了
new DefinePlugin({
'ENV': JSON.stringify('development'),
'HMR': true,
'process.env': {
'ENV': JSON.stringify('development'),
'NODE_ENV': JSON.stringify('development'),
'HMR': true,
}
})
接下來,也把 tslint 和 devServer 移除
tslint: {
emitErrors: false,
failOnHint: false,
resourcePath: 'src'
},
devServer: {
port: 3000,
host: 'localhost',
historyApiFallback: true,
watchOptions: {
aggregateTimeout: 300,
poll: 1000
}
},
P.S 如果大家有去看 Source ,會發現有一個 Metadata 如下兩個 source code , 神人作者對於 Metadata 其實有兩個用途,一個是拿來輸出到網頁上,例如 title . 另外一個是拿來使用設定環境變數;基本上輸出 title 的部分,我就會直接拿掉了。
/*
* Webpack Constants
*/
const METADATA = {
title: 'Angular2 Webpack Starter by @gdi2290 from @AngularClass',
baseUrl: '/'
};
/*
* Static metadata for index.html
*
* See: (custom attribute)
*/
metadata: METADATA,
完整的 webpack.common.js
/**
* @author: @AngularClass
*/
const webpack = require('webpack');
const helpers = require('./helpers');
/*
* Webpack Plugins
*/
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin;
/*
* Webpack configuration
*
* See: http://webpack.github.io/docs/configuration.html#cli
*/
module.exports = {
debug: true,
devtool: 'cheap-module-eval-source-map',
entry: {
'polyfills': './src/polyfills.ts',
//'vendor': './src/vendor.ts',
'main': './src/app/main.ts'
},
output: {
path: helpers.root('dist'),
filename: '[name].bundle.js',
sourceMapFilename: '[name].map',
chunkFilename: '[id].chunk.js'
},
resolve: {
extensions: ['', '.ts', '.js'],
root: helpers.root('src'),
modulesDirectories: ['node_modules']
},
module: {
preLoaders: [
{
test: /\.js$/,
loader: 'source-map-loader',
exclude: [
// these packages have problems with their sourcemaps
helpers.root('node_modules/rxjs'),
helpers.root('node_modules/@angular2-material')
]
}
],
loaders: [
{
test: /\.ts$/,
loader: 'awesome-typescript-loader',
exclude: [/\.(spec|e2e)\.ts$/]
},
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.css$/,
loader: 'raw-loader'
},
{
test: /\.html$/,
loader: 'raw-loader',
//exclude: [helpers.root('src/index.html')]
exclude: [helpers.root('src/app/index.html')]
}
]
},
plugins: [
new ForkCheckerPlugin(),
new webpack.optimize.OccurenceOrderPlugin(true),
new webpack.optimize.CommonsChunkPlugin({
name: helpers.reverse(['polyfills', 'vendor'])
}),
new CopyWebpackPlugin([{
from: 'src/assets',
to: 'assets'
}]),
new HtmlWebpackPlugin({
template: 'src/index.html',
chunksSortMode: helpers.packageSort(['polyfills', 'vendor', 'main'])
}),
],
node: {
global: 'window',
crypto: 'empty',
process: true,
module: false,
clearImmediate: false,
setImmediate: false
}
};
那接下來,我們就要調整原始的 webpack.config.js
調整 webpack.config.js
我們改成如下,首先,我們會引用 webpack-merga;另外,我們這邊開始啟用 ENV , HRM , METADATA 環境的變數。
P.S 在原本的 Source Code 裡面,有使用到 webpackMerge(commonConfig.metadata, 整合 commonConfig 的 metadata, 但是在原本的 Source Code 裡面,MetaData 的定義 ,主要是用來顯示於 index 上。 基本上這是不需要的,所以 webpack.config.js 的這邊這段,我就移除了 webpackMerge 。
const METADATA = {
title: 'Angular2 Webpack Starter by @gdi2290 from @AngularClass',
baseUrl: '/'
};
另外,要特別注意 require 的路徑,因為路徑換了,所以要加上 config
P.S 或許大家有注意到,node,debug, devtool, output 都有重複.. 為什麼需要重複,其實小弟我沒有在去做額外的嘗試,但 source 是有保留的。
完整的 webpack.config.js
/**
* @author: @AngularClass
*/
const helpers = require('./config/helpers');
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
const commonConfig = require('./config/webpack.common.js'); // the settings that are common to prod and dev
/**
* Webpack Plugins
*/
const DefinePlugin = require('webpack/lib/DefinePlugin');
/**
* Webpack Constants
*/
const ENV = process.env.ENV = process.env.NODE_ENV = 'development';
const HMR = helpers.hasProcessFlag('hot');
const METADATA = {
host: 'localhost',
port: 3000,
ENV: ENV,
HMR: HMR
};
/**
* Webpack configuration
*
* See: http://webpack.github.io/docs/configuration.html#cli
*/
module.exports = webpackMerge(commonConfig, {
metadata: METADATA,
debug: true,
devtool: 'cheap-module-eval-source-map',
output: {
path: helpers.root('dist'),
filename: '[name].bundle.js',
sourceMapFilename: '[name].map',
chunkFilename: '[id].chunk.js'
},
plugins: [
new DefinePlugin({
'ENV': JSON.stringify(METADATA.ENV),
'HMR': METADATA.HMR,
'process.env': {
'ENV': JSON.stringify(METADATA.ENV),
'NODE_ENV': JSON.stringify(METADATA.ENV),
'HMR': METADATA.HMR,
}
})
],
tslint: {
emitErrors: false,
failOnHint: false,
resourcePath: 'src'
},
devServer: {
port: METADATA.port,
host: METADATA.host,
historyApiFallback: true,
watchOptions: {
aggregateTimeout: 300,
poll: 1000
}
},
node: {
global: 'window',
crypto: 'empty',
process: true,
module: false,
clearImmediate: false,
setImmediate: false
}
});
最後,我們還希望做一件事情,調整一下,npm 的 scripts,讓我們能更輕鬆地執行測試環境。
調整 package.json
我們重新調整了 npm scripts 裡面的項目,我們把 tsc、tsc:w、lite 拿掉, 因為這幾個都整合到 webpack,或是不使用了... 而增加了 server 的區段,未來我們就可以使用
- npm run server:dev 來啟動開發環境 ( 不包含 hot reload )
- npm run server:dev:hmr 來啟動開發環境 ( 包含 hot reload )
- npm run server:prod 來啟動正式環境
( npm run server:prod 現階段會無法使用,這篇文章的最後,會裝上 http-server 就可以正常運作了 )
{
"name": "angular2-quickstart",
"version": "1.0.0",
"scripts": {
"start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
"typings": "typings",
"postinstall": "typings install",
"server": "npm run server:dev",
"server:dev": "webpack-dev-server --config webpack.config.js --inline --progress --profile --colors --watch --display-error-details --display-cached --content-base src/",
"server:dev:hmr": "npm run server:dev -- --hot",
"server:prod": "http-server dist --cors"
},
"license": "ISC",
"dependencies": {
"angular2": "2.0.0-beta.15",
"core-js": "^2.2.2",
"es6-shim": "^0.35.0",
"es7-reflect-metadata": "^1.6.0",
"rxjs": "5.0.0-beta.2",
"zone.js": "0.6.10"
},
"devDependencies": {
"typescript": "^1.8.10",
"typings":"^0.7.12",
"webpack": "^1.13.0",
"webpack-dev-server": "^1.14.1",
"copy-webpack-plugin": "^1.1.1",
"html-webpack-plugin": "^2.15.0",
"awesome-typescript-loader": "~0.16.2",
"source-map-loader": "^0.1.5",
"json-loader": "^0.5.4",
"raw-loader": "0.5.1",
"angular2-hmr": "~0.5.5",
"webpack-merge": "^0.8.4"
}
}
完成後就可以 npm run server:dev:hmr 試試看。
增加 webpack.dev.js
到這邊,已經越來越方便了,但我們正式環境,可能會有一組設定檔,或是其他環境有其他的設定檔, 所以我們希望把 webpack.config.js 也放到 config 目錄底下。
所以我們就將 webpack.config.js 移到 config 目錄底下,並且改名為 webpack.dev.js
因為目錄改變了,所以要調整一下 require 的路徑,把 config 拿掉。
完整 webpack.dev.js 如下
/**
* @author: @AngularClass
*/
const helpers = require('./helpers');
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
const commonConfig = require('./webpack.common.js'); // the settings that are common to prod and dev
/**
* Webpack Plugins
*/
const DefinePlugin = require('webpack/lib/DefinePlugin');
/**
* Webpack Constants
*/
const ENV = process.env.ENV = process.env.NODE_ENV = 'development';
const HMR = helpers.hasProcessFlag('hot');
const METADATA = {
host: 'localhost',
port: 3000,
ENV: ENV,
HMR: HMR
};
/**
* Webpack configuration
*
* See: http://webpack.github.io/docs/configuration.html#cli
*/
module.exports = webpackMerge(commonConfig, {
metadata: METADATA,
debug: true,
devtool: 'cheap-module-eval-source-map',
output: {
path: helpers.root('dist'),
filename: '[name].bundle.js',
sourceMapFilename: '[name].map',
chunkFilename: '[id].chunk.js'
},
plugins: [
new DefinePlugin({
'ENV': JSON.stringify(METADATA.ENV),
'HMR': METADATA.HMR,
'process.env': {
'ENV': JSON.stringify(METADATA.ENV),
'NODE_ENV': JSON.stringify(METADATA.ENV),
'HMR': METADATA.HMR,
}
})
],
tslint: {
emitErrors: false,
failOnHint: false,
resourcePath: 'src'
},
devServer: {
port: METADATA.port,
host: METADATA.host,
historyApiFallback: true,
watchOptions: {
aggregateTimeout: 300,
poll: 1000
}
},
node: {
global: 'window',
crypto: 'empty',
process: true,
module: false,
clearImmediate: false,
setImmediate: false
}
});
調整 package.json
我們就在 server:dev --config 改成 config/webpack.dev.js
{
"name": "angular2-quickstart",
"version": "1.0.0",
"scripts": {
"start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
"typings": "typings",
"postinstall": "typings install",
"server": "npm run server:dev",
"server:dev": "webpack-dev-server --config config/webpack.dev.js --inline --progress --profile --colors --watch --display-error-details --display-cached --content-base src/",
"server:dev:hmr": "npm run server:dev -- --hot",
"server:prod": "http-server dist --cors"
},
"license": "ISC",
"dependencies": {
"angular2": "2.0.0-beta.15",
"core-js": "^2.2.2",
"es6-shim": "^0.35.0",
"es7-reflect-metadata": "^1.6.0",
"rxjs": "5.0.0-beta.2",
"zone.js": "0.6.10"
},
"devDependencies": {
"typescript": "^1.8.10",
"typings":"^0.7.12",
"webpack": "^1.13.0",
"webpack-dev-server": "^1.14.1",
"copy-webpack-plugin": "^1.1.1",
"html-webpack-plugin": "^2.15.0",
"awesome-typescript-loader": "~0.16.2",
"source-map-loader": "^0.1.5",
"json-loader": "^0.5.4",
"raw-loader": "0.5.1",
"angular2-hmr": "~0.5.5",
"webpack-merge": "^0.8.4"
}
}
完成後,可以在 npm run server:dev:hmr 來測試看看
增加編譯環境
接下來,我們要準備增加 build 的設定,我們希望透過
- npm run build:dev 編譯出 dev 環境的檔案
- npm run build:prod 編譯出 正式環境的檔案
修改 package.json
所以我們先修改 package.json,這邊增加了 build 的相關設定,比較需要注意的是,prebuild:dev 和 prebuild:prod 與裡面。
prebuild:dev 代表著,執行 build:dev 之前,他會先跑 prebuild:dev ,而 prebuild:dev 裡面的 npm run clean:dist 其實就是要清除舊的檔案資訊,而 clean:dist 就是 clean 那個區段的設定,目的就是刪除 dist 目錄的所有檔案。
而他主要是用到了 rimraf 這個 package ,所以底下的 package 也要再加上 "rimraf": "^2.5.2" 。 並且在 scripts 裡面也加上 rimraf ,不然 npm run rimraf 會錯誤。
另外,等下的 webpack.prod.js 會使遇到以下 plugin ,所以我們也一併裝上
- compression-webpack-plugin
- webpack-md5-hash,原因https://github.com/webpack/webpack/issues/1315
- http-server
{
"name": "angular2-quickstart",
"version": "1.0.0",
"scripts": {
"start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
"rimraf": "rimraf",
"typings": "typings",
"postinstall": "typings install",
"clean": "npm cache clean && npm run rimraf -- node_modules doc typings coverage dist",
"clean:dist": "npm run rimraf -- dist",
"build": "npm run build:dev",
"prebuild:dev": "npm run clean:dist",
"build:dev": "webpack --config config/webpack.dev.js --progress --profile --colors --display-error-details --display-cached",
"prebuild:prod": "npm run clean:dist",
"build:prod": "webpack --config config/webpack.prod.js --progress --profile --colors --display-error-details --display-cached --bail",
"server": "npm run server:dev",
"server:dev": "webpack-dev-server --config config/webpack.dev.js --inline --progress --profile --colors --watch --display-error-details --display-cached --content-base src/",
"server:dev:hmr": "npm run server:dev -- --hot",
"server:prod": "http-server dist --cors"
},
"license": "ISC",
"dependencies": {
"angular2": "2.0.0-beta.15",
"core-js": "^2.2.2",
"es6-shim": "^0.35.0",
"es7-reflect-metadata": "^1.6.0",
"rxjs": "5.0.0-beta.2",
"zone.js": "0.6.10"
},
"devDependencies": {
"typescript": "^1.8.10",
"typings":"^0.7.12",
"webpack": "^1.13.0",
"webpack-dev-server": "^1.14.1",
"copy-webpack-plugin": "^1.1.1",
"html-webpack-plugin": "^2.15.0",
"awesome-typescript-loader": "~0.16.2",
"source-map-loader": "^0.1.5",
"json-loader": "^0.5.4",
"raw-loader": "0.5.1",
"angular2-hmr": "~0.5.5",
"webpack-merge": "^0.8.4",
"rimraf": "^2.5.2",
"compression-webpack-plugin": "^0.3.1",
"webpack-md5-hash": "^0.0.5",
"http-server": "^0.9.0"
}
}
完成後,先用 npm install 裝一下 rimraf
P.S 原始文件的 clean 其實還有其他設定,但似乎用不到,所以我就先移除了。
{
"clean": "npm cache clean && npm run rimraf -- node_modules doc typings coverage dist",
"clean:dist": "npm run rimraf -- dist",
"preclean:install": "npm run clean",
"clean:install": "npm set progress=false && npm install",
"preclean:start": "npm run clean",
"clean:start": "npm start",
}
加上 webpack.prod.js 檔案
最後,我們於 config 加上 webpack.prod.js 上。
/**
* @author: @AngularClass
*/
const helpers = require('./helpers');
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
const commonConfig = require('./webpack.common.js'); // the settings that are common to prod and dev
/**
* Webpack Plugins
*/
const ProvidePlugin = require('webpack/lib/ProvidePlugin');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const DedupePlugin = require('webpack/lib/optimize/DedupePlugin');
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
const CompressionPlugin = require('compression-webpack-plugin');
const WebpackMd5Hash = require('webpack-md5-hash');
/**
* Webpack Constants
*/
const ENV = process.env.NODE_ENV = process.env.ENV = 'production';
const HOST = process.env.HOST || 'localhost';
const PORT = process.env.PORT || 8080;
const METADATA = {
host: HOST,
port: PORT,
ENV: ENV,
HMR: false
};
module.exports = webpackMerge(commonConfig, {
debug: false,
devtool: 'source-map',
output: {
path: helpers.root('dist'),
filename: '[name].[chunkhash].bundle.js',
sourceMapFilename: '[name].[chunkhash].bundle.map',
chunkFilename: '[id].[chunkhash].chunk.js'
},
plugins: [
new WebpackMd5Hash(),
new DedupePlugin(),
new DefinePlugin({
'ENV': JSON.stringify(METADATA.ENV),
'HMR': METADATA.HMR,
'process.env': {
'ENV': JSON.stringify(METADATA.ENV),
'NODE_ENV': JSON.stringify(METADATA.ENV),
'HMR': METADATA.HMR,
}
}),
new UglifyJsPlugin({
// beautify: true, //debug
// mangle: false, //debug
// dead_code: false, //debug
// unused: false, //debug
// deadCode: false, //debug
// compress: {
// screw_ie8: true,
// keep_fnames: true,
// drop_debugger: false,
// dead_code: false,
// unused: false
// }, // debug
// comments: true, //debug
beautify: false, //prod
mangle: {
screw_ie8 : true,
keep_fnames: true
}, //prod
compress: {
screw_ie8: true
}, //prod
comments: false //prod
}),
new CompressionPlugin({
regExp: /\.css$|\.html$|\.js$|\.map$/,
threshold: 2 * 1024
})
],
tslint: {
emitErrors: true,
failOnHint: true,
resourcePath: 'src'
},
htmlLoader: {
minimize: true,
removeAttributeQuotes: false,
caseSensitive: true,
customAttrSurround: [
[/#/, /(?:)/],
[/\*/, /(?:)/],
[/\[?\(?/, /(?:)/]
],
customAttrAssign: [/\)?\]?=/]
},
node: {
global: 'window',
crypto: 'empty',
process: false,
module: false,
clearImmediate: false,
setImmediate: false
}
});
加上後,我們就可以 npm run build:prod 來產生正式的檔案 ( 會放在 dist 下 )
然後就可以使用 npm run server:prod 來啟動 Server
參考資料
- https://angular.io/docs/ts/latest/quickstart.html
- https://github.com/angularclass/angular2-webpack-starter
- [https://github.com/angular/angular/issues/5755]((https://github.com/angular/angular/issues/5755)
- https://github.com/typings/typings
- http://www.typescriptlang.org/docs/handbook/tsconfig.json.html
- https://github.com/s-panferov/awesome-typescript-loader
- https://github.com/ampedandwired/html-webpack-plugin