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

前言

上一期從零構(gòu)建了一個基礎(chǔ)版的vue-cli項目,主要介紹了loader的安裝和一些配置項的用法,還給項目添加了less預(yù)處理器

上一期的鏈接-從搭建vue-腳手架到掌握webpack配置(一.基礎(chǔ)配置)

本期開始引入常用的插件實現(xiàn)開發(fā)環(huán)境和生成環(huán)境會用到的一些功能,比如熱插拔、css樣式提取、公共模塊提、取代碼壓縮等等

區(qū)分開發(fā)與生產(chǎn)環(huán)境

很多插件功能是在開發(fā)環(huán)境(development)用到的但是在s生產(chǎn)環(huán)境(production)用不到的,反之亦然。比如

-development用到的
  • 熱插拔調(diào)試
  • 生成html模板
-production用到的
  • 生成html模板
  • css樣式提取
  • 公共模塊提取
  • JavaScript壓縮
  • ......

引用官方的說法 ,區(qū)分生產(chǎn)和開發(fā)環(huán)境有兩種方法,如下圖

image

第二種方法涉及到二次封裝,就像官方vue-cli構(gòu)建的項目一樣,分成了三個配置文件,對目前的我們來說比較復(fù)雜,我們使用第一種方法,設(shè)置環(huán)境變量來區(qū)分部署環(huán)境。

參考vue-cli生成的簡單版工程(webpack-simple),我們發(fā)現(xiàn)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,這就是給環(huán)境變量賦值的過程,但是單單這樣寫是無法執(zhí)行的,我們需要安裝一個插件——cross-env

npm install --save cross-env

這樣我們就可以在之后運行在node環(huán)境的js 文件中訪問到這些環(huán)境變量,通過process.env對象還能拿到package.json里面的配置信息,這就涉及到node的知識了,不多說。

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

簡單點寫,把環(huán)境變量的判斷直接放到webpack.config.js文件的最下面

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

module.exports = {
    entry:{
        app:'./src/main.js'
    },
    //...
}
/**
 * 生成生產(chǎn)代碼的時候才觸發(fā)
 */
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插件是設(shè)置全局常量的插件,要記住!賦值的時候記得寫成'"production"', 官方對DefinePlugin插件 是這么說的

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

生成html模板

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

npm install --save-dev html-webpack-plugin

先通過require引入插件,然后在輸出對象里面添加plugins屬性,數(shù)據(jù)值類型是數(shù)組,數(shù)組成員new [插件]()添加插件就行。每個插件都有自己的配置項和規(guī)范,可以查 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標簽的內(nèi)容
  • template html模板地址,這里我們用我上一期建在跟目錄的index.html

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

index.html的內(nèi)容要改一改了,因為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服務(wù)器

使用熱替換之前當然要先有一個web服務(wù)器環(huán)境啦,安裝webpack-dev-server

npm install --save-dev webpack-dev-server

webpack-dev-server其實是一個獨立的插件,但是webpack內(nèi)置了它的配置項,屬性devServer對應(yīng)的就是它的配置項。

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

端口地址什么的都默認 http://localhost:8080/ ,就設(shè)置了跟資源目錄地址contentBase。
想更深入的去配置可以看官方文檔 dev-server。我還真沒認真看過,嘻嘻。

熱替換插件

熱替換就是開發(fā)的過程中修改文件內(nèi)容之后不用頻繁刷新頁面,修改會自動同步到瀏覽器中,webpack內(nèi)部已經(jīng)有這份插件了,不用安裝直接都用就可以。在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,熱部署搞定

以上是開發(fā)環(huán)境要用到的插件,下面就是生成環(huán)境用到的插件了

css文件和vue內(nèi)樣式提取

如果不提取css樣式,所有的.css文件和vue內(nèi)的style都會以style標簽的形式被添加到頁面的head里面,不利于資源的緩存而且降低了頁面的加載速度。

