從搭建vue-腳手架到掌握webpack配置(二.外掛與提取)

JensonWild發表於2018-01-11

前言

上一期從零構建了一個基礎版的vue-cli專案,主要介紹了loader的安裝和一些配置項的用法,還給專案新增了less前處理器

上一期的連結-從搭建vue-腳手架到掌握webpack配置(一.基礎配置)

本期開始引入常用的外掛實現開發環境和生成環境會用到的一些功能,比如熱插拔、css樣式提取、公共模組提、取程式碼壓縮等等

區分開發與生產環境

很多外掛功能是在開發環境(development)用到的但是在生產環境(production)用不到的,反之亦然。比如

-development用到的
  • 熱插拔除錯
  • 生成html模板
-production用到的
  • 生成html模板
  • css樣式提取
  • 公共模組提取
  • JavaScript壓縮
  • ......

引用官方的說法 ,區分生產和開發環境有兩種方法,如下圖

image

第二種方法涉及到二次封裝,就像官方vue-cli構建的專案一樣,分成了三個配置檔案,對目前的我們來說比較複雜,我們使用第一種方法,設定環境變數來區分部署環境。

參考vue-cli生成的簡單版工程(webpack-simple),我們發現npm script寫得有點奇怪

"scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
  }
複製程式碼

在執行webpack命令之前執行了 cross-env NODE_ENV=develpomentproduction,這就是給環境變數賦值的過程,但是單單這樣寫是無法執行的,我們需要安裝一個外掛——cross-env

npm install --save cross-env
複製程式碼

這樣我們就可以在之後執行在node環境的js 檔案中訪問到這些環境變數,通過process.env物件還能拿到package.json裡面的配置資訊,這就涉及到node的知識了,不多說。

const env = process.env.NODE_ENV
//獲取工程的版本號
const version = process.env.npm_package_version
複製程式碼

簡單點寫,把環境變數的判斷直接放到webpack.config.js檔案的最下面

const path = require('path')
const webpack = require('webpack')

module.exports = {
    entry:{
        app:'./src/main.js'
    },
    //...
}
/**
 * 生成生產程式碼的時候才觸發
 */
if (process.env.NODE_ENV === 'production') {
    // http://vue-loader.vuejs.org/en/workflow/production.html
    module.exports.plugins = (module.exports.plugins || []).concat([
        new webpack.DefinePlugin({
            'process.env': {
            NODE_ENV: '"production"'
            }
        }),
    ])
  }
複製程式碼

如果以後額外的配置項越來越多的話,像上面這樣寫是不太好合並配置項的,到最後還是要抽離出另一個js檔案裝載新增或重寫的配置項,用webpack-merge中介軟體合併配置物件。

webpack.DefinePlugin外掛是設定全域性常量的外掛,要記住!賦值的時候記得寫成'"production"', 官方對DefinePlugin外掛 是這麼說的

注意,因為這個外掛直接執行文字替換,給定的值必須包含字串本身內的實際引號。通常,有兩種方式來達到這個效果,使用 '"production"', 或者使用 JSON.stringify('production')。

生成html模板

之前根目錄下index.html要我們自己引入js資源地址,有新的資源都要手動引入,很麻煩,這時候就會用到HtmlWebpackPlugin 外掛,按照index.html作為模板在dist目錄下生成帶上所有資源的html 檔案。

npm install --save-dev html-webpack-plugin
複製程式碼

先通過require引入外掛,然後在輸出物件裡面新增plugins屬性,資料值型別是陣列,陣列成員new [外掛]()新增外掛就行。每個外掛都有自己的配置項和規範,可以查 npmjs 或者 他們的官方文件

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')

