This section contain the following items:
3.Webpack preloaders, loaders, postloaders
6.Webpack & React Hot Reloader
1.Webpack Howto
以下參考自:
1.What is webpack?
(1)可做到bowswerify可做到的, 但是可以分散封裝專案使用的程式碼,使載入頁面時只需載入當頁所需的程式碼以加速載入速度,以下兩行指令的效果是相同的:
browserify main.js > bundle.js
webpack main.js bundle.js
Webpack 提供比 browserify 更多的功能,一般來說會創建一個名為 webpack.config.js 的檔案來做集中管理:
// webpack.config.js
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
}
};
(2)用來取代grunt&gulp, 因為webpack可以build跟bundle css, preprocess css, compile-to-JS languages and images, among other things.
(3)其他還有:
->可同時整合 CommonJS 和 AMD 模組
->轉換 JSX, Coffee Script, TypeScript 等
->整合樣式表 (css, sass, less 等)
->建置 production-ready 的程式碼 (壓縮)
2.How to invoke webpack?
切換到有 webpack.config.js 檔案的目錄下並執行:
webpack : 會在開發模式下開始一次性的建置
webpack -p : 會建置 production-ready 的程式碼 (壓縮)
webpack --watch : 會在開發模式下因應程式碼的變換持續更新建置 (快速!)
webpack -d : 加入 source maps 檔案
3.Compile-to-JS languages:
webpack與browserify transforms及RequireJS pluguns相同作用的是loaders, 以下示範如何使用webpackload Coffeescript, 以及Facebook的JSX+ES6 (必須先npm install babel-loader coffee-loader,npm install babel-core babel-preset-es2015 babel-preset-react):
(1)先建置webpack.config.js, 並調整loaders的內容:
// webpack.config.js
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.coffee$/, loader: 'coffee-loader' },
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react']
}
}
]
}
};
(2)如果需要require檔案而不指定特定的副檔名, 可以加入resolve.extensions:
// webpack.config.js
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.coffee$/, loader: 'coffee-loader' },
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react']
}
}
]
},
resolve: {
// you can now require('file') instead of require('file.coffee')
extensions: ['', '.js', '.json', '.coffee']
}
};
4.Stylesheets and images:
(1)首先更新你的程式碼去require你的static assets:
require('./bootstrap.css');
require('./myapp.less');
var img = document.createElement('img');
img.src = require('./glyph.png');
(2)當require css(或less等)時, webpack中CSS是可以被require()的,webpack 會自動生成一個 <style> 標籤並加入到 html的<head>中; 當require image, webpack將URL放到bundle中, 被require時再return回來, 但是必須給他指定loaders:
// webpack.config.js
module.exports = {
entry: './main.js',
output: {
path: './build', // This is where images AND js will go
publicPath: 'http://mycdn.com/', // This is used to generate URLs to e.g. images
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.less$/, loader: 'style-loader!css-loader!less-loader' }, // use ! to chain loaders
{ test: /\.css$/, loader: 'style-loader!css-loader' },
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' } // inline base64 URLs for <=8k images, direct URLs for the rest
]
}
};
5.Feature flags (特定功能標籤)
(1)當想要在特定環境(dev)下,可先在code中定義全域變數
if (__DEV__) {
console.warn('Extra logging');
}
// ...
if (__PRERELEASE__) {
showSecretFeature();
}
(2)然後在webpack.config.js中定義不同環境時的行為
// webpack.config.js
// definePlugin 接收字串,因此你也可以直接寫入字串而不使用 JSON.stringify()
var definePlugin = new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV || 'true')),
__PRERELEASE__: JSON.stringify(JSON.parse(process.env.BUILD_PRERELEASE || 'false'))
});
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
plugins: [definePlugin]
};
(3)設定完成後, 可以使用 BUILD_DEV=1 BUILD_PRERELEASE=1 webpack 來完成建置。調整指令的參數即可模擬各狀況下的建置。特別注意的是, webpack -p在壓縮時會將沒用到的程式碼刪除, 因此不用擔心會有不該出現的程式碼被加入到最終產出的程式碼內。
6.Multiple entrypoints (分批進入)
(1)如果你有profile page 及 feed page, 如果使用者只想要profile而不希望下載feed的code, 可以建置不同的bundle:
// webpack.config.js
module.exports = {
entry: {
Profile: './profile.js',
Feed: './feed.js'
},
output: {
path: 'build',
filename: '[name].js' // Template based on keys in entry above
}
};
(2)For profile, insert <script src="build/Profile.js"></script> into your page. Do a similar thing for feed.
7.Optimizing common code
(1)Feed與Profile間有許多共同的code, webpack分析出共通處並額外整合成一組在頁面間可快取的共用包:
// webpack.config.js
var webpack = require('webpack');
var commonsPlugin =new webpack.optimize.CommonsChunkPlugin('common.js');
module.exports = {
entry: {
Profile: './profile.js',
Feed: './feed.js'
},
output: {
path: 'build',
filename: '[name].js'
// [name] 會依據上面 entry 的屬性名稱變動
},
plugins: [commonsPlugin]
};
(2)Add <script src="build/common.js"></script> before the script tag you added in the previous step and enjoy the free caching.
8.Async loading
2.Webpack.config.js
以下以https://github.com/preboot/angular-webpack中的範例來分析:
2.1 設定不同的環境變數: test, build(production)
利用定義NODE_ENV變數, 此部份可參考npm life cycle (https://docs.npmjs.com/misc/scripts#current-lifecycle-event)來區分dev時或是production時應該做什麼事,例如在命令列下輸入:
1.npm run build: run build scripts
2.npm run test: run test server
3.npm run start: run production server
設定可以在package.json中設定:
{
"scripts": {
"build": "rimraf dist && webpack --bail --progress --profile",
"server": "webpack-dev-server --history-api-fallback --inline --progress",
"test": "karma start",
"test-watch": "karma start --auto-watch --no-single-run",
"start": "npm run server"
}
}
行為則在Webpack.config.js中定義:
取得環境變數
var ENV = process.env.npm_lifecycle_event;
var isTest = ENV === 'test' || ENV === 'test-watch';
var isProd = ENV === 'build';
reference:
2.2 Export config
module.exports = function makeWebpackConfig () {
var config = {};
return config;
}();
config的內容大致如下:
1.entry: webpack的進入點:
example: 設定進入點為./src/app/app.js
config.entry = isTest ? {} : {
app: './src/app/app.js'
};
2.output: 設定打包後的輸出檔
example:
config.output = isTest ? {} : {
// 輸出檔的輸出路徑
path: __dirname + '/dist',
// require()時參考的路徑,例如圖片的路徑
// 在dev環境下, 使用webpack-dev-server
publicPath: isProd ? '/' : 'http://localhost:8080/',
// 輸出的bundle檔名
// build mode下加入hash
filename: isProd ? '[name].[hash].js' : '[name].bundle.js',
// 未被列在entry裡, 卻又希望被輸出的budle
// build mode下加入hash
chunkFilename: isProd ? '[name].[hash].js' : '[name].bundle.js'
};
3.devtool
4.module:
preloaders, loaders, postloaders
example:
preloaders: 圖片壓縮
loaders:配置文件, 如coffie-script轉換成js
postloaders: 代碼覆蓋率測試等
5.plugins
6.devServer:
contentBase: 啟動webpack-dev-server的路徑
example:
config.devServer = {
contentBase: './src/public',
stats: 'minimal'
};
3.Webpack preloaders, loaders, postloaders
1.preloaders,
2.loaders
1.babel loader
2.css loader
3.file loader
4.raw-loader
5.ngtemplate-loader: https://github.com/WearyMonkey/ngtemplate-loader
6.raw-loader: https://github.com/webpack/raw-loader
4.Webpack Plugins
Extract Text Plugin:
webpack中CSS是可以被require()的,webpack 會自動生成一個style標籤並加入到 html的head中
ExtractTextPlugin可輸出打包成一包的css, 一般來說會用在production的時候
example:
new ExtractTextPlugin('[name].[hash].css', {disable: !isProd})
HtmlWebpackPlugin: 可以動態組成index.html
example:
new HtmlWebpackPlugin({
template: './src/public/index.html',
inject: 'body'
})
template: 動態產生檔案所存放的位置
CopyWebpackPlugin:
copies individual files or entire directories to the build directory.
UglifyJsPlugin
5.Webpack & Angular
5.1 Environment
5.2 Webpack.config.js
(1)Add 2 things into webpack.config.js: 'use strict'; var webpack = require('webpack'); (2)Add 3 thing in module.exports = {}: (2.1) Entry: 告訴webpack打包的進入點 (2.2) Output: filename: 指定webpack打包後所產出的檔案名稱 path: 指定webpack打包後所產出的檔案的路徑 (2.3) Module: loaders: 指定Loader的種類 (3)Add loaders to webpack.config.js (4)Example: var webpack = require('webpack');
module.exports = {
entry: [
'./Angular/app.js'
],
output: {
path: __dirname + '/public',
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.js$/, loader: 'babel', exclude: [/node_modules/] },
{ test: /\.html$/, loader: 'html-loader', exclude: [/node_modules/] },
{ test: /\.css$/, loader: "style!css", exclude: [/node_modules/] },
{ test: /\.scss$/, loader: "style!css!sass", exclude: [/node_modules/] }
]
}
};
5.3 Directive and Webpack
6.Webpack & React Hot Reloader
1.Introduction
雖然使用webpack有很多優點, 但比較不便利的地方是更新完必須重新build bundle.js, 再重新啟動node.js, 重新reload, 造成開發上的不方便.
Hot Reloader的目的在於一旦有更新檔案, Webpack可以自己重build, Browser可接到訊息並reload.
要讓Webpack做到Hot Reload有兩種方式:
一種是使用webpack-dev-server, 可以提供獨立的standalone server.
另一種則是使用webpack提供給express整合的middleware: webpack-dev-middleware及webpack-hot-middleware, 本段所記錄的是使用這種方式.
2.Install packages
需要的套件有webpack, webpack-dev-middleware, webpack-hot-middleware, react-hot-loader, react, babel-loader:
npm install webpack webpack-dev-middleware, webpack-hot-middleware react-hot-loader react babel-loader
1.react, babel-loader便不多作解釋
2.webpack-dev-middleware, webpack-hot-middleware:讓我們用 express 客製一個有熱替換功能的 webpack 開發伺服器
3.react-hot-loader:可以在不改變 React 元件的 state 下,將更改過程式碼的元件直接更新到畫面上
3.Webpack.config.js
要修改的地方有五處: cache, entry, output, plugins, module
2.entry: 加入'webpack-hot-middleware/client', 'webpack/hot/dev-server
entry: [
'./js/mainentry.js', 'webpack-hot-middleware/client', 'webpack/hot/dev-server'
],
3.output: 加入publicPath
var publicPath = 'http://localhost:3000/assets';
output: {
path: __dirname + '/assets',
publicPath: publicPath,
filename: 'mainbundle.js'
},
4.plugins: 修改如下:
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
],
5.module:
module: {
loaders: [{
test: /\.jsx?$/, // Match both .js and .jsx files
loaders: ['react-hot', 'babel?presets[]=es2015,presets[]=react']
}]
}
4.index.dev.js
Create一個index.dev.js: 使用webpack-dev-middleware及webpack-hot-middleware,以及導入webpack.config.js
var webpack = require('webpack'),
webpackDevMiddleware = require('webpack-dev-middleware'),
webpackHotMiddleware = require('webpack-hot-middleware'),
webpackDevConfig = require('./webpack.config.js');
var compiler = webpack(webpackDevConfig);
function useWebpackMiddleware(app) {
app.use(webpackDevMiddleware(compiler, {
// public path should be the same with webpack config
publicPath: webpackDevConfig[0].output.publicPath,
noInfo: true,
hot: true,
inline: true,
stats: {
colors: true
}
}));
app.use(webpackHotMiddleware(compiler, {
log: console.log
}));
return app;
}
module.exports = {
useWebpackMiddleware: useWebpackMiddleware
};
5.app.js
目的是分為develope版本及production版本, 當環境變數不是production時,就會切到index.dev.js
var webpackDevHelper = require('./index.dev.js');
if (process.env.NODE_ENV !== 'production') {
console.log('DEVOLOPMENT ENVIRONMENT: Turning on WebPack Middleware...');
webpackDevHelper.useWebpackMiddleware(app);
} else {
console.log('PRODUCTION ENVIRONMENT');
}
6.package.json
在package.json的scripts中放入dev, production兩個script
production: 將環境變數改為production, 並執行node.js
dev: 如果mainbundle.js存在則刪除, 並執行node.js
"scripts": {
"production": "export NODE_ENV=production && webpack && node app.js",
"dev": "rm -f ./assets/mainbundle.js && node app.js"
},
7.index.html
記得埋入script source
<script src="./assets/mainbundle.js"></script>