好的,就用extract-text-webpack-plugin插件吧,老規(guī)矩安裝一下

npm install extract-text-webpack-plugin --save-dev
簡單使用

在使用css相關(guān)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內(nèi)部的style需要先抽取出來,所以要在fallback屬性上添加預(yù)先的加載器 'vue-style-loader','vue-style-loader'是vue-loader自帶的哦,如果運行時報錯的話那就手動install一下他吧。

生成多文件

我一般的習(xí)慣是把外部引入的css文件認為是可以復(fù)用的,而vue內(nèi)的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,//填入插件實例,復(fù)用的css
        ExtractVueCss,//記得按順序填入,vue內(nèi)的css
        new webpack.HotModuleReplacementPlugin(),
    ]
}

這就是ExtractTextPlugin插件生成多個文件的方法。你也可以按照自己的習(xí)慣去配置。

公共代碼提取

在多頁面或者多入口的時候(entry設(shè)了不只一個),不同的模塊(chunks)會多次引入一樣的資源模塊(module,也就是import引入的js文件),還有vue等庫的代碼,以上這些復(fù)用的代碼最好是可以獨立出來,一方面方便緩存,一方面減少包的體積。

CommonsChunkPlugin插件就是解決這一問題的,它從屬于webpack.optimize對象所以也是不用安裝的。具體使用如下

new webpack.optimize.CommonsChunkPlugin({
    name: 'vender',
    minChunks:2
})

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

(chunk可以簡單理解為entry屬性設(shè)置的入口而生成的整條關(guān)系樹,所以到目前為止本項目也只有一個chunk,就是'app',當然插件生成的vender也是一個chunk。對初學(xué)者來說就這樣理解吧,用多了自然會有概念)

既然只有一個chunk 那就先抽取公用庫中的代碼吧,如vue包中的代碼。把代碼放到生產(chǎn)環(huán)境判斷里面哦~

/*生成生產(chǎn)代碼的時候才觸發(fā)*/
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

這是一個好心人總結(jié)的各種配置情況下打包的結(jié)果 https://segmentfault.com/a/1190000006808865

其他插件

源碼映射

因為重構(gòu)和壓縮后的代碼不利于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是開發(fā)環(huán)境用的源碼映射,source-map是生成環(huán)境用的源碼映射

官方對 devtool的介紹在這里

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

js代碼壓縮

css文件在 build(抽取和裝載)的同時已經(jīng)進行了簡單的壓縮,所以下面主要是對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
}),

運行構(gòu)建試一下

好了到目前為止大部分會用到的插件都引入到了webpack配置里面,構(gòu)建一下試試。

完整webpack.config.js的代碼在這里 https://pan.baidu.com/s/1jKnDSYa

npm run dev
dev
npm run build
image

發(fā)現(xiàn)uglifyJs報錯,是因為我們沒有配置babel的翻譯器和編譯規(guī)則,篇幅有限babel的配置說明放到下一期。

解決方法:在根目錄下創(chuàng)建文件.babelrc,內(nèi)容如下

{
  "presets": [
    ["env", {
       "modules": false 
    }]
  ]
}

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

然后再build,沒問題了

image

打包后的目錄結(jié)構(gòu)如下

image

嘮叨幾句

想要深入了解每個插件的具體用法,定制自己的需求一定要多點去參考文檔和資料。為了方便大家我已經(jīng)在教程中每一個插件的下面給了大量的鏈接,可以說省去了大家百度的時間,突然感覺自己好細心。

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

下期預(yù)告

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

很可惜,沒想到講插件用了這么長的篇幅,還是沒有提到postcss和babel的配置,下一期開始簡要提一下這些,然后我們繼續(xù)優(yōu)化構(gòu)建過程,讓他可以適應(yīng)多入口多頁面的開發(fā)。想要了解以后的內(nèi)容可以關(guān)注哦~~

?。?!文章首發(fā)地址

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容