module.exports = {
    entry:{
        app:'./src/main.js'
    },
    output:{
        path:path.resolve(__dirname,'./dist'),
        filename:"js/[name].js",
    },
    module:{
        rules:[
        //...
        ]
    },
    plugins:[
        new HtmlWebpackPlugin({
            filename:'index.html',
            title:'vue demo',
            template:'./index.html',
        })
    ],
    externals:{
        'jquery':'window.jQuery'
    }
}
複製程式碼
說明
  • filename 生成的html的檔名,不填就預設是原檔名
  • title title標籤的內容
  • template html模板地址,這裡我們用我上一期建在跟目錄的index.html

這裡有前輩對HtmlWebpackPlugin的詳細說明文章

index.html的內容要改一改了,因為webpack打包完之後自動新增資源地址到html檔案裡,所以我們要刪掉原本寫上去的script標籤

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vue demo</title>
</head>
<body>
    <div id="app">
     
    </div>
    <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.js"></script>
</body>
</html>
複製程式碼

有人可能會奇怪,這裡為什麼加了一個cdn的jQuery,因為我要在這裡帶過一個知識點:有時候我們會有用到cdn加速的庫資源,但是不知道怎麼在工程中使用。

很簡單我們在html模板中直接引入,然後在webpack.config.js配置中加一項“外部引入”(externals)

// webpack.config.js
externals:{
    'jquery':'window.jQuery'
}
//app.vue中引入
import $ from 'jquery'
複製程式碼

熱替換

web伺服器

使用熱替換之前當然要先有一個web伺服器環境啦,安裝webpack-dev-server

npm install --save-dev webpack-dev-server
複製程式碼

webpack-dev-server其實是一個獨立的外掛,但是webpack內建了它的配置項,屬性devServer對應的就是它的配置項。

module.exports = {
    entry:{
        app:'./src/main.js'
    },
    output:{
        path:path.resolve(__dirname,'./dist'),
        filename:"js/[name].js",
    },
    devServer:{
        contentBase:"./dist"
    }
}
複製程式碼

埠地址什麼的都預設 http://localhost:8080/ ,就設定了跟資源目錄地址contentBase。 想更深入的去配置可以看官方文件 dev-server。我還真沒認真看過,嘻嘻。

熱替換外掛

熱替換就是開發的過程中修改檔案內容之後不用頻繁重新整理頁面,修改會自動同步到瀏覽器中,webpack內部已經有這份外掛了,不用安裝直接都用就可以。在plugins新增一項 new webpack.HotModuleReplacementPlugin()就ok了

plugins:[
        new HtmlWebpackPlugin({
            filename:'index.html',
            title:'vue demo',
            template:'./index.html',
        }),
        new webpack.HotModuleReplacementPlugin()
    ]
複製程式碼

改一下npm scripts

"scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
  }
複製程式碼

執行 npm run dev,熱部署搞定

以上是開發環境要用到的外掛,下面就是生成環境用到的外掛了

css檔案和vue內樣式提取

如果不提取css樣式,所有的.css檔案和vue內的style都會以style標籤的形式被新增到頁面的head裡面,不利於資源的快取而且降低了頁面的載入速度。

好的,就用extract-text-webpack-plugin外掛吧,老規矩安裝一下

npm install extract-text-webpack-plugin --save-dev
複製程式碼
簡單使用

在使用css相關loader之前先用本外掛過濾一遍

var ExtractTextPlugin = require("extract-text-webpack-plugin")

module.exports = {
  // other options...
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            css: ExtractTextPlugin.extract({
              use: 'css-loader',
              fallback: 'vue-style-loader' // <- 這是vue-loader的依賴
            }),
            //用了less或者sass的地方都要用上哦
            'less': ExtractTextPlugin.extract({
                use:[
                    'css-loader',
                    'less-loader'
                ],
                fallback:'vue-style-loader'
            })
          }
        }
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin("styles/style.css")
  ]
}
複製程式碼

vue內部的style需要先抽取出來,所以要在fallback屬性上新增預先的載入器 'vue-style-loader','vue-style-loader'是vue-loader自帶的哦,如果執行時報錯的話那就手動install一下他吧。

生成多檔案

我一般的習慣是把外部引入的css檔案認為是可以複用的,而vue內的style是每個頁面都不一樣的要另外生成的,所以我建了兩ExtractTextPlugin例項分別抽取樣式到兩個檔案裡。

const path = require('path')
const webpack = require('webpack')
const ExtractTextPlugin = require("extract-text-webpack-plugin")
const ExtractRootCss = new ExtractTextPlugin({filename:'styles/root.css',allChunks:false});
const ExtractVueCss = new ExtractTextPlugin({filename:'styles/[name]/style.css',allChunks:true});

module.exports = {
    //other options...
    module:{
        rules:[
        //...
            {
                test:/\.css$/,
                //這裡用的ExtractRootCss
                use:ExtractRootCss.extract({
                    fallback:'style-loader',
                    use:['css-loader']
                })
            },
            {
                test:/\.less$/,
                //這裡用的ExtractRootCss
                use:ExtractRootCss.extract({
                    fallback:'style-loader',
                    use:[
                        'css-loader',
                        'less-loader'
                    ]
                })
            },
            {
                test:/\.vue$/,
                loader:'vue-loader',
                options:{
                    loaders:{
                        //這裡用的ExtractVueCss
                        'css': ExtractVueCss.extract({
                            use: 'css-loader',
                            fallback: 'vue-style-loader' // <- 這是vue-loader的依賴,所以如果使用npm3,則不需要顯式安裝
                          }),
                        //這裡用的ExtractVueCss
                        'less':
                        ExtractVueCss.extract({
                            use:[
                                'css-loader',
                                'less-loader'
                            ],
                            fallback:'vue-style-loader'
                        })
                    },
                }
            },
        ]
    },
    plugins:[
        new HtmlWebpackPlugin({
            filename:'index.html',
            title:'vue demo',
            template:'./index.html',
        }),
        ExtractRootCss,//填入外掛例項,複用的css
        ExtractVueCss,//記得按順序填入,vue內的css
        new webpack.HotModuleReplacementPlugin(),
    ]
}
複製程式碼

這就是ExtractTextPlugin外掛生成多個檔案的方法。你也可以按照自己的習慣去配置。

公共程式碼提取

在多頁面或者多入口的時候(entry設了不只一個),不同的模組(chunks)會多次引入一樣的資源模組(module,也就是import引入的js檔案),還有vue等庫的程式碼,以上這些複用的程式碼最好是可以獨立出來,一方面方便快取,一方面減少包的體積。

CommonsChunkPlugin外掛就是解決這一問題的,它從屬於webpack.optimize物件所以也是不用安裝的。具體使用如下

new webpack.optimize.CommonsChunkPlugin({
    name: 'vender',
    minChunks:2
})
複製程式碼

minChunks引數可以是number型別,填2 就是說有2個chunk以上用到的公共塊就會被打包的vender.js裡面。minChunks也可以傳一個方法,返回值是boolean型別.

(chunk可以簡單理解為entry屬性設定的入口而生成的整條關係樹,所以到目前為止本專案也只有一個chunk,就是'app',當然外掛生成的vender也是一個chunk。對初學者來說就這樣理解吧,用多了自然會有概念)

既然只有一個chunk 那就先抽取公用庫中的程式碼吧,如vue包中的程式碼。把程式碼放到生產環境判斷裡面哦~

/*生成生產程式碼的時候才觸發*/
if (process.env.NODE_ENV === 'production') {
    module.exports.plugins = (module.exports.plugins || []).concat([
        new webpack.DefinePlugin({
            'process.env': {
            NODE_ENV: '"production"'
            }
        }),
        //抽取從node_modules引入的模組,如vue
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vender',
            minChunks:function(module,count){
                var sPath = module.resource;
                // console.log(sPath,count);
                //匹配 node_modules檔案目錄
                return sPath &&
                    /\.js$/.test(sPath) &&
                    sPath.indexOf(
                        path.join(__dirname, 'node_modules')
                    ) === 0
            }
        })
    ])
  }
