本篇文章解答了60多道關於webpack
的問題,答案有詳細有簡單,時間關係,有的問題還未作出解答,後續會補上,持續更新。若是答案有不對的地方,請輕拍。javascript
全局安裝webpack
,意味着webpack
包被安裝到npm的全局包目錄裏了。
查看npm
全局包位置:css
npm root -g
複製代碼
附加點npm
的知識:
html
查看npm
配置信息:vue
npm config ls
複製代碼
查看npm
下載源:java
npm config get registry
複製代碼
查看npm
安裝目錄:node
npm config get prefix
複製代碼
這裏假設經過命令npm config get prefix
獲取到的路徑爲C:\Users\wz\AppData\Roaming\npm
,爲了方便起見,用A
代替C:\Users\wz\AppData\Roaming
經過這個目錄能夠獲取到如下信息:
react
npm
全局配置文件(A\npm\etc
)A\npm\node_modules
)A\npm
)A\npm-cache
)本地安裝webpack
,webpack
包被安裝到了項目根目錄下的node_modules
裏了。jquery
開發依賴,指的是隻有在項目開發、編譯、打包、壓縮等過程纔會用到的包,一旦文件成功產出,就再也不須要他們了,好比:less-loader
、webpack
等等。webpack
項目依賴,指的是從開始編寫項目到項目上線之後都須要的包,好比:vue
、axios
等等。ios
須要安裝:
webpack
(webpack
核心包)webpack-cli
(webpack
命令行工具,依賴於webpack
核心包)webpack
默認配置文件:
第一種:webpack.config.js
第二種:webpackfile.js
webpack
源碼中和配置相關的代碼,位置:webpack/bin/convert-argv.js
//...
//從這裏能夠看出webpack的2中配置文件
var defaultConfigFiles = ["webpack.config", "webpackfile"].map(function(filename) {
return extensions.map(function(ext) {
return {
path: path.resolve(filename + ext),
ext: ext
};
});
}).reduce(function(a, i) {
return a.concat(i);
}, []);
//...
複製代碼
爲何會在瀏覽器裏執行,就要從webpack
打包後的文件中去找答案了。
搭建一個簡單的項目,只安裝webpack
這個包,項目目錄以下:
index.js
以下:
function foo() {
console.log(window.localStorage)
}
module.exports = { foo }
複製代碼
webpack.config.js
以下:
module.exports = {
mode:'development',
entry: './src/index.js',
output: {
path: require('path').resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
}
複製代碼
在命令行執行webpack
命令後,打包後的文件內容,通過刪減,整體"框架"代碼以下:
(function (modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports;
}
return __webpack_require__("./src/index.js");
})({
"./src/index.js": (function (module, exports) {
eval(/* ... */)
})
})
複製代碼
分析以上代碼:
一、整體是一個自執行函數,雙括號形式。
(function(modules){/* ... */})({})
複製代碼
一個自執行函數固然能夠在瀏覽器中執行了。
二、自執行函數的函數體內,定義了模塊緩存對象installedModules
,定義了模塊加載方法__webpack_require__
,這個__webpack_require__
方法就是爲瀏覽器量身打造的,做用至關於node
中的require
方法。__webpack_require__
方法的邏輯也不難理解,首先經過模塊名(其實就是一個路徑)去模塊緩存對象上查找,找不到的話,就新建一個模塊module
並緩存到installedModules
上,而後經過模塊名找到對應的模塊函數,執行它,並將module
等參數傳入,最後返回模塊導出對象module.exports
。這段代碼建議仔細看看
三、自執行函數的參數。該參數是一個對象,相似下面這樣:
{
"./src/index.js": (function (module, exports) {eval(/* ... */)})
}
複製代碼
該對象的鍵是一個路徑字符串,其實就是咱們調用require方法時傳入的模塊路徑; 值爲一個接收
module
和exports
參數的函數,函數體內是一個包裹着一堆字符串代碼的eval
函數,這一堆字符串代碼就是咱們寫的代碼。可見,webpack
爲了讓咱們的代碼可以在瀏覽器裏執行,作了多少工做。
webpack
有幾種模式,對應模式的做用webpack
有3種模式:
development
production
none
簡單來講,不一樣的模式下,webpack會啓用不一樣的插件,或者不一樣的優化手段。
development
模式下,會啓用如下插件:
production
模式下,會啓用如下插件:
none
模式下,不會啓動任何插件。 詳情可參考這篇文章
package.json
中如何配置在package.json
文件中的script
字段裏配置,以下,咱們配置3條命令,分別爲dev
、pro
、start
:
{
"name": "l",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev":"", //dev命令
"pro":"", //pro命令
"start":"" //start命令
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.35.3"
}
}
複製代碼
配置完成後,就能夠在項目的命令行裏執行如下命令了:
npm run dev
npm run pro
npm run start
複製代碼
webpack-dev-server
的啓動目錄修改和webpack-dev-server
有關配置的contentBase
選項。
let path = require('path')
module.exports = {
mode:'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename:'bundle.js'
},
devServer: {
port: 8888,//監聽的端口號
progress: true,//啓動打包進度顯示
contentBase: path.join(__dirname, 'dist'),//這裏指定啓動目錄
compress: true //啓動壓縮
}
}
複製代碼
關於webpack-dev-server
的更多配置,點擊這裏
從打包的過程也能夠看出webpack-dev-server
的啓動目錄,以下:
使用html-webpack-plugin
插件,配置以下:
plugins: [
new HtmlWebpackPlugin({
template: './index1.html',
filename: 'main.html',
minify: {
collapseWhitespace: true,//移除空格
removeAttributeQuotes:true//移除屬性的雙引號
}
})
]
複製代碼
官方說若是模式爲production
的話,minify
選項會被默認設置成true
,產出的HTML
文件會自動壓縮,你們能夠試試,我嘗試的不行。
minify
的配置選項有不少,感興趣能夠點這裏查看更多。
其實,html-webpack-plugin
能夠壓縮HTML
文件,內部是依賴的是這個庫html-minifier
,這個壓縮HTML
的庫也能夠這樣使用:
var minify = require('html-minifier').minify;
var result = minify('<p title="blah" id="moo">foo</p>', {
removeAttributeQuotes: true
});
result; // '<p title=blah id=moo>foo</p>'
複製代碼
關於html-minifier
的更多信息,這裏
JavaScript
文件解決方法:
第一種:
讓文件名稱帶有hash
字符串,這樣每次打包js
文件時,只有內容有變化,hash
字符串就會發生變化,好比下面:
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash:8].bundle.js'
},
複製代碼
這裏打包後的文件名稱爲main.d3a5dd20.bundle.js
。
第二種:
html-webpack-plugin
的配置項hash
置爲true
,這樣打包後的js
文件在插入html
文件後會以?
開始添加hash
字符串。以下:
plugins: [
new HtmlWebpackPlugin({
template: './index1.html',
filename: 'main.html',
hash:true
})
]
複製代碼
這裏打包後的文件名稱以下:
<body>
<h1>This is a title</h1>
<script type="text/javascript" src="main.js?d3a5dd204b4d1b64170c"></script>
</body>
複製代碼
loader
的做用是對源代碼進行轉換,webpack
只認識js
、json
2種文件,其餘類型的文件,好比css
、img
、less
等等,只能依靠loader
去解析轉換了。
官方解釋,這裏
css-loader
主要處理css
文件中@import
和url()
語法的。官方文檔
style-loader
主要做用是將css
樣式以style
標籤的形式插入到頁面中。官方文檔
loader
要返回js
腳本loader
只作一件事情,爲了使loader
在更多場景下鏈式調用loader
都是一個node
模塊loader
有同步的,也有異步的loader
有兩個執行階段,pitch
、normal
官網文檔
loader
的執行順序比較講究, 以下圖所示:
配置文件寫法、行內loader
寫法、命令行寫法3種
配置文件寫法:
就是將配置信息寫到webpack.config.js
,寫法又有如下幾種:
loader
module.exports={
module:{
rules:[
{
test: /.js$/,
loader: 'my-loader',
exclude: /node_modules/
},
]
}
}
複製代碼
use
,字符串形式module.exports={
module:{
rules[
{
test: /.js$/,
use: 'my-loader',//直接傳遞字符串
exclude: /node_modules/
},
]
}
}
複製代碼
use
,對象形式module.exports={
module:{
rules[
{
test: /.js$/,
use: { //對象形式,能夠給loader傳遞參數
loader:'my-loader',
options:{}//這裏傳遞參數給loader
}
exclude: /node_modules/
},
]
}
}
複製代碼
use
,數組形式數組內的每一項能夠爲字符串,也能夠是對象。
module.exports = {
module: {
rules: [
{
test: /.js$/,
use: [
'my-loader1',//字符串形式
{ loader: 'my-loader2', options: {} }//對象形式
],
exclude: /node_modules/
},
]
}
}
複製代碼
行內loader
寫法:
多個loader
之間用!
分割。
let something=require('loader2!loader1!./profile.js')
複製代碼
行內loader
可添加前綴,表明當前文件是否交由其餘loader
處理:
-!
表示不會讓文件再去經過 pre+normal
loader
處理了!
表示不會讓normal
loader
處理了!!
該文件只會讓行內loader
處理let a = require('inline-loader!./a') // !分割,inline-loader就是行內loader
let a = require('-!inline-loader!./a') // -!表示不會讓文件再去經過 pre+normal loader處理了
let a = require('!inline-loader!./a') // ! 表示不會讓normal loader處理了
let a = require('!!inline-loader!./a') // !! 該文件只會讓行內loader處理
複製代碼
命令行寫法:
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
複製代碼
從右向左,從下到上
{
test: /\.js$/,
use: ['loader3', 'loader2', 'loader1']
}
複製代碼
以上loader
執行順序爲 loader1
---> loader2
--->loader3
{
test: /\.js$/,
use: {
loader:'loader3'
}
},
{
test: /\.js$/,
use: {
loader: 'loader2'
}
},
{
test: /\.js$/,
use: {
loader: 'loader1'
}
}
複製代碼
以上loader
執行順序爲 loader1
--->loader2
--->loader3
第一種:在配置文件中傳遞參數
module.exports={
module:{
rules[
{
test: /.js$/,
use: { //對象形式,能夠給loader傳遞參數
loader:'my-loader',
options:{}//這裏傳遞參數給loader
}
exclude: /node_modules/
},
]
}
}
複製代碼
第二種:行內loader
傳遞參數的方法
let foo = require('expose-loader?$!./a.js')
複製代碼
loader
的默認順序是從右向左,從下到上,不過能夠經過enforce
字段來打破這種順序。
enforce
有兩個取值:pre
表明第一個執行。post
表明最後一個執行
有以下loader
配置:
{
test: /.js$/,
use: 'loader1.js',
exclude: /node_modules/,
enforce: 'pre',//表明loader1首先被執行
},
{
test: /.js$/,
use: 'loader2.js',
exclude: /node_modules/
},
{
test: /.js$/,
use: 'loader3.js',
exclude: /node_modules/
},
{
test: /.js$/,
use: 'loader4.js',
exclude: /node_modules/,
enforce: 'post'//表明loader4最後被執行
}
複製代碼
若是沒有配置enforce
字段,執行順序爲:loader4--->loader3--->loader2--->loader1
若是配置了enforce
字段,執行順序爲:loader1--->loader3--->loader2--->loader4
注意:沒有配置enforce
字段的loader
默認爲normal
,按照默認順序執行.
若是文件在require
的時候用到了行內loader
的話,執行順序以下:
pre
--->normal
--->inline
--->post
思考中...
css
預處理器:less
、sass
、stylus
對應的loader
:less-loader
、sass-loader
、stylus-loader
css
樣式抽離安裝插件:
npm install mini-css-extract-plugin -D
複製代碼
配置文件:
{
module: {
rules: [
{
test: /\.css$/,
use: [{
loader: MiniCssExtractPlugin.loader,//使用插件loader
},
'css-loader'
]
},
]
},
plugins: [
//添加插件實例
new MiniCssExtractPlugin({
filename: 'index.css'
})
]
}
複製代碼
mini-css-extract-plugin
更多用法
安裝包:
npm install postcss-loader autoprefixer -D
複製代碼
項目根目錄下新建文件postcss.config.js
,內容以下:
module.exports = {
plugins: [require('autoprefixer')]
}
複製代碼
webpack.config.js
配置:
module: {
rules: [
{
test: /\.css$/,
use: [{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
'postcss-loader'//這裏加入了新的loader
],
include: path.join(__dirname, './src'),
exclude: /node_modules/
},
]
}
複製代碼
安裝包:
npm install optimize-css-assets-webpack-plugin -D
複製代碼
webpack.config.js
配置:
{
plugins: [
new OptimizeCSSAssetsPlugin()
]
}
複製代碼
安裝包:
npm install uglifyjs-webpack-plugin --save-dev
複製代碼
webpack.config.js
配置:
module.exports = {
optimization: {
minimizer: [ new UglifyJsPlugin() ],
},
};
複製代碼
或者:
{
plugins: [
new UglifyJsPlugin()
]
}
複製代碼
uglifyjs-webpack-plugin
官方文檔
npm install @babel/core @babel/preset-env babel-loader -D
複製代碼
@babel/core
是核心包,提供基礎的API服務。babel-loader
依賴@babel/core
關於babel的知識,推薦閱讀這兩篇:
我是第一篇
我是第二篇
@babel/plugin-proposal-class-properties
是用來轉換class
語法的。好比:
以下語法:
class Bork {
static a = 'foo';
static b;
x = 'bar';
y;
}
複製代碼
會被轉換爲:
var Bork = function Bork() {
babelHelpers.classCallCheck(this, Bork);
this.x = 'bar';
this.y = void 0;
};
Bork.a = 'foo';
Bork.b = void 0;
複製代碼
官方文檔
@babel/plugin-proposal-decorators
是用來轉換修飾符(@
)的。
官方文檔
@babel/polyfill
:
babel
默認只轉換 js
語法,而不轉換新的 API,好比 Iterator
、Generator
、Set
、Maps
、Proxy
、Reflect
、Symbol
、Promise
等全局對象,以及一些定義在全局對象上的方法(好比 Object.assign
)都不會轉碼。
舉例來講,es2015
在 Array
對象上新增了 Array.from
方法。babel
就不會轉碼這個方法。若是想讓這個方法運行,必須使用 @babel/polyfill
。(內部集成了 core-js 和 regenerator) 使用時,在全部代碼運行以前增長 require('@babel/polyfill
')。
@babel/plugin-transform-runtime
依賴@babel/runtime
,也就是說:在使用@babel/plugin-transform-runtime
的時候必須把@babel/runtime
當作依賴。
推薦閱讀24題的兩篇文章。
可使用eslint
包對js
代碼進行校驗。
安裝包:
npm i eslint eslint-loader babel-eslint -D
複製代碼
新建eslint
配置文件.eslint.js
:
module.exports = {
root: true,
//指定解析器選項
parserOptions: {
sourecType: 'module'
},
//指定腳本執行的環境
env: {
browser: true
},
//啓用的規則及其各自的錯誤級別
rules: {
"semi": "error",//語句強制分號結尾
"indent": ["error",4],//縮進風格
"quotes":["error","double"]//引號類型
}
}
複製代碼
webpack.config.js
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'eslint-loader',
options: {
fix: true
},
},
include: [path.resolve(__dirname, 'src')],
exclude: /node_modules/,
}
]
}
複製代碼
eslint
官網文檔
暴露變量其實就是暴露到全局,也就是掛在到window
上。
第一種:
安裝包expose-loader
npm install expose-loader -D
複製代碼
在入口文件(webpack.config.js
中的entry
)中使用expose-loader
,這種方式屬於內聯loader
。
import $ from 'expose-loader?$!jquery'
console.log(window.$)
複製代碼
loader
和包隔開符號expose-loader
傳遞參數固然也能夠不使用內聯loader
:
第一步:入口文件正常引入:
import $ from 'jquery'
console.log(window.$)
複製代碼
第二步:在webpack.config.js
配置文件中配置
rules: [
{
test: require.resolve('jquery'),
use: 'expose-loader?$'
}
]
複製代碼
第二種:
在每一個模塊中注入$
,注意是每一個模塊都有一個$
,可是這個$
並非全局的。
在webpack.config.js
配置文件中配置
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
複製代碼
而後模塊中能夠直接使用$
第三種:
這種方式叫引入不打包。在html
文件中引入jquery
,這裏以jquery
的cdn
爲例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script src="../dist/bundle.js"></script>
</head>
<body>
</body>
</html>
複製代碼
注意:若是使用了這種方式,就不要在js文件內在引入jquery了,不然會重複打包。若是非要引入,那就修改一下webpack.config.js
:
module.exports = {
//...
externals: {
jquery: '$'//webpack打包時,會忽略掉jquery
},
//...
}
複製代碼
不會,官方文檔沒有明說,可是有句話的潛臺詞代表了不會。哪句話呢,友情提示:Whenever the identifier is encountered as free variable in a module...
參考官方文檔
先說一下,使用圖片的幾種方式。
使用方式:
安裝包file-loader
npm install file-loader -D
複製代碼
js
文件內:
import logo from '../logo.png'
//file-loader 默認會在內部生成一張圖片到build目錄下,被導入的圖片在js文件內是一個hash字符串
console.log(logo)//19470b4db4deed52a8ba081c816e8f0d.png
let image = new Image()
image.src = logo
複製代碼
webpack.config.js
文件內配上相應的loader
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader'
}
]
},
複製代碼
使用方式:
安裝包style-loader
、css-loader
,固然還能夠安裝css
預處理包
npm install style-loader css-loader -D
複製代碼
css文件內:
body{
background: url("../logo.png")
}
複製代碼
webpack.config.js
文件內配上相應的loader
module: {
rules: [
{
test: /\.css$/,
loader: 'style-loader!css-loader'
}
]
},
複製代碼
使用方式:
安裝包html-withimg-loader
npm install html-withimg-loader -D
複製代碼
html
文件內:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="../dist/bundle.js"></script>
</head>
<body>
<img src="./logo.png" alt="">
</body>
</html>
複製代碼
webpack.config.js
文件內配上相應的loader
module: {
rules: [
{
test: /\.html$/,
loader: 'html-withimg-loader'
},
]
},
複製代碼
使用html-withimg-loader
包來處理html的img。
安裝包url-loader
.
npm install url-loader -D
複製代碼
webpack.config.js
文件內配上相應的loader
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 200 * 1024
}
}
},
]
},
複製代碼
當圖片小於設定的大小(K)的時候,用base64
來轉化,不然用file-loader
產生真實的圖片。
他倆其實沒有必然聯繫,只能說能夠搭配起來一塊兒工做。url-loader
只能將圖片解析成base64
,當圖片大小超過了限制,url-loader
就會把解析圖片的工做交給其餘工具(默認是file-loader
),固然,當圖片大小超過了限制,而咱們想用其餘工具來處理圖片,能夠經過參數來控制:
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
fallback: 'responsive-loader',//當圖片大小超過8K,用responsive-loader處理
limit: 8*1024,
},
},
],
},
],
},
};
複製代碼
能夠參考篇幅不長的官方文檔
publicPath
指定的路徑會被做爲前綴添加到全部的url
上。這裏的url
指的是:
html
文件中的link
標籤,script
標籤、img
標籤css
中的帶有文件引入的屬性等。好比:background:url()
通常當靜態資源放在CDN時,publicPath
會指定CDN的路徑。
官方文檔
配置多頁面就須要配置webpack
的多入口。
module.exports = {
mode: 'development',
entry: {
main: './src/main.js',//入口1,main.js
index: './src/index.js',//入口2,index.js
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash:8].bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
chunks: 'main',//這裏的main和entry裏的main屬性須要保持一致
filename: 'index.html'
}),
new HtmlWebpackPlugin({
template: './index.html',
chunks: 'index',//這裏的index和entry裏的index屬性須要保持一致
filename: 'main.html'
})
]
}
複製代碼
思考中...
source-map
和eval-source-map
的區別這個配置主要是debug
用的,配置選項有不少,這裏挑選4個說明。
source-map
生成map
文件,定位到行列eval-source-map
不生成map
文件,定位到行列cheap-module-source-map
生成map
文件,定位到行cheap-module-eval-source-map
不生成map
文件,定位到行在webpack配置文件中新增以下配置信息:
module.exports = {
mode: 'development',
//開啓實時編譯
watch: true,
//實時編譯的配置選項
watchOptions: {
ignored: /node_modules/,
poll:1000,//每秒詢問文件變動的次數
aggregateTimeout:500//防止重複保存頻繁從新編譯,500毫秒內重複保存不打包
}
}
複製代碼
當檢測文件再也不發生變化,會先緩存起來,等待一段時間後,再通知監聽者,這個等待時間經過aggregateTimeout
配置。
bannerPlugin
的做用bannerPlugin
的做用是在產出的資源文件頭部添加信息,好比:添加做者、版本號等信息。
let webpack=require('webpack')
module.exports={
//...
plugins: [
new webpack.BannerPlugin('author:wangZhi')
],
//...
}
複製代碼
產出的文件頭部以下所示:
css
/*! author:wangZhi */
body{
background:red;
}
複製代碼
js
/*! author:wangZhi */
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
複製代碼
webpack
如何配置代理首先啓動一個web
服務,配置以下:
var path = require('path');
module.exports = {
//...
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000
}
};
複製代碼
而後配置代理:
proxy: {
'/api': {//以/api開頭的請求會被代理到'https://other-server.example.com'
target: 'https://other-server.example.com',
pathRewrite: {
'^/api': ''//以/api開頭的路徑會被替換成''
}
}
}
複製代碼
before
函數中的app
有什麼做用webpack-dev-server
是依靠express
啓動一個web
服務的,配置中的app
就是express
中的app
,有了app
,咱們能夠編寫接口,響應接口等,至關於前臺本身mock
一些數據。建議看看express
官網。
module.exports = {
//...
devServer: {
before: function(app, server) {
//編寫一個/some/path接口,後續請求會在這裏直接處理
app.get('/some/path', function(req, res) {
res.json({ custom: 'response' });
});
}
}
};
複製代碼
webpack-dev-middleware
做用是什麼服務端啓動webpack,webpack-dev-middleware
實際上是一個express
的中間件。
let webpack = require('webpack')
let express = require('express')
let config = require('./webpack.config')
let middle = require('webpack-dev-middleware')
let app = express();
//webpack提供的方法,傳入webpack配置,獲得一個編譯對象
let compiler = webpack(config);
//使用中間件
app.use(middle(compiler))
//監聽端口
app.listen(2000)
複製代碼
resolve
屬性有哪些配置列出幾個經常使用的配置:
module.exports = {
//...
resolve: {
//模塊查找路徑
modules: [path.resolve('node_modules'),'mydir'],
//配置別名
alias: {
bootstrap:'bootstrap/dist/css/bootstrap.css'
},
//查找字段
mainFields: ['main', 'style'],
//查找文件名
mainFiles: ['index.js'],
//查找文件的後綴名
extensions:['.js','.css','.vue']
},
};
複製代碼
見上44題。
見上44題。
定義環境變量須要用到webpack
的一個包--->definePlugin
。
module.exports = {
//...
plugins: [
new webpack.DefinePlugin({
DEV: 'dev',
DEV_str: JSON.stringfiy('dev'),
FLAG: 'true',
FLAG_str: "'true'",
expression: '1+1',
expression_str: JSON.stringify('1+1')
})
]
}
複製代碼
上述配置定義了6個環境變量,打包編譯後的結果爲:
dev is not defined
'dev'
(字符串)true
(布爾值)'true'
(字符串)2
(數字型)'1+1'
(字符串)第一種方式,能夠經過設置環境變量來區分,設置環境變量見上一題。
if(DEV==='development'){
//do something
}else if(DEV==='production'){
//do something
}
複製代碼
第二種方式,建立多套配置文件。
安裝包 webpack-merge
npm install --save-dev webpack-merge
複製代碼
項目目錄:
webpack-demo
|- package.json
|- webpack.common.js //開發環境、生產環境公用配置文件
|- webpack.dev.js //開發環境配置文件
|- webpack.prod.js //生產環境配置文件
|- /dist
|- /src
|- index.js
|- math.js
|- /node_modules
複製代碼
webpack.common.js
文件:
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js'
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Production'
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
複製代碼
webpack.dev.js
文件:
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
contentBase: './dist'
}
});
複製代碼
webpack.prod.js
文件:
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
});
複製代碼
參考官方文檔
webpack-merge
做用是什麼webpack-merge
的做用是合併對象。
最基本的用法:
let merge = require('webpack-merge')
let newObj = merge(obj1,obj2,obj3,...)
複製代碼
仍是看文檔吧
webpack
的配置文件中,經過配置externals
字段能夠達到不解析某些依賴庫的目的。以下:
module.exports = {
//...
externals: {
jquery: '$'//webpack打包時,會忽略掉jquery
},
//...
}
複製代碼
loader
的解析文件夾一、直接寫絕對路徑
{
test: /.js$/,
use: path.resolve(__dirname,'loader/loader1.js')
}
複製代碼
二、配置別名
resolveLoader: {
alias: {
loader1: path.resolve(__dirname, 'loader', 'loader1')
}
}
複製代碼
三、配置modules
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'loader')]
}
複製代碼
moment
後默認會引入locale
文件夾思考中...
dllPlugin
如何使用打包一個dll文件:
let path = require('path');
//引入插件,webpack內置插件
let DllPlugin = require('webpack/lib/DllPlugin')
module.exports = {
mode: 'development',
entry: {
//將react、react-dom庫打包成動態連接庫
react:['react','react-dom']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].dll.js',
//動態連接庫導出的全局變量
library: '_dll_[name]'
},
plugins: [
new DllPlugin({
//name需和output.library一致。
name: '_dll_[name]',
//生成的json文件存放目錄
path: path.join(__dirname, 'dist', '[name].manifest.json')
})
]
}
複製代碼
按照如上配置,咱們最終獲得了一個react.dll.js
文件,該文件內容通過刪減替換整理後,以下:
var _dll_react = (function (modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
return module.exports;
}
return __webpack_require__(0);
})({
"module1": (function (module, exports, __webpack_require__) { /* ... */}),
"module2": (function (module, exports, __webpack_require__) { /* ... */}),
"module3": (function (module, exports, __webpack_require__) { /* ... */}),
"module4": (function (module, exports, __webpack_require__) { /* ... */}),
0: (function (module, exports, __webpack_require__) {
eval("module.exports = __webpack_require__")
})
})
複製代碼
爲了直觀,我將源碼中的相似./node_modules/object-assign/index.js
這樣的屬性名替換成了module1
,module2
等等。
分析上述代碼,咱們能夠得知,_dll_react
變量其實就是__webpack_require__
方法。該方法接受一個模塊id
,返回該模塊的內容。
再來看一下,生成的react.manifest.json
文件,內容通過刪減整理以下:
{
"name": "_dll_react",
"content": {
"./node_modules/react-dom/index.js": {
"id": "./node_modules/react-dom/index.js",
"buildMeta": {
"providedExports": true
}
}
//...
}
}
複製代碼
content
對象裏的鍵名./node_modules/react-dom/index.js
就是模塊請求路徑,也就是說,當webpack
遇到了以下語句require('./node_modules/react-dom/index.js')
時,webpack
會拿着調用require
方法傳入的路徑去react.manifest.json
文件內的content
對象中找到鍵爲該路徑的屬性,而後webpack
就獲取到了該路徑對應的模塊內容。
若是咱們不使用動態連接庫,當webpack
遇到了以下語句require('./node_modules/react-dom/index.js')
時,webpack
會拿着調用require
方法傳入的路徑去獲取文件內容,而後拼接頭部信息(就是function (module, exports, __webpack_require__) {}
),而後遞歸解析文件內容的require語句,而後將依賴寫入依賴列表。
綜上所述,使用動態連接庫確實在打包速度上獲得了必定的提高。
使用一個dll文件相對來講就比較簡單了,按照格式寫就能夠了:
let path = require('path');
//引入插件,webpack內置插件
let DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
},
plugins: [
//使用動態連接庫
new DllReferencePlugin({
manifest:require('./dist/react.manifest.json')
})
]
}
複製代碼
安裝包happypack
。
npm install --save-dev happypack
複製代碼
webpack.config.js
配置文件以下:
module.exports = {
rules: [
{
test: /\.js$/,
use: 'happypack/loader?id=jsx'
},
{
test: /\.less$/,
use: 'happypack/loader?id=styles'
},
],
plugins: [
new HappyPack({
id: 'jsx',
threads: 4,
loaders: ['babel-loader']
}),
new HappyPack({
id: 'styles',
threads: 2,
loaders: ['style-loader', 'css-loader', 'less-loader']
})
]
}
複製代碼
happypack如何工做的呢,一圖勝千言:
tree-shaking
是否支持require
語法和有什麼做用不支持require
語法,依賴於ES2015模塊的靜態結構,好比:import
export
。 做用就是可以去除未用到的代碼。
官方文檔
scope hosting
的做用是什麼Scope Hoisting
可讓 Webpack
打包出來的代碼文件更小、運行的更快, 它又譯做 "做用域提高",是在 Webpack3
中新推出的功能。
在配置文件中添加一個新的插件,就能夠實現scope hosting
功能。
module.exports = {
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
}
複製代碼
參考文章
webpack
整個工做流程webpack
的打包過程比較複雜,這裏用一張圖簡述一下,權當拋磚引玉了。
webpack
中的HotModuleReplacement
原理。一、當文件發生變化後,webpack
會從新打包,打包完成後,發佈done
事件。
二、done
回調函數執行,經過服務端與客戶端創建的長鏈接發送hash
值到客戶端。
三、客戶端收到hash
值以後,確認是否要更新。若是更新,則會經過Ajax
去請求manifest.json
文件,該文件記錄了全部發生變更的模塊。
四、經過manifest.json
文件,客戶端使用jsonp
方式去拉取每個變更模塊的最新代碼。
五、客戶端更新模塊,加入了3個屬性:parents
、children
、hot
。
六、經過模塊id
找到父模塊中全部依賴該模塊的回調函數並執行。
七、頁面自動更新,熱替換完成。
以下圖所示:
webpack
代碼分割方式有哪些,其中import()
方法的原理是什麼。第一種:多入口
module.exports = {
entry: {
page1: './src/page1.js',
page2: './src/page2.js',
page3: './src/page3.js'
}
}
複製代碼
第二種:webpack內部配置項。
module.exports = {
optimization:{
splitChunks:{
cacheGroups:{
vendors:{
chunks:'initial',//指定分割的類型,默認3種選項 all async initial
name:'vendors',//給分割出去的代碼塊起一個名字叫vendors
test:/node_modules/,//若是模塊ID匹配這個正則的話,就會添加一vendors代碼塊
priority:-10 //優先級
},
commons:{
chunks:'initial',
name:'commons',
minSize:0,//若是模塊的大小大於多少的話才須要提取
minChunks:2,//最少最幾個chunk引用才須要提取
priority:-20
}
}
}
}
}
複製代碼
第三種:調用webpack
提供的import
方法。
假設,項目的入口文件是index.js
,內容以下:
let button = document.createElement('button');
button.innerHTML = '點我點我';
button.addEventListener('click',event=>{
debugger;
import('./hello.js').then(result=>{
alert(result.default);
});
});
document.body.appendChild(button);
複製代碼
入口文件index.js
被編譯後的代碼以下:
let button = document.createElement('button');
button.innerHTML = '點我點我';
button.addEventListener('click', event => {
__webpack_require__.e("src_hello_js.js").then(__webpack_require__.t.bind(__webpack_require__, "./src/hello.js")).then(result => {
alert(result.default);
});
});
document.body.appendChild(button);
複製代碼
看重點代碼import('./hello.js')
被編譯成了__webpack_require__.e("src_hello_js.js").then(__webpack_require__.t.bind(__webpack_require__, "./src/hello.js"))
,./hello.js
會被看成爲一個入口文件,而後打包成一個獨立的文件,和項目入口文件index.js
打包出來的文件放在一塊兒。也就是說,咱們這裏的項目原本就一個入口文件,結果打包出來兩個文件,緣由就是使用了import
方法將代碼進行了分割。
__webpack_require__.e
方法代碼以下:
__webpack_require__.e = function (chunkId) {
return new Promise((resovle, reject) => {
installedChunks[chunkId] = resovle;
let script = document.createElement('script');
script.src = chunkId;
document.body.appendChild(script);
}).catch(error => {
alert('異步加載失敗');
});
}
複製代碼
能夠看到該方法主要功能就是根據傳入的chunkId
使用jsonp
去拉取對應的模塊代碼。這裏它返回了一個promise
,並將resolve
放到了全局installedChunks
對象上。由於這裏不能肯定jsonp
何時成功,因此沒法調用resolve
,只能將它掛載到全局變量中。那何時能肯定jsonp
成功了呢,答案是在jsonp
的回調函數裏能夠肯定,也就是下面的webpackJsonp
方法裏。
先來看看jsonp
拉取回來的代碼長什麼樣子吧,以下:
window.webpackJsonp("src_hello_js.js", {
"./src/hello.js": (function (module, exports, __webpack_require__) {
module.exports = 'hello';
}),
});
複製代碼
jsonp
標準格式,返回一個方法調用,參數就是響應的數據。
window.webpackJsonp
方法代碼以下:
window.webpackJsonp = (chunkId, moreModules) => {
for (moduleId in moreModules) {
modules[moduleId] = moreModules[moduleId];
}
installedChunks[chunkId]();//resolve()
installedChunks[chunkId] = 0;
}
複製代碼
webpackJsonp
主要工做就是將拉取回來的模塊一一掛載到全局的modules
對象中。而且改變對應的promise
狀態,也便是執行事先放在全局變量中的resolve
方法,這樣就會執行以後的then
方法了。
__webpack_require__.t
方法代碼以下:
__webpack_require__.t = function (value) {
value = __webpack_require__(value);
return {
default:
value
};
}
複製代碼
該方法就是簡單的去加載模塊,而後返回加載後的結果。
一圖勝千言: