tip
webpack的入門篇可以看我的這一片博文。
《如何使用webpack—webpack-howto》.
前言
最近一段時間在專案中使用了webpack和React來開發,總之來說也是遇到了許多坑,webpack畢竟還是比較新的技術,而且也很難有一個很好的構建案例來適應所有的專案,總之,在看了許多專案demo和官方文件以及官方推薦的tutorials之後,也算是自己總結出的一套最佳實踐吧。
程式碼
package.json 命令配置
既然是需要用到的是實際專案的構建,那麼必然就要考慮開發環境和生產環境下的配置項了:
// package.json
{
// ...
"scripts": {
"build": "webpack --progress --colors --watch",
"watch": "webpack-dev-server --hot --progress --colors",
"dist": "NODE_ENV=production webpack --progress --colors"
},
// ...
}
可以在目錄下執行
npm run build
npm run watch
npm run dist
解釋一下:
build 是在我們開發環境下執行的構建命令;
watch 也是在開發環境下執行,但是加了webpack最強大的功能--搭建靜態伺服器和熱插拔功能(這個在後面介紹;
dist 是專案在要部署到生產環境時打包釋出。
dist 裡面的NODE_ENV=production
是宣告瞭當前執行的環境是production-生產環境
後面跟著幾個命令:
--colors 輸出的結果帶彩色
--progress 輸出進度顯示
--watch 動態實時監測依賴檔案變化並且更新
--hot 是熱插拔
--display-error-details 錯誤的時候顯示更多詳細錯誤資訊
--display-modules 預設情況下 node_modules 下的模組會被隱藏,加上這個引數可以顯示這些被隱藏的模組
-w 動態實時監測依賴檔案變化並且更新
-d 提供sorcemap
-p 對打包檔案進行壓縮
目錄結構
現在前端模組化的趨勢導致目錄結構也發生了很大的改變和爭議,這只是我自己用到的一種形式,可以參考。
.
├── app #開發目錄
| ├──assets #存放靜態資源
| | ├──datas #存放資料 json 檔案
| | ├──images #存放圖片資原始檔
| | └──styles #存放全域性sass變數檔案和reset檔案
| ├──lib
| | ├──components #存放資料 模組元件 檔案
| | | └──Header
| | | ├──Header.jsx
| | | └──Header.scss
| | |
| | └──utils #存放utils工具函式檔案
| |
| └──views
| ├──Index #入口檔案
| | ├──Index.html #html檔案
| | ├──Index.jsx
| | └──Index.scss
| └──Index2
├── dist #釋出目錄
├── node_modules #包資料夾
├── .gitignore
├── .jshintrc
├── webpack.config.js #webpack配置檔案
└── package.json
具體可以到Github上看demo。
webpack.config.js
引入包
var webpack = require('webpack');
var path = require('path');
var fs = require('fs');
這個毋庸置疑吧。
判斷是否是在當前生產環境
定義函式判斷是否是在當前生產環境,這個很重要,一位開發環境和生產環境配置上有一些區別
var isProduction = function () {
return process.env.NODE_ENV === 'production';
};
宣告資料夾
// 定義輸出資料夾
var outputDir = './dist';
// 定義開發資料夾
var entryPath = './app/views';
定義外掛
var plugins = [
new webpack.optimize.CommonsChunkPlugin({
name: 'commons',
filename: 'js/commons.js',
}),
new webpack.ProvidePlugin({
React: 'react',
ReactDOM: 'react-dom',
reqwest: 'reqwest',
}),
];
if( isProduction() ) {
plugins.push(
new webpack.optimize.UglifyJsPlugin({
test: /(\.jsx|\.js)$/,
compress: {
warnings: false
},
})
);
}
CommonsChunkPlugin 外掛可以打包所有檔案的共用部分生產一個commons.js檔案。
ProvidePlugin 外掛可以定義一個共用的入口,比如下面加的 React ,他會在每個檔案自動require了react,所以你在檔案中不需要
require('react')
,也可以使用 React。如果是在生產環境下,則加入外掛 UglifyJsPlugin ,執行程式碼壓縮,並且去除 warnings。
自動遍歷多檔案入口
var entris = fs.readdirSync(entryPath).reduce(function (o, filename) {
!/\./.test(filename) &&
(o[filename] = './' + path.join(entryPath, filename, filename + '.jsx'));
return o;
}, {}
);
函式會自動遍歷開發的入口資料夾下面的檔案,然後一一生產入口並且返回一個物件--入口。
如果在這一步不需要多頁面多入口
那麼可以使用html-webpack-plugin外掛,它可以自動為入口生成一個html檔案,配置如下:
var HtmlWebpackPlugin = require('html-webpack-plugin');
plugins.push(new HtmlWebpackPlugin({
title: 'index',
filename: outputDir+'/index.html', #生成html的位置
inject: 'body', #插入script在body標籤裡
}));
entry 就可以自定義一個入口就夠了
config的具體配置
var config = {
target: 'web',
cache: true,
entry: entris,
output: {
path: outputDir,
filename: 'js/[name].bundle.js',
publicPath: isProduction()? 'http://******' : 'http://localhost:3000',
},
module: {
loaders: [
{
test: /(\.jsx|\.js)$/,
loaders: ['babel?presets[]=es2015&presets[]=react'],
exclude: /node_modules/
},
{
test: /\.scss$/,
loaders: ['style', 'css?root='+__dirname, 'resolve-url', 'sass']
},
{
test: /\.json$/,
loader: 'json',
},
{
test: /\.(jpe?g|png|gif|svg)$/,
loader: 'url?limit=1024&name=img/[name].[ext]'
},
{
test: /\.(woff2?|otf|eot|svg|ttf)$/i,
loader: 'url?name=fonts/[name].[ext]'
},
{
test: /\.html$/,
loader: 'file?name=views/[name].[ext]'
},
]
},
plugins: plugins,
resolve: {
extensions: ['', '.js', 'jsx'],
},
devtool: isProduction()?null:'source-map',
};
這裡來一一說明:
對於output
path和filename都不用多說了,path是生成檔案的存放目錄,filename是檔名,當然可以在前面加上目錄位置。
這裡提醒一下,filename 的相對路徑就是 path了,並且下面 靜態檔案生成的filename也是相對於這裡的path的,比如 image 和 html。
publicPath 的話是打包的時候生成的檔案連結,比如 圖片 資源,
如果是在生產環境當然是用伺服器地址,如果是開發環境就是用本地靜態伺服器的地址。
module loaders 打包載入的處理器
可以不用夾 loader了 比如 原來 url-loader 現在 url
js/jsx
{
test: /(\.jsx|\.js)$/,
loaders: ['babel?presets[]=es2015&presets[]=react'],
exclude: /node_modules/
},
對於js檔案和jsx檔案用了babel來處理,這裡注意一下,最新版本的babel吧es2015和react的處理分開了,所有要這麼寫。
處理scss檔案
{
test: /\.scss$/,
loaders: ['style', 'css?root='+__dirname, 'resolve-url', 'sass']
},
這裡用了sass、css、style的loader這不用多說了。
那麼root和resolve-url是怎麼回事呢,root是定義了scss檔案裡面宣告的url地址是相對於根目錄的,然後resolve-url回去相對解析這個路徑,而不用require去獲取,比如
background: url('./assets/images/webpack.png');
這樣就可以載入到./assets/images/webpack.png
這個檔案,而不用使用相對路徑和require
處理json檔案
{
test: /\.json$/,
loader: 'json',
},
對於json檔案,可以自動請求該模組並且打包。
處理 圖片 字型 資原始檔
{
test: /\.(jpe?g|png|gif|svg)$/,
loader: 'url?limit=1024&name=img/[name].[ext]'
},
{
test: /\.(woff2?|otf|eot|svg|ttf)$/i,
loader: 'url?name=fonts/[name].[ext]'
},
這裡使用了 url 這個loader,但是url依賴 file-loader,它是對file-loader的二次封裝。
在請求圖片的時候如果檔案大小小於 1024k ,使用內聯 base64 URLs,否則會自動匯入到name所宣告的目錄,這裡是相對之前宣告的 outputDir 路徑。
字型資源也是一樣。
處理html檔案
{
test: /\.html$/,
loader: 'file?name=views/[name].[ext]'
},
在多頁面的專案中需要,可以自動吧html檔案匯入到指定的生產資料夾下。
resolve
resolve: {
extensions: ['', '.js', 'jsx'],
},
是可以忽略的檔案字尾名,比如可以直接require('Header');
而不用加.jsx。
devtool
devtool: isProduction()?null:'source-map',
規定了在開發環境下才使用 source-map。
疑問
目前為止,對於多頁面專案還是沒有找到一個很好的方案去構建自動化。
原文可以看我的部落格 webpack-best-practice-最佳實踐-部署生產;