动态

详情 返回 返回

Webpack入門之遇到的那些坑,系列示例Demo - 动态 详情

前言

2017/12/18/更新
已經有一段時間沒有更新webpack了,從現在的角度看,文章有一部分內容也是不夠成熟,後續還是會繼續梳理更新的。

網上關於webpack的教程已經數不勝數了,也無意再重新寫一篇複製文。但是實際操作過程中,發現各種教程版本都不一致,有的教程已經過時了,有的教程模糊不清,因此還是遇到了各種問題,因此特將自身實際操作過程中遇到的問題記錄下來,並附上相應的示例demo,也希望能給他人帶來幫助!

本文主要是記錄的一些遇到的問題以及提供了示例,如果想要看入門教程還是去官網上或者參考參考資源中的鏈接。

問題記錄較多,想要直接看示例Demo的請拉到最下方。

另外,此文會持續更新。

題綱

  • 遇到的那些問題
  • 示例,各階段的示例demo,配有README

遇到的那些問題

問題一:package.json註釋帶來的問題

説明:項目的package.json中,只要帶有註釋,必然編譯不能通過

解決:禁止在package.json中註釋

問題二:全局安裝帶來的問題

説明:在最開始做項目時,由於本地已經全局安裝過了對應的依賴包,因此沒有再重新在本地安裝,導致運行時報錯,全局安裝版本與本地依賴的版本不一致導致。

解決:每一個項目中,所有的依賴包均在本地安裝,依賴情況通過npm install *** --save -dev注入package.json中,其中一些環境以外的依賴,比如第三方庫express等可以通過npm install *** --save安裝。

所有的依賴包都在本地安裝是一個好習慣,因為這樣可以隨時打包帶走與重新裝箱。

問題三:css-loader省略-loader帶來的問題

説明:在老版本的webpack中,經常會用省略的寫法來寫loader,比如

loaders: [{
  test: /\.css$/, 
  loader: "style!css"
 }],

但是,使用此種寫法後,編譯時會報錯。

解決:現在新版本中,已經不允許省略了,比如使用全稱,比如:

loaders: [{
  test: /\.css$/, 
  loader: "style-loader!css-loader"
 }],

問題四:默認情況,在js中require(css)後,樣式嵌入在js中,沒有獨立的css文件

説明:webpack的默認設置中,如果沒有引入特殊的第三方插件,在js中require的css文件是會自動寫入到相應打包出的js中的,這樣一來有一些缺點:

  • js是阻塞加載的,樣式會出現很慢
  • 沒有單獨的css文件,緩存也不便,而且不符合開發習慣

解決:需要引入一個第三方插件extract-text-webpack-plugin,具體如下:

//頭部引入css打包插件
const ExtractTextPlugin = require("extract-text-webpack-plugin");

//聲明對應的loaders
loaders: [{
  test: /\.css$/,
  //請注意loader裏的寫法,有一些低版本的例子中是過時的寫法
  loader: ExtractTextPlugin.extract({
    fallback: "style-loader",
    use: "css-loader"
  })
}],

plugins: [
  ...
  //這樣會定義,所有js文件中通過require引入的css都會被打包成相應文件名字的css
  new ExtractTextPlugin("[name].css"),
],

注意,以上loaders和plugins中都必須聲明,缺一不可

問題五:ExtractTextPlugin 插件loader寫法不對導致報錯

説明:最初在引用ExtractTextPlugin 插件時,使用瞭如下寫法,導致了報錯

loaders: [{
  test: /\.css$/,
  loader: ExtractTextPlugin.extract(["style-loader","css-loader"])
}],

解決:原因是這種寫法已經過時,根據命令台中的提示,修改為最新寫法即可:

loaders: [{
  test: /\.css$/,
  loader: ExtractTextPlugin.extract({
    fallback: "style-loader",
    use: "css-loader"
  })
}],

問題六:在js中直接require(html)不會輸出成html

説明:在使用webpack時,習慣性的用了萬物皆模塊這個概念,於是在js中引入html,如下:

//***.js中
require('./index.html')
...

結果是並不會生成一個index.html的文件,而是會將html代碼嵌入到js中,作為模塊代碼而存在。

