字數:3700css
閱讀時間:15分鐘html
環境:webpack3.8.1前端
前段時間,使用webpack查閱資料時發現要麼是入門級文章,要麼是如何優化打包速度或如何使用某個plugin、loader的文章。找不到一個在真實項目中的使用webpack的完整方案用以參考,因此花了許多精力去整合資料、查看代碼和踩坑。node
所以我將本身摸索的一個配置方案,分享出來,但願能提供一點借鑑。不足之處,歡迎大夥指正。jquery
說明一下,本文旨在講述思路和遇到的問題,不會涉及到基礎的講解。若是想了解基礎,這裏能夠給大夥推薦兩個很是好的入門資料:webpack
入門Webpack,看這篇就夠了:初步地瞭解webpack的用法已經簡單地練手。git
webpack中文文檔:這一版的官方文檔,相對以前你們詬病已久的文檔混亂問題有了很大的改善,最好不過的學習資料了。angularjs
爲增長代碼的可讀性和維護性,我將配置拆分爲如下五個配置文件:github
webpack.common.config.js | 公共配置 |
---|---|
webpack.dev.config.js | 開發環境配置 |
webpack.prod.config.js | 生產環境配置 |
webpack.dll.config.js | 公共庫配置 |
webpack.alias.js | 模塊地址配置 |
爲提高打包效率,我會將一些變化較小的代碼和第三方庫都打包成公共庫,webpack.dll.config.js
就是打包公共庫的配置文件,若是其中的內容沒有變化,以後的打包不會再處理這些文件了,極大地增長了打包效率。若是使用了較多第三方庫,強烈建議使用這種方式打包。web
由於開發環境配置和生產環境配置有許多差別,所以分別作配置,分別對應着webpack.dev.config.js
和webpack.prod.config.js
配置文件。而後提取其中的公共部分,這就是咱們的公共配置文件webpack.common.config.js
。
最後,筆者對每個模塊都作了別名配置,以解耦代碼對代碼目錄的依賴,對應着咱們的webpack.alias.js
配置文件。
下面,咱們就一塊兒探討一下這五個配置文件的具體內容。
公共配置,先上代碼:
const wepackMerge = require('webpack-merge');
const Path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const Webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
const GTLoaderFilesPlugin = require('./plugins/gt-file-loader-plugin');
const ProdConfig = require('./webpack.prod.config');
const DevConfig = require('./webpack.dev.config');
const alias = require('./webpack.alias');
const dlls = require('./webpack.dll.config');
//根據條件處理相關配置
const genarateConfig = env => {
//樣式loader
let cssLoader = [{
loader: 'css-loader',
options: {
sourceMap: true
}
}, {
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
require('postcss-cssnext')()
],
sourceMap: true
}
}, {
loader: 'less-loader',
options: {
sourceMap: true
}
}];
let styleLoader = [{
test: /\.(css|less)$/,
use: env === 'prod' ? ExtractTextPlugin.extract({
fallback: 'style-loader',
use: cssLoader
}) : [{
loader: 'style-loader',
options: {
sourceMap: true
}
}].concat(cssLoader)
}];
//腳本loader
let jsLoader = [{
test: /\.js$/,
exclude: /(node_modules|bower_components|libs)/,
use: [{
loader: 'babel-loader'
}].concat(env === 'dev' ? [{
loader: 'eslint-loader'
}] : [])
}];
//文件處理loader
let fileLoaderOptions = {
useRelativePath: false,
name: '[name]-[hash:5].[ext]'
};
if (env === 'prod') {
fileLoaderOptions.limit = 10000;
}
let fileLoader = [{
test: /\.(jpg|jpeg|png|icon)$/,
use: [{
loader: env === 'dev' ? 'file-loader' : 'url-loader',
options: env === 'dev' ? fileLoaderOptions : Object.assign({}, fileLoaderOptions, {
outputPath: '../dist/img'
})
}]
}, {
//解析字體文件
test: /\.(eot|svg|ttf|woff2?)$/,
use: [{
loader: env === 'dev' ? 'file-loader' : 'url-loader',
options: env === 'dev' ? fileLoaderOptions : Object.assign({}, fileLoaderOptions, {
outputPath: '../dist/fonts'
})
}]
}, {
//解析主頁面和頁面上的圖片
test: /\.html$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src', 'img:data-src'],
minimize: true
}
}
}];
//webpack插件
let plugins = [];
//組織第三方庫插件
for (let key in dlls.entry) {
//組織DllReferencePlugin
let dllPlugin = new Webpack.DllReferencePlugin({
manifest: require('../dll/manifest/' + key + '.manifest.json')
});
plugins.push(dllPlugin);
}
//加載js
plugins.push(new AddAssetHtmlPlugin({
filepath: Path.join(__dirname, '../dll/*.js'),
hash: true,
includeSourcemap: false,
publicPath: './dll/',
outputPath: '../dist/dll/'
}));
//加載css
plugins.push(new AddAssetHtmlPlugin({
filepath: Path.join(__dirname, '../dll/*.css'),
hash: true,
typeOfAsset: 'css',
includeSourcemap: false,
publicPath: './dll/',
outputPath: '../dist/dll/'
}));
//入口html插件
plugins.push(new HtmlWebpackPlugin({
template: Path.join(__dirname, '../src/control.html'),
filename: 'index.html',
inject: true,
chunks: ['vendor', 'example']
}));
//拷貝文件
plugins.push(new CopyWebpackPlugin([{
// 第三方的字體文件
from: './dll/fonts',
to: '../dist/fonts'
}, {
//表單頁面文件
from: './src/form/core/views',
to: '../dist/core-views'
}, {
//表單頁面文件
from: './src/form/office/views',
to: '../dist/office-views'
}], {
ignore: ['**/.svn/**']
}));
//友好提示插件
plugins.push(new FriendlyErrorsPlugin());
//不打包默認加載項
plugins.push(new Webpack.IgnorePlugin(/^\.\/locale$/, /moment$/));
//將加載項寫入loader.js中
plugins.push(new GTLoaderFilesPlugin());
let config = {
devtool: 'source-map',
output: {
path: Path.join(__dirname, '../dist/'),
filename: env === 'dev' ? '[name]-[hash:5].bundle.js' : '[name]-[chunkhash:5].bundle.js'
},
module: {
rules: [].concat(styleLoader).concat(jsLoader).concat(fileLoader)
},
plugins: plugins,
resolve: {
alias: alias
}
};
return config;
};
module.exports = env => {
let config = env === 'dev' ? DevConfig : ProdConfig;
let result = wepackMerge(genarateConfig(env), config);
return result;
};
複製代碼
開發環境和生產環境皆使用這個配置文件執行webpack,經過執行CLI命令時傳入的環境變量來區分。開發環境傳入dev
,生產環境傳入prod
,藉助於npm的scripts命令,可更便利地實現,代碼以下:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --env prod --config build/webpack.common.config.js",
"server": "webpack-dev-server --env dev --config build/webpack.common.config.js --open",
"dll": "webpack --config build/webpack.dll.config.js"
}
複製代碼
運行npm run server
就能夠打開開發環境了。
公共配置中,生成配置代碼以下:
module.exports = env => {
let config = env === 'dev' ? DevConfig : ProdConfig;
let result = wepackMerge(genarateConfig(env), config);
return result;
};
複製代碼
使用webpack-merge插件,根據傳入的參數將不一樣的配置與公共配置進行融合。注意,因爲loader是倒序執行的,因此loader相關的配置沒法使用這個方式融合,只能在代碼中自行處理。這裏genarateConfig
就是處理公共配置的函數。
生產環境:less-loader→postcss-loader→css-loader→extract-text-webpack-plugin
開發環境:less-loader→postcss-loader→css-loader→style-loader
less-loader:webpack自帶loader,預編譯less文件,將less語法編譯成css語法。
postcss-loader:webpack自帶loader,css轉換工具,自動添加瀏覽器前綴、壓縮css、支持將來語法。
css-loader:webpack自帶loader,編譯css。
extract-text-webpack-plugin:webpack自帶插件,extract()
函數會返回一個loader,輸出獨立樣式文件,通常用於生產環境。
style-loader:webpack自帶loader,將樣式以style標籤的方式直接插入到html文檔中。
注意: postcss-loader中引入postcss-cssnext模塊,能夠支持將來語法。全部loader設置sourceMap: true
才能在控制檯看到樣式源碼。
生產環境:babel-loader
開發環境:eslint-loader→babel-loader
eslint-loader:webpack自帶loader,須要依賴Eslint工具,作靜態代碼檢查只用。能夠配合webpack-dev-server的overlay使用。
babel-loader:編譯js,兼容新特性。
生產環境:html-loader→url-loader
開發環境:html-loader→file-loader
html-loader:webpack自帶loader,編譯html文件,將其中加載的圖片等資源做爲模塊處理。
url-loader:webpack自帶loader,解析圖片、字體等文件資源,能夠將超過限制的資源解析成base64編碼字符串,達到減小請求數的優化效果。
file-loader:webpack自帶loader,同url-loader,只是不會將文件解析成base64編碼字符串。
DllReferencePlugin:webpack自帶插件,配合公共庫webpack.dll.config.js
使用。
add-asset-html-webpack-plugin:在生成的html頁面中自動插入資源,這裏使用它引入了公共庫中的js和css資源。
html-webpack-plugin:根據模板生成html文件,默認支持ejs模板語法。須要注意:與html-loader共用時,因爲html-loader會先將html文件編譯成字符串,從而致使ejs語法解析失效。我使用的解決方案以下:全部使用到ejs語法的後綴改成.ejs,其中加載的圖片等資源文件手動加載模塊。例:<img src="${require('./assets/img/6.jpg')}" alt="">
。而後html-loader不解析以ejs爲後綴的文件。
copy-webpack-plugin:webpack自帶插件,用以複製文件,主要複製不做爲模塊引入的資源文件,例如:一些圖片字體等文件,不必編譯,直接複製過來打包速度更快。
friendly-errors-webpack-plugin:友好提示插件,CLI中提示信息可視化更加友好。若是使用 git bash 或者mac 的 Terminal 則不必安裝該插件。
IgnorePlugin:webpack自帶插件,不打包默認加載項,webpack會默認打包locale、moment等模塊,若是項目不須要,可使用該插件屏蔽。
GTLoaderFilesPlugin:這是我自定義的資源加載插件,可忽略。
context:配置entry和loader的參考路徑。
resolve.alias:模塊別名配置,配合webpack.alias.js使用
開發環境配置,先上代碼:
const Webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
entry: {
example: './examples/index.js'
},
devServer: {
port: '9091',
overlay: true,
//設置爲false則會在頁面中顯示當前webpack的狀態
inline: true,
historyApiFallback: true,
//代理配置
proxy: {
},
hot: true
//強制頁面不經過刷新頁面更新文件
// hotOnly: true
},
plugins: [
//分析插件
// new BundleAnalyzerPlugin(),
//模塊熱更新插件
new Webpack.HotModuleReplacementPlugin(),
//使用HMR時顯示模塊的相對路徑
new Webpack.NamedModulesPlugin()
]
};
複製代碼
devServer:配置webpack自帶的服務器,以做調試只用。須要安裝webpack-dev-server插件,注意,只能安裝V3以前的版本,V3版本是兼容webpack4的,沒法在webpack3中使用。
webpack-bundle-analyzer:第三方的分析插件,能夠對打包結果進行分析。也可使用官方的分析方案:結合插件stats-webpack-plugin生成的分析結果文件和官方提供的在線工具官方工具來分析打包結果。
HotModuleReplacementPlugin:webpack自帶工具,模塊熱更新必須插件。
NamedModulesPlugin:webpack自帶插件,用模塊的路徑命名模塊,運行結果更清晰。不使用這個插件,webpack就會默認使用一個隨機ID命名。利於調試,官方推薦開發環境須要使用的插件。
生產環境配置,先上代碼:
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const Webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const ZipPlugin = require('zip-webpack-plugin');
const StatsPlugin = require('stats-webpack-plugin');
const SvnInfo = require('svn-info').sync('https://218.106.122.66/svn/framework/trunk/gt-ui', 'HEAD');
const Path = require('path');
const pkg = require('../package.json');
module.exports = {
entry: {
frame0: 'frame',
frame2: 'frame2',
frame3: 'frame3',
center1: 'center1',
center2: 'center2',
center3: 'center3',
login1: 'login1',
login2: 'login2',
form: 'form',
example: './examples/index.js'
},
plugins: [
//模塊分析頁面
// new BundleAnalyzerPlugin(),
new Webpack.optimize.CommonsChunkPlugin({
names: ['vendor'],
minChunks: 2
}),
//混淆代碼
new UglifyJsPlugin({
sourceMap: true,
//多線程處理
parallel: true,
//使用緩存
cache: true
}),
//提取css文件
new ExtractTextPlugin({
filename: '[name]-[hash:5].css'
}),
new CleanWebpackPlugin(['dist', 'package'], {
root: Path.join(__dirname, '../')
}),
new Webpack.NamedChunksPlugin(),
new Webpack.NamedModulesPlugin(),
//版本信息
new Webpack.BannerPlugin({
banner: `Name: ${pkg.name}\nSVNVersion: ${SvnInfo.revision}\nDate: ${new Date().toISOString().slice(0, 10)}\nDescription: ${pkg.description}`,
raw: false,
entryOnly: true,
include: /\.js/g
}),
//分析結果
new StatsPlugin('../stats.json', {
chunkModules: true,
exclude: [/node_modules/]
}),
//複製文檔頁面
new CopyWebpackPlugin([{
// 第三方的字體文件
from: './examples',
to: '../dist/examples'
}, {
//表單頁面文件
from: './docs',
to: '../dist/docs'
}], {
ignore: ['**/.svn/**']
}),
//打包生成包的主頁
new HtmlWebpackPlugin({
template: Path.join(__dirname, '../src/index.html'),
filename: '../index.html',
inject: true
}),
//壓縮文件夾
new ZipPlugin({
filename: 'gt-ui.zip',
path: '../package/',
pathPrefix: 'dist'
})
],
profile: true
};
複製代碼
CommonsChunkPlugin:webpack自帶插件,提取多個入口中公共代碼,webpack最開始的核心優點codesplting的實現之一。
uglifyjs-webpack-plugin:代碼壓縮混淆插件,開啓多線程和緩存能夠加快打包速度。
clean-webpack-plugin:清空文件夾插件,每次打包前先清空以前打包殘留文件。
NamedChunksPlugin:webpack自帶插件,開發環境配置中有說過,這裏旨在長效緩存之用。若是不使用這個插件,webpack生成的隨機ID會致使最終生成的代碼文件對應的hash值變化,致使長效緩存失效。
NamedModulesPlugin:同上,這裏做用於chunk依賴的模塊。
BannerPlugin:webpack自帶插件,在代碼中添加代碼段,如代碼版本、版權等信息。
svn-info:獲取當前代碼的SVN信息。
stats-webpack-plugin:生成打包分析文件,以在官方提供的在線析工具上使用。
zip-webpack-plugin:將打包結果壓縮成壓縮包。在使用這個插件時,遇到了該插件的執行順序錯誤,致使打包失敗的問題。相同的配置,有時該插件會先於其餘插件執行,出現須要壓縮的文件還沒生成致使打包中斷問題,有時卻不會。查了一些資料,發現除非是插件自己處理了執行順序的問題,不然webpack的插件的執行順序實際上是不定的(略坑,相比而言gulp就要肯定地多)。這裏有一個替代插件,filemanager-webpack-plugin 。
公共庫配置,先上代碼:
const Path = require('path');
const Webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const alias = require('./webpack.alias');
module.exports = {
entry: {
ngs: ['angular', 'angular-resource', 'angular-sanitize', '@uirouter/angularjs',
'angular-animate', 'angular-touch', 'angular-cookies'
],
ngui: ['jquery', 'sweetalert', 'datetimepickerCN', 'datetimepicker', 'angular-loading-bar', 'angular-strap', 'angular-ui-grid', 'ui-select',
'angular-ui-tour', 'angular-ui-tree', 'angular-validation', 'angular-carousel'
],
base: ['babel-polyfill', 'lodash']
},
output: {
path: Path.join(__dirname, '../dll'),
filename: '[name].dll.js',
library: '[name]'
},
resolve: {
alias: alias
},
plugins: [
new Webpack.DllPlugin({
path: Path.join(__dirname, '../dll/manifest/', '[name].manifest.json'),
name: '[name]'
}),
new CopyWebpackPlugin([{
from: './src/libs/bootstrap-datetimepicker-master/css/bootstrap-datetimepicker.min.css'
}, {
from: './node_modules/angular-loading-bar/build/loading-bar.css'
}, {
from: './node_modules/ui-select/dist/select.css'
}, {
from: './node_modules/angular-ui-tree/dist/angular-ui-tree.min.css'
}, {
from: './node_modules/angular-carousel/dist/angular-carousel.min.css'
}])
]
};
複製代碼
這裏,爲了加快打包速度,我將一些不須要編譯的文件直接用拷貝插件拷貝,用加載到代碼中。
DllPlugin :webpack自帶插件,生成庫定義文件的,配合DllReferencePlugin使用。
模塊別名配置,先上代碼:
var Path = require('path');
module.exports = {
api: Path.join(__dirname, '../src/common/api'),
//自定義控件
ngControl: Path.join(__dirname, '../src/custom/controls/control'),
//框架
frame: Path.join(__dirname, '../src/custom/frame/frame'),
frame1: Path.join(__dirname, '../src/custom/frame/frame1/frame'),
frame2: Path.join(__dirname, '../src/custom/frame/frame2/frame'),
frame3: Path.join(__dirname, '../src/custom/frame/frame3/frame'),
login1: Path.join(__dirname, '../src/custom/login/login1/login'),
login2: Path.join(__dirname, '../src/custom/login/login2/login'),
center1: Path.join(__dirname, '../src/custom/system-center/center1/system-center'),
center2: Path.join(__dirname, '../src/custom/system-center/center2/system-center'),
center3: Path.join(__dirname, '../src/custom/system-center/center3/system-center'),
frameManager: Path.join(__dirname, '../src/custom/frame-manager')
};
複製代碼
這裏面就是配置不一樣模塊對應的具體文件地址,以便維護。
咱們的構建配置是分爲兩大類的:框架配置和應用系統配置。以上是前端框架的構建配置,應用系統的構建配置我在下一篇文章和大夥分享!