本文github倉庫: "https://github.com/Rynxiao/webpack2 learn" 從v1遷移到v2 1. 配置類型 在webpack1的時候,主要是通過導出單個 來進行配置。例如下麵的配置: 而在webpack2中,則有三種方式來靈活配置,可以針對不同的場景。 1.1 ...
本文github倉庫:https://github.com/Rynxiao/webpack2-learn
從v1遷移到v2
1. 配置類型
在webpack1的時候,主要是通過導出單個object
來進行配置。例如下麵的配置:
// webpack1 導出方式
module.export = {
entry : 'app.js',
output : { */... */},
/* ... */
};
而在webpack2中,則有三種方式來靈活配置,可以針對不同的場景。
1.1 通過不同環境變數導出不同的配置文件
// 可以有兩種方式傳遞當前值,一種是簡單傳遞字元串,另外一種則是傳遞一個對象
// 例如: webpack --env production 控制台列印的就是 'production',是一個字元串
// 而當這樣調用時:webpack --env.production --env.size 60,控制台列印的就是 { production : true, size : 60 }
var path = require('path'),
webpack = require('webpack'),
UglifyJsPlugin = new webpack.optimize.UglifyJsPlugin(),
plugins = [];
module.exports = function(env) {
console.log(env);
if (env === 'production') {
plugins.push(UglifyJsPlugin);
}
return {
entry : path.resolve(__dirname, 'js/app.js'),
output : {
path : path.resolve(__dirname, 'build'),
filename : '[name].bundle.js'
},
module : {
rules : [
{
test : /\.js|\.jsx$/,
loader : 'babel-loader',
options : {
presets : ["es2015", "react"]
}
},
{
test : /\.css$/,
use : ['style-loader', 'css-loader']
},
{
test : /\.less$/,
use : ['style-loader', 'css-loader', 'less-loader']
}
]
},
plugins : plugins
};
}
// 在package.json中配置兩個命令
{
"dev" : "webpack",
"build" : "webpack --env production"
}
具體的生產環境構建方式可以參看官網production
1.2 通過promise方式導出配置文件
這種方式的應用場景是在某些情況下,我們暫時拿不到配置文件所需要的配置參數,比如需要配置的文件名等等,或許這是一個非同步的操作,通過promise方式可以使我們在非同步操作之後得到配置變數,然後再執行配置文件。
// 在這種情況下,1秒之後會返回配置文件並且執行
var path = require('path');
module.exports = () => {
return new Promise((resolve, reject) => {
console.log('loading configuration ...');
setTimeout(() => {
console.log('loading completed!');
resolve({
entry : path.resolve(__dirname, 'js/app.js'),
output : {
path : path.resolve(__dirname, 'build'),
filename : '[name].bundle.js'
},
module : {
rules : [
{
test : /\.js|\.jsx$/,
loader : 'babel-loader',
options : {
presets : ["es2015", "react"]
}
},
{
test : /\.css$/,
use : ['style-loader', 'css-loader']
},
{
test : /\.less$/,
use : ['style-loader', 'css-loader', 'less-loader']
}
]
},
});
}, 1000);
});
}
1.3 同時打包多份配置文件
webpack1時只能導出單份配置文件,在webpack2中可以同時打包多份配置文件,意味著可以為多個入口文件打包,在多頁面打包的時候,就再也不需要為在每一個單獨的頁面執行打包命令了。
// config-amd.js
var path = require('path');
module.exports = {
entry : path.resolve(__dirname, 'js/app.js'),
output : {
path : path.resolve(__dirname, 'build'),
filename : '[name].amd.js',
libraryTarget : 'amd'
},
module : {
rules : [
{
test : /\.js|\.jsx$/,
loader : 'babel-loader',
options : {
presets : ["es2015", "react"]
}
},
{
test : /\.css$/,
use : ['style-loader', 'css-loader']
},
{
test : /\.less$/,
use : ['style-loader', 'css-loader', 'less-loader']
}
]
}
};
// config-commonjs.js
var path = require('path');
module.exports = {
entry : path.resolve(__dirname, 'js/app.js'),
output : {
path : path.resolve(__dirname, 'build'),
filename : '[name].commonjs.js',
libraryTarget : 'commonjs'
},
module : {
rules : [
{
test : /\.js|\.jsx$/,
loader : 'babel-loader',
options : {
presets : ["es2015", "react"]
}
},
{
test : /\.css$/,
use : ['style-loader', 'css-loader']
},
{
test : /\.less$/,
use : ['style-loader', 'css-loader', 'less-loader']
}
]
}
};
// webpack.config.js
var configAmd = require('./config-amd.js'),
configCommonjs = require('./config-commonjs.js');
module.exports = [
configAmd,
configCommonjs
]
2. resolve相關
2.1 extensions 尾碼擴展
在webpack2中,不需要預設寫一個空字元串,如果沒有配置這個選項,則預設的尾碼名是['.js', '.json']
,這樣可以在需要用到import 'some.js'
的時候直接寫import 'some'
就好。
如果不想開啟自動尾碼,則需要在resolve
中配置enforceExtension : true
,例如:
var path = require('path');
module.exports = {
entry : // ....,
// ...
resolve : {
enforceExtension : true
}
};
此時,如果在js/app.js
中引用js/text.js
,就會報錯
// Error
import './text';
// Right
import './text.js';
2.2 root/fallback/modulesDirectories 文件定位
在webapck1 resolve
中配置這三個屬性,是告訴webpack在引入模塊的時候必須要尋找的文件夾,webpack2中則直接更換成了一個單獨的屬性modules
,預設優先搜索node_modules
(註意,這是一個相對位置)
// config
resolve: {
// root : path.join(__dirname, "src") webpack1方式
modules : [
path.join(__dirname, "src"), // 優先於node_modules/搜索
"node_modules"
]
}
// 修改 js/app.js
// 在js文件夾中,增加一個lodash.js,如果按照上面的配置了modules,則會優先載入我們自己的lodash庫
import '../css/style.less';
import _ from 'lodash';
console.log(_.isObject([1, 2, 3]));
document.getElementById('container').textContent = 'APP';
// js/lodash.js
export default {
isObject(a) {
console.log('this is my lodash library!');
return a && typeof a === 'object';
}
}
得到的結果如下圖:
3. module相關
3.1 module.rules替換module.loaders
The old loader configuration was superseded by a more powerful rules system, which allows configuration of loaders and more. For compatibility reasons, the old
module.loaders
syntax is still valid and the old names are parsed. The new naming conventions are easier to understand and are a good reason to upgrade the configuration to usingmodule.rules
.
大意就是新的命名更容易理解(反正對於我來說就是換了個英文單詞:-D),同時還會相容老的方式,也就是說,你照樣寫module.loaders
還是可以的。
module : {
// webpack1 way
// loaders : [...]
// now
rules : [
...
]
}
3.2 module[*].loader寫法
如果需要載入的模塊只需要一個loader
,那麼你還是可以直接用loader
這個關鍵詞;如果要載入的模塊需要多個loader
,那麼你需要使用use
這個關鍵詞,在每個loader
中都可以配置參數。代碼如下:
module : {
rules : [
{ test : /\.js|\.jsx$/, loader : 'babel-loader' },
/* 如果後面有參數需要傳遞到當前的loader,則在後面繼續加上options關鍵詞,例如:
{
test : /\.js|\.jsx$/,
loader : 'babel-loader',
options : { presets : [ 'es2015', 'react' ] }
}
*/
{
test : /\.css$/,
// webpack1 way
// loader : 'style!css'
use : [ 'style-loader', 'css-loader' ]
},
{
test : /\.less$/,
use : [
'style-loader', // 預設相當於 { loader : 'style-loader' }
{
loader : 'css-loader',
options : {
modules : true
}
},
'less-loader'
]
}
]
}
3.2 取消自動添加-loader
尾碼
之前寫loader通常是這樣的:
loader : 'style!css!less'
// equals to
loader : 'style-loader!css-loader!less-loader'
都自動添加了-loader
尾碼,在webpack2中不再自動添加,如果需要保持和webpack1相同的方式,可以在配置中添加一個屬性,如下:
module.exports = {
...
resolveLoader : {
moduleExtensions : ["-loader"]
}
}
// 然後就可以繼續這樣寫,但是官方並推薦這樣寫
// 不推薦的原因主要就是為了照顧新手,直接寫會讓剛接觸的童鞋感到困惑
// github.com/webpack/webpack/issues/2986
use : [ 'style', 'css', 'less' ]
3.3 json-loader內置啦
如果要載入json
文件的童鞋再也不需要配置json-loader
了,因為webpack2已經內置了。
4. plugins相關
4.1 UglifyJsPlugin 代碼壓縮插件
壓縮插件中的warnings
和sourceMap
不再預設為true,如果要開啟,可以這樣配置
plugins : [
new UglifyJsPlugin({
souceMap : true,
warnings : true
})
]
4.2 ExtractTextWebapckPlugin 文本提取插件
主要是寫法上的變動,要和webpack2配合使用的話,需要使用version 2版本
// webpack1 way
modules : {
loaders : [
{
test : /\.css$/,
loader : ExtractTextPlugin.extract('style-loader', 'css-loader', { publicPath : '/dist' })
}
]
},
plugins : [
new ExtractTextPlugin('bunlde.css', { allChunks : true, disable : false })
]
// webapck2 way
modules : {
rules : [
{
test : /\.css$/,
use : ExtractTextPlugin.extract({
fallback : 'style-loader',
use : 'css-loader',
publicPath : '/dist'
})
}
]
},
plugins : [
new ExtractTextPlugin({
filename : 'bundle.css',
disable : false,
allChunks : true
})
]
5. loaders的debug模式
在webpack1中要開啟loaders的調試模式,需要載入debug
選項,在webpack2中不再使用,在webpack3或者之後會被刪除。如果你想繼續使用,那麼請使用以下寫法:
// webpack1 way
debug : true
// webapck2 way
// webapck2將loader調試移到了一個插件中
plugins : [
new webpack.LoaderOptionsPlugin({
debug : true
})
]
6. 按需載入方式更改
6.1 import()方式
在webpack1中,如果要按需載入一個模塊,可以使用require.ensure([], callback)
方式,在webpack2中,ES2015 loader定義了一個import()
方法來代替之前的寫法,這個方法會返回一個promise.
// 在js目錄中新增一個main.js
// js/main.js
console.log('main.js');
// webpack1 way
require.ensure([], function(require) {
var _ = require('./lodash').default;
console.log(_);
console.log('require ensure');
console.log(_.isObject(1));
});
// webpack2 way
// 採用這種方式,需要promise 的 polyfill
// 兩種方式:
// 1. npm install es6-promise --save-dev
// require('es6-promise').polyfill();
//
// 2. babel方式,在webpack中配置babel插件
// npm install babel-syntax-dynamic-import --save-dev
// options : {
// presets : ['es2015'],
// plugins : ['syntax-dynamic-import']
// }
import('./lodash').then(module => {
let _ = module.default;
console.log(_);
console.log('require ensure');
console.log(_.isObject(1));
});
會得到的chunk文件,如下圖:
6.2 動態表達式
可以動態的傳遞參數來載入你需要的模塊,例如:
function route(path, query) {
return import(`./routes/${ path }/route`)
.then(route => { ... })
}
7. 熱替換更加簡單
webpack2中提供了一種更簡單的使用熱替換功能的方法。當然如果要用node啟動熱替換功能,依然可以按照webpack1中的方式。
npm install webpack-dev-server --save-dev
// webpack.config.js
module.exports = {
// ...,
devServer : {
contentBase : path.join(__dirname, 'build'),
hot : true,
compress : true,
port : 8080,
publicPath : '/build/'
},
plugins : [
new webpack.HotModuleReplacementPlugin()
]
}
談談V2版本
主要是介紹之前在webpack1中忽略的以及v2版本中新加的一些東西。
1. caching(緩存)
瀏覽器為了不重覆載入相同的資源,因此加入了緩存功能。通常如果請求的文件名沒有變的話,瀏覽器就認為你請求了相同的資源,因此載入的文件就是從緩存裡面拿取的,這樣就會造成一個問題,實際上確實你的文件內容變了,但是文件名沒有變化,這樣還是從緩存中載入文件的話,就出事了。
那麼,之前傳統的做法就是給每個文件打上加上版本號,例如這樣:
app.js?version=1
app.css?version=1
每次變動的時候就給當前的版本號加1,但是如果每次只有一個文件內容變化就要更新所有的版本號,那麼沒有改變的文件對於瀏覽器來說,緩存就失效了,需要重新載入,這樣就很浪費了。那麼,結合數據摘要演算法,版本號根據文件內容生成,那麼現在的版本可能是這樣的。
// before
app.js?version=0add34
app.css?version=1ef4a2
// after
// change app.js content
app.js?versoin=2eda1c
app.css?version=1ef4a2
關於怎麼部署前端代碼,可以查看大公司怎樣開發和部署前端代碼
webpack為我們提供了更簡單的方式,為每個文件生成唯一的哈希值。為了找到對應的入口文件對應的版本號,我們需要獲取統計信息,例如這樣的:
{
"main.js": "main.facdf96690cca2fec8d1.js",
"vendor.js": "vendor.f4ba2179a28531d3cec5.js"
}
同時,我們結合html-webpack-plugin
使用的話,就不需要這麼麻煩,他會自動給文件帶上對應的版本。具體看法參看之前寫的webpack1知識梳理,那麼我們現在的配置變成了這個樣子:
npm install webpack-manifest-plugin --save-dev
// webpack.config.js
module.exports = {
entry : { /* ... */ },
output : {
path : path.resolve(__dirname, 'build-init'),
filename : '[name].[chunkhash].js',
chunkFilename : '[name].[chunkhash].js'
},
module : {
// ...
},
plugins : [
new htmlWebpackPlugin({
title : 'webpack caching'
}),
new WebpackManifestPlugin()
]
}
html引入情況
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>webpack caching</title>
</head>
<body>
<div id="container"></div>
<script type="text/javascript" src="main.facdf96690cca2fec8d1.js"></script><script type="text/javascript" src="vendor.f4ba2179a28531d3cec5.js"></script></body>
</html>
WARNNING:
不要在開發環境下使用[chunkhash],因為這會增加編譯時間。將開發和生產模式的配置分開,併在開發模式中使用[name].js的文件名,在生產模式中使用[name].[chunkhash].js文件名。
為了使文件更小化,webpack使用標識符而不是模塊名稱,在編譯的時候會生成一個名字為manifest的chunk塊,並且會被放入到entry中。那麼當我們更新了部分內容的時候,由於hash值得變化,會引起manifest塊文件重新生成,這樣就達不到長期緩存的目的了。webpack提供了一個插件ChunkManifestWebpackPlugin
,它會將manifest映射提取到一個單獨的json文件中,這樣在manifest塊中只需要引用而不需要重新生成,所以最終的配置是這樣的:
var path = require('path'),
webpack = require('webpack'),
htmlWebpackPlugin = require('html-webpack-plugin'),
ChunkManifestWebpackPlugin = require('chunk-manifest-webpack-plugin'),
WebpackChunkHash = require('webpack-chunk-hash');
module.exports = {
entry : {
main : path.resolve(__dirname, 'js/app.js'),
vendor : path.resolve(__dirname, 'js/vendor.js')
},
output : {
path : path.resolve(__dirname, 'build'),
filename : '[name].[chunkhash].js',
chunkFilename : '[name].[chunkhash].js'
},
module : {
// ...
},
plugins : [
new webpack.optimize.CommonsChunkPlugin({
name : ['vendor', 'manifest'],
minChunks : Infinity
}),
new webpack.HashedModuleIdsPlugin(),
new WebpackChunkHash(),
new htmlWebpackPlugin({
title : 'webpack caching'
}),
new ChunkManifestWebpackPlugin({
filename : 'chunk-mainfest.json',
manifestVariable : 'webpackManifest',
inlineManifest : true
})
]
}
tips:如果還不是很明白,去對比一下加了ChunkManifestWebpackPlugin
和沒加的區別就可以清楚的感受到了。在本文的代碼文件夾caching
中可以看到這一差別