解決: 一般webpack項目中,會用htmlPagePluginConfig插件來引入html(這時候會給每一個html制定一個入口js文件),然後接下來所有操作都可以從這個入口js文件中進行(比如引入css,比如邏輯操作)

需要注意的是,這個插件聲明html時,對應引入的js路徑一定要對,如:

{
  template: 'pages/index.html',
  // 如果是隻聲明index,由於路徑不對,則不會自動插入
  chunks: ['js/index'],
}

問題七:html中的img資源不會被自動替換

説明:開發過程中使用的htmlPagePluginConfig插件加載html,但是發現構建過程中,html內部引入的img等標籤不會被自動替換。

解決:需要引入html-loader插件,有了這個插件後,引入,默認會將html內部的img自動替換掉

問題八:在html中link(css)後,不會替換對應的css,或者是會報錯

説明:正常開發是所有css都是在js中引入的,但是在某一次嘗試中,準備嘗試在html裏用原始的link方式引入css,並引入了html-loader,如下:

<meta charset="utf-8" />
<link rel="stylesheet" href="test.css" />
loaders: [{
  test: /\.html$/,
  use: [{
    loader: 'html-loader',
    options: {
    minimize: false,
   }
}],

結果按上述的方法進行後發現對於的html裏的css路徑沒有替換,這時發現html-loader默認是不會替換css,於是又做如下改動

loaders: [{
  test: /\.html$/,
  use: [{
    loader: 'html-loader',
    options: {
    minimize: false,
    //開啓link的替換
    attrs: ['img:src', 'link:href']
   }
}],

但是採用上述配置後,提示:Error: "extract-text-webpack-plugin" loader is used without the corresponding plugin

解決:webpack中ExtractTextPlugin需要配合js模塊化引入css使用。
如果想要實現html內部linkcss,目前沒有找到完美的解決方法,只知道使用webpack構建項目時,css都通過js require引入就不會有錯了

問題九:css-loader中,.min.css重複壓縮會報錯

説明:在項目中引入第三方lib文件後,發現,如果引入的文件本身已經壓縮過了(如min.css文件),這時候再採用壓縮配置css-loader?minimize,則會報錯

解決:採用了正在表達式進行了過濾,含有.min.css的文件不會壓縮,其它css則會壓縮,如下:

loaders: [{
  //20170314更新:以下是錯誤寫法,比如common.css也無法匹配的
  //test: /[^((?!\.min\.css).)*$]\.css$/,
  //以下是正確寫法
  test: /^((?!\.min\.css).)*\.css/,
  loader: ExtractTextPlugin.extract({
    fallback: "style-loader",
    use: "css-loader?minimize&-autoprefixer"
   })
}, {
  test: /\.min\.css$/,
  loader: ExtractTextPlugin.extract({
    fallback: "style-loader",
    use: "css-loader"
   })
}],

20170320更新
最終發現重複壓縮錯誤的原因是因為.min.css文件中包含如下代碼:

 background-image: url('data:image/svg+xml;charset=utf-8,<svg viewBox=\'0 0 120 120\' xmlns=\'http://www.w3.org/2000/svg\' xmlns:xlink=\'http://www.w3.org/1999/xlink\'><defs><line id=\'l\' x1=\'60\' x2=\'60\' y1=\'7\' y2=\'27\' stroke=\'%236c6c6c\' stroke-width=\'11\' stroke-linecap=\'round\'/></defs><g><use xlink:href=\'%23l\' opacity=\'.27\'/><use xlink:href=\'%23l\' opacity=\'.27\' transform=\'rotate(30 60,60)\'/><use xlink:href=\'%23l\' opacity=\'.27\' transform=\'rotate(60 60,60)\'/><use xlink:href=\'%23l\' opacity=\'.27\' transform=\'rotate(90 60,60)\'/><use xlink:href=\'%23l\' opacity=\'.27\' transform=\'rotate(120 60,60)\'/><use xlink:href=\'%23l\' opacity=\'.27\' transform=\'rotate(150 60,60)\'/><use xlink:href=\'%23l\' opacity=\'.37\' transform=\'rotate(180 60,60)\'/><use xlink:href=\'%23l\' opacity=\'.46\' transform=\'rotate(210 60,60)\'/><use xlink:href=\'%23l\' opacity=\'.56\' transform=\'rotate(240 60,60)\'/><use xlink:href=\'%23l\' opacity=\'.66\' transform=\'rotate(270 60,60)\'/><use xlink:href=\'%23l\' opacity=\'.75\' transform=\'rotate(300 60,60)\'/><use xlink:href=\'%23l\' opacity=\'.85\' transform=\'rotate(330 60,60)\'/></g></svg>');

所以包含這種代碼的css使用css-loader的minify時會報錯,解決方法是暫時將這類的css單獨放入文件中,並且不進行minify

問題十:css中和html中的img路徑引用出錯,不符合預期

説明:在將項目劃分目錄結構後,html內和css內都有引用img或font,採用html-loader替換html內部資源,之後發現構建出來後的文件中,img和font等資源的引用路徑不對。如:

本來應該是路徑 ../img.***.img
結果直接就變成了 img/***.img 
導致路徑不一致

解決:默認打包路徑無法替換為相對路徑。這時候需要引入publicPath這個變量,將所有的資源用全局路徑替換,如:

output: {
// 用來解決css中的路徑引用問題
publicPath: config.publicPath,
    },

以上publicPath由開發者自身根據實際情況進行配置,一般是指向項目的發佈路徑根目錄,比如http://localhost:8080/dist/,可以自由切換開發模式和發佈模式

問題十一:webpack-dev-serve配置錯誤帶來的問題

説明:在使用webpack-dev-serve時遇到了如下問題:

one:

  • helloworld項目時,webpack-dev-server --port 8082即可開啓服務,並且自動監聽進行刷新(並且支持iframe刷新與inline刷新)
  • 但是在config的output中加入了publicPath後,默認的服務端配置,啓動後不會自動監聽進行刷新
  • 問題最終分析出了,是因為devServer和構建的路徑outputPath不一致導致的,如
Project is running at  http://localhost:8082/
webpack output is served from http://localhost:8080/dist/
  • 解決方法為:增加devServer的配置,自動讀取publicPath,如下:(默認用了iframe刷新)
    //dev版才有serve
    devServer: {
        historyApiFallback: true,
        hot: false,
        //不使用inline模式,inline模式一般用於單頁面應用開發,會自動將socket注入到頁面代碼中
        inline: false,
        //content-base就是 codeResource -需要監聽源碼
        contentBase: path.resolve(__dirname, config.codeResource),
        watchContentBase: true,
        // 默認的服務器訪問路徑,這裏和配置中一致,需要提取成純粹的host:ip
        public: /https?:\/\/([^\/]+?)\//.exec(config.publicPath)[1]
    },

two:

  • 在運行devServer時,將contentBase指向了發佈目錄,如contentBase:"dist",但是卻發現修改dist裏的任何文件,服務端都不會刷新
  • 最終發現是功能理解錯誤,在啓動devServer的命令中,並不會構建出dist,也就是説,它並不是訪問的dist目錄下的文件,而是基於源碼src直接發佈到服務端的
  • 解決方法,將contentBase指向src目錄,修改src目錄下的文件就可以看到自動刷新了(一定要注意,server用到的不是dist下的文件)

three:

  • 使用devServe時,開啓了inlinehot,但是熱更新無效
  • 原因是,沒有引入這個插件HotModuleReplacementPlugin,需要如下聲明
new webpack.HotModuleReplacementPlugin()

four:

  • 熱更新模式下,提示ERR_NAME_NOT_RESOLVED,不能實時刷新,一直都無法找到原因
  • 説實話,正常情況下不會遇到這個問題,一般遇到這個問題請檢查是否版本號不對應,也可去github上找issue
  • 但是,我這裏僅僅是因為自己設置devServe時的public路徑設置錯誤了
//正常設置
localhost:8080
//我的設置
http://localhost:8080
  • 從而導致了提示Project is running at http://http://localhost:8080/,所以只需要改成正常配置即可
  • 另外需要注意的是,如果想要iframe刷新,inline和hot都要是false

five:(20170322更新)

  • webpack-dev-server默認只能在localhost訪問,換為內網ip就不行了(比如192.×××,就算是本機也不行的)
  • 解決: 修改server的默認配置,如
//這個配置可以運行其它機器訪問
host: '0.0.0.0',

//或者運行時
-- host  0.0.0.0

問題十二:hashchunkhash造成的問題

説明:項目發佈時,為了解決緩存,需要進行md5簽名,這時候就需要用到hashchunkhash等了,但是卻遇到了如下問題:(hash系列問題的解決大多參考了參考來源中的博客)

one:(hash問題)

  • 使用hash對js和css進行簽名時,每一次hash值都不一樣,導致無法利用緩存
  • 原因是因為,hash字段是根據每次編譯compilation的內容計算所得,也可以理解為項目總體文件的hash值,而不是針對每個具體文件的。(所以每一次編譯都會有一個新的hash,並不適用)
  • 解決:不用hash,而用chunkhash(js和css要使用chunkhash),chunkhash的話每一個js的模塊對應的值是不同的(根據js裏的不同內容進行生成)

two:(img的chunkhash問題)

  • 前面有提到,hash在js和css中不實用,所以在項目中所有的文件都準備用chunkhash,但是又有了新的問題-img和font等資源中,使用chunkhash會報錯
  • 解決:因為chunkhash只適用於js和css,img中是沒有這種東西的,仍然需要用到hash(這個hash有點區別,每一個資源本身有自己的hash)

three:(chunkhash重複問題)

  • 打包時發現,js和js引入的css的chunkhash是相同的,導致無法區分css和js的更新,如下
index2-ddcf83c3b574d7c94a42.css
index2-ddcf83c3b574d7c94a42.js
  • 原因是因為webpack的編譯理念,webpack將css視為js的一部分,所以在計算chunkhash時,會把所有的js代碼和css代碼混合在一起計算

*解決:css是使用ExtractTextPlugin插件引入的,這時候可以使用到這個插件提供的contenthash,如下(使用後css就有獨立於js外的指紋了),

new ExtractTextPlugin("[name]-[contenthash].css")
  • 需要注意的是,在新版本中,親自測試過,修改css的內容並不會引起js中的chunkhash變動(原因估計是webpack內置的算法變為了只計算js chunk),所以css請務必使用contenthash,否則修改後無法生成新的簽名,而是會覆蓋以前的資源

問題十三:提示UNMET PEER DEPENDENCY node-sass,依賴包安裝失敗

説明:剛開始試了下,sass的編譯,裏面有引入node-sass這個依賴包,之後基於這個出現了一些問題

one:(安裝node-sass失敗)

  • 在用sass時,依賴了這個包,但是用npm安裝時,安裝失敗
  • 原因是這個包放在github上的,導致裝不下了
  • 解決:使用淘寶鏡像安裝或者使用淘寶的cnpm安裝,安裝cnpm如下
npm install -g cnpm --registry=https://registry.npm.taobao.org  

two:(node-sass引起的其它安裝包安裝失敗)

  • 使用node.js安裝某個依賴包時,提示UNMET PEER DEPENDENCY node-sass,而且關鍵是這個包本身不會依賴於它,提示如下:
+-- UNMET PEER DEPENDENCY node-sass@^4.0.0
 -- webpack-dev-server@2.4.1 extraneous
  • 原因是,以前這個項目中曾經有安裝過node-sass,但是安裝失敗了,導致node_modules裏一直記錄了這個任務,後續安裝時都會先嚐試去安裝它。
  • 解決:去node_modules目錄下,刪除與node-sass這個依賴包的相關內容(可以全局搜索),重新安裝即可

問題十四:sourceMap配置帶來的問題

説明:在使用sourceMap時,遇到了以下問題

one:(uglify壓縮去掉了sourceMap)

  • 在使用sourceMap時,由於用到了uglify壓縮插件,所以默認去除了js尾部的註釋,導致無法找到map文件
  • 解決: uglify插件加上如下配置
sourceMap:true,
  • 另外config裏的output可以配置sourceMapFilename:'maps/[name].map',將map文件放入maps文件夾中

問題十五:htmlPagePluginConfig配置帶來的問題

20170318更新
説明: 同時引入了html-loaderhtml-webpack-plugin後,兩個插件都設置了minify屬性,則會編譯生成時報錯,錯誤配置如下:

loader: 'html-loader',
options: {
  minimize: config.isRelease ? true : false,
}

new HtmlWebpackPlugin({
  ***
  minify: config.isRelease ? {
    collapseWhitespace: true,
    collapseBooleanAttributes: true,
    removeComments: true,
    removeEmptyAttributes: true,
    removeScriptTypeAttributes: true,
    removeStyleLinkTypeAttributes: true,
    minifyJS: true,
    minifyCSS: true
}: null,
 
})

解決:只需要將HtmlWebpackPlugin中對應的minify屬性去掉即可。

問題十六:webpack中JS手動引入的圖片問題

20170318更新
説明: webpack是萬物皆模塊,但也就是説,不通過require引入的就不會算成模塊了(插件中的另算,那是處理過的)。所以,在JS中手動引入圖片時會遇到問題就是對應的圖片並不會被打包,導致之後找不到路徑。如下:

var GalleryData = [{
    id: "testgallery1",
    title: "",
    //為空
    //保持目錄結構
    url: "../../static/img/gallery/img_testgallery1.jpg"
},
{
    id: "testgallery2",
    title: "",
    //為空
    url: "../../static/img/gallery/img_testgallery2.jpg"
}];

以上的url就是引入的源碼本地圖片,但是卻發現並不會被打包出來。

解決:

  • 將以上的static文件夾作為靜態資源,用copy-webpack-plugin插件提取出來(這時候需要遵守的一個約定就是static文件夾下的是專門給js引入或者外部資源訪問的,平時正常的css,html中的引入請放入其它文件夾中,比如img,避免相互影響,這就是約定大於配置)
  • 或者,通過require引入圖片後再設置,如下:(但是這樣會破壞代碼結構,個人並不建議)
var imgUrl = require('./images/bg.jpg'),
imgTempl = '![]('+imgUrl+')';

示例Demo

本次進行webpack學習時。依次安裝功能遞增,循序漸進的寫了多個demo(每一個均可正常運行),每一個demo都有自身的READEME.MD説明,目錄結構如下;

├── 01helloWorld    # 入門hellworld,一個html,一個js,一個css,css默認嵌入在js中,html採用`HtmlWebpackPlugin`加載
├   
├── 02helloWorld2    # 基於第1個進行拓展,css使用`ExtractTextPlugin`單獨打包成一個文件
├   
├── 03pageWithSingleJsAndCss    # 基於第2個進行拓展,示例頁面由一個變為多個,並且抽取了通用配置文件`common.config`
├   
├── 04pageWithStaticResource    # 基於第3個進行拓展,增加了`html-loader`替換靜態資源,解決了css重複壓縮報錯問題,使用`publicPath`,解決資源文件引用路徑問題,增加了`webpack-dev-server`配置
├
├── 05withCommonChunk    # 基於第4個進行拓展,增加了`CommonsChunkPlugin`提取公告js和css,增加了`UglifyJsPlugin`,修改了一些配置,更好應用於項目
├
├── 06withHashStaticAndRelease    # 基於第5個進行拓展,增加了`CopyWebpackPlugin`複製靜態資源,增加了`chunkhash`,`contenthash`等指紋簽名功能,增加了`alias`別名設置,增加了release版本和dev版本的開關
├   
├── 07withLocalServer    # 基於第6個進行拓展,增加了一個`api-server`,來寫本地測試接口(已經進行了跨域配置)
├   
└── 08withFamilyBucket    # 基於第7個進行拓展,webpack全家桶項目,增加了`source-map`,增加了`assets-webpack-plugin`等等

源碼地址:

系列demo的源碼地址是: https://github.com/dailc/webpack-freshmanual

附錄

參考資料

  • http://www.jianshu.com/p/42e11515c10f
  • Webpack中hash與chunkhash的區別

博客

初次發佈2017.03.13於個人博客

  • http://www.dailichun.com/2017/03/13/webpackfreshmanualAndBug.html

Add a new 评论

Some HTML is okay.