複製程式碼

這是中文文件上的介紹 commons-chunk-plugin

這是一個好心人總結的各種配置情況下打包的結果 segmentfault.com/a/119000000…

其他外掛

原始碼對映

因為重構和壓縮後的程式碼不利於debug,所以我們先要開啟source map功能,在webpack配置裡面新增一項devtool,如下

module.exports = {
    //entry: ...
    devtool: '#eval-source-map'
}
if (process.env.NODE_ENV === 'production') {
    module.exports.devtool = '#source-map'
}
複製程式碼

eval-source-map是開發環境用的原始碼對映,source-map是生成環境用的原始碼對映

官方對 devtool的介紹在這裡

阮一峰老師對 source map 的介紹在這裡

js程式碼壓縮

css檔案在 build(抽取和裝載)的同時已經進行了簡單的壓縮,所以下面主要是對js程式碼的壓縮,也就是常常的UglifyJs(醜化js),webpack自帶了UglifyJsPlugin外掛,在plugins上啟用就行。

new webpack.optimize.UglifyJsPlugin({
    sourceMap: true,//開啟原始碼對映
    compress: {
        warnings: false//去到警告
    }
}),
複製程式碼

但是以上的用法是webpack1.0遺留下來的,用的舊版的UglifyJs,他的使用說明也在wepack1.0的文件裡。你可以有手動安裝uglifyjs-webpack-plugin,引入最新的UglifyJs

/* npm install -save-dev uglifyjs-webpack-plugin */

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

new UglifyJsPlugin({
  uglifyOptions: {
    compress: {
      warnings: false
    }
  },
  sourceMap: true
})
複製程式碼

webpack3.0中文文件對該外掛的說明 在這裡

官方文件的介紹 在這裡

webpack1.0遷移外掛

loader-options-plugin 和其他外掛不同。它的用途是幫助人們從 webpack 1 遷移至 webpack 2。官方說明

new webpack.LoaderOptionsPlugin({
    minimize: true
}),
複製程式碼

執行構建試一下

好了到目前為止大部分會用到的外掛都引入到了webpack配置裡面,構建一下試試。

完整webpack.config.js的程式碼在這裡 pan.baidu.com/s/1jKnDSYa

npm run dev
複製程式碼

dev

npm run build
複製程式碼

image

發現uglifyJs報錯,是因為我們沒有配置babel的翻譯器和編譯規則,篇幅有限babel的配置說明放到下一期。

解決方法:在根目錄下建立檔案.babelrc,內容如下

{
  "presets": [
    ["env", {
       "modules": false 
    }]
  ]
}
複製程式碼

安裝babel-preset-env,npm install --save-dev babel-preset-env

然後再build,沒問題了

image

打包後的目錄結構如下

image

嘮叨幾句

想要深入瞭解每個外掛的具體用法,定製自己的需求一定要多點去參考文件和資料。為了方便大家我已經在教程中每一個外掛的下面給了大量的連結,可以說省去了大家百度的時間,突然感覺自己好細心。

官方文件也不需要全部都看,用到什麼看什麼,要什麼功能配置就重點看那部分就好,等到有時間再簡要的過一遍文件。

下期預告

到目前為止,整個工程可以說完全可用了。樣式抽離,公共提取,壓縮都用到了,對比一下vue init webpack-simple project-name構建的簡單工程,會發現我們比它的功能還完整,有沒有一點成就感呢?

很可惜,沒想到講外掛用了這麼長的篇幅,還是沒有提到postcss和babel的配置,下一期開始簡要提一下這些,然後我們繼續優化構建過程,讓他可以適應多入口多頁面的開發。想要了解以後的內容可以關注哦~~

第三期已更新:從搭建vue-腳手架到掌握webpack配置(三.多頁面構建)

參考

相關文章