webpack搭建前端一條龍服務


作為從grunt、gulp一路走來的老碼農,一開始用webpack的時候我是很抗拒的。但由於核心庫使用了vue,而webpack又是vue的最佳拍檔(vue作者專門為其寫了vue-loader),所以用webpack來構建項目就成了自然而然的事情。經過一段時間的摸索,各個流程都跑通了,「從入門到放棄」的那點事也就都不算事了。

webpack是以模塊為中心的打包工具,但由於其日漸豐富的插件,能做的事情已經很多了,從開發環境搭建到上線構建,幾乎可以一條龍包辦了。事實上我在最近的項目中,連gulp都省了,完全用webpack就完成了所有的工作。下面一一道來。

本地server

在前后端分離的大前提下,前端本地已不需要起后端服務了,那為什么還要起一個本地server呢?這個server主要提供以下支持:

  1. 靜態資源訪問

  2. 代碼熱更新(實時刷新瀏覽器)

  3. 模擬請求數據

  4. 代理http請求

本地server是由webpack插件webpack-dev-middleware提供的,它是基於express的,所以還需要把express也裝上。配置代碼相當簡單:

var devMiddleware = require('webpack-dev-middleware')(compiler, {
    publicPath: config.output.publicPath,
    stats: {
        colors: true,
        chunks: false
    }
});

app.use(devMiddleware);

通常我們把它保存為dev-server.js,然后配合npm script來啟動這個server,在package.json中寫好:

"scripts": {
    "dev": "node ./build/dev-server.js"
}

然后一行命令就可以啟動了:npm run dev

需要注意的一點事,當我們起了本地server之后,webpack打包后的文件並不寫入到硬盤上,而是保存在內存中。所以你並不會在目錄下看到生成的文件,但是瀏覽器已經能夠按照路徑進行訪問了,這樣開發環境下的編譯速度就大大加快。

獨立vue組件

用了webpack之后,寫vue組件最爽的就是可以寫成單獨的.vue文件了,在一個文件中寫好所有的樣式、模板、js邏輯。然后vue-loader就會幫我們編譯成標准的vue組件。

令人高興的是,vue2.0引入了虛擬DOM來提高性能,vue-loader也會將我們的模板編譯成虛擬DOM來使用,你也不必費勁再去寫render函數了。

編譯ES6

之前也說過項目已經完全用ES6了,所以我們在開發環境需要用babel進行編譯,我們寫ES6代碼的地方有兩個,一個是js文件,另一個是.vue文件中的。

js文件的在webpack配置文件中配置loader即可:

{
    test: /\.js$/,
    loader: 'babel',
    exclude: /node_modules/
}

而.vue文件中的,vue-loader已經天然幫我們做了,所以不需要任何設置。

babel的配置項,寫在.babelrc中放在根目錄下即可。

編譯sass

前端進入編譯時代,css當然也是少不了的。項目中使用了sass來編寫css代碼,所以也需要在開發環境進行編譯。需要編譯的有兩個地方,一個是外鏈的.scss文件,另一個是.vue文件中的。

對於.scss文件,我們還是用loader來處理,安裝sass-loader,然后在webpack配置文件中配好:

{
    test: /\.scss$/,
    loader: ExtractTextPlugin.extract('style', 'css!sass')
}

至於.vue文件中的,vue-loader早幫我們做好了工作,只需在<style>標簽加上lang屬性就可以了,例如編譯sass

<style lang="sass">
/*此處可以寫sass代碼啦*/

</style>

代碼檢查

在開發階段進行代碼檢查也是一項必要工作,檢查ES6當然eslint是標配,在配置文件中可以通過preLoaders來配置:

preLoaders: [
{
    test: /\.(js|vue)$/,
    exclude: /node_modules/,
    loader: 'eslint-loader'
}
]

這樣在js文件和.vue文件中的代碼都會用eslint規則進行檢查。eslint的配置同樣寫在.eslintrc文件中放在根目錄下。

代碼熱更新

編譯工作就是以上這些了。當我們開始正式敲代碼之后,還有一個功能是夢寐以求的,那就是代碼熱更新。即編輯器保存代碼后,瀏覽器實時刷新。這個特性在webpack中叫做”模塊熱替換(hot module repacement)“,使用webpack-hot-middleware這個插件來完成。

這個插件跟我們以前用的live-reload不同,它不會刷新瀏覽器頁面,而是把模塊進行熱替換。這樣的好處是,應用的當前狀態還能保持,但是代碼已經更新了。比以前爽了不止一點半點。

模擬數據

前后端分離開發中,還有重要的一環,就是前端mock數據。在后端接口開發完成之前,我們可以通過自己模擬的數據完成調試。

事實上mock數據並不需要webpack提供,而是通過我們的本地server,寫express中間件的方式。需要以下兩步:

第一步,根目錄下建一個mock目錄,用於放置假數據,每個接口一個js文件,為了便於express使用,文件的格式如下:

module.exports = {
    api: '/api/mock/banner',
    response: function (req, res) {
        res.json({
            success: true,
            data: {
                name: 'test'
            }
        });
    }
}

第二步,在dev-server中把mock的數據掛載到express上,代碼如下:

var mockDir = path.resolve(__dirname, '../mock');
    fs.readdirSync(mockDir).forEach(function (file) {
    var mock = require(path.resolve(mockDir, file));
    app.use(mock.api, mock.response);
});

這樣當我們請求的路徑中含有/api/mock字樣時,就會返回我們mock的數據啦。當后端的接口開發完成時,我們就可以把路徑中的mock去掉,從而去請求后端的接口。

代理請求

由於我們起了本地server,ajax路徑是相對的,所以請求會打到本地的這個server上。當我們需要調試后端接口時,就得把請求轉發到后端服務器。為了避免跨域的麻煩,通常需要配置http代理。

我使用了http-proxy-middleware這個中間件,這也是和webpack無關的,它也是一個express中間件。配置方法如下:

app.use(proxy('/api', {
    target: {
        host: 'localhost',
        protocol: 'http:',
        port: 5050
    },
    logLevel: 'debug'
}));

我們和后端協商好,所有異步請求都帶"/api"前綴,這樣我就把請求轉發到后端服務器了。此處我把host填了localhost,意味着我本地起了一個后端服務,事實上可以指向任何一台可以提供接口的服務器。

這個proxy中間件有一個小坑,就是路徑必須向我這樣分開寫。如果拼在一起不會生效,我目前沒找到原因。

移動端遠程調試

出於平台兼容性的考慮,移動端調試我選擇了weinre。用過weinre的同學應該知道,我們需要在頁面上嵌一個<script>標簽才能使用weinre。不熟悉的同學可以參考我之前寫的這篇:http://www.cnblogs.com/lvdabao/p/3436620.html

所以,我們需要對當前運行的環境進行判斷,如果是開發環境才嵌入這個標簽。我用了一個叫做DefinePlugin的插件來在通過webpack在頁面全局定義變量,用法如下:

new webpack.DefinePlugin({
    LOCAL_IP: JSON.stringify(localIP),
    __ENV__: JSON.stringify('dev'),
    WEINRE_RUN: JSON.stringify(process.argv[2]=='weinre')
})

這樣,在頁面初始化的時候,我們就可以根據當前的環境來決定是否嵌入這個<script>啦,如下:

if(__ENV__ == 'dev' && WEINRE_RUN){
    document.write('<script src="http://'+LOCAL_IP+':8080/target/target-script-min.js#anonymous"></script>');
}

插入<script>標簽

我們的js打包好后,下一步就是把它引入到html文件了。由於我們通常會用md5作為文件名來控制js版本,所以html中的文件地址就需要每次構建后進行更新。

webpack中是通過HtmlWebpackPlugin這個插件來實現的,它的特點在於,每次構建基於一個html模板來生成新的html文件,所以並不是替換js路徑。在webpack的plugins選項中如下配置:

new HtmlWebpackPlugin({
    filename: path.resolve(__dirname, '../static/dist/index.html'),
    chunks: ['vendor', 'components', 'app'],
    template: path.resolve(__dirname, '../static/src/index.html'),
    inject: true
})

代碼壓縮

在gulp時代,我們是通過配置gulp任務來完成js的壓縮。在webpack中,是通過插件來完成的,相同的道理,在plugins中進行如下配置:

new webpack.optimize.UglifyJsPlugin({
    compress: {
        warnings: false
    }
})

另外,webpack可以進行sourcemap配置,用於幫助映射文件,開啟代碼如下:devtool: 'source-map'

打包結果分析

在打包完成之后,我們經常會有這個心理:生成的文件完全不可讀,打包的結果到底對不對,有沒有重復打包,有沒有冗余的東西。

這個時候就需要一個分析工具了,webpack有這樣一個插件,能以可視化的方式展示打包結果,為你提供分析需求。它就是BundleAnalyzerPlugin,相當好用,我上一張我的截圖:

 

上線構建

以上大部分是開發環境需要的配置,當我們本地測試完畢需要上線時,就需要真正生成文件了。

我們一般會准備兩個或者多個(如何有多個環境,如test環境)webpack配置文件,用於不同的環境。在線上服務器跑的配置文件我們一般會修改輸出地址,如果需要配cdn的話,把output中的publicPath填成cdn地址即可。然后直接運行webpack編譯命令,而不是像本地server那樣起。

同樣我們也會寫一個npm script,像這樣:

"scripts": {
    "dev": "node ./build/dev-server.js",
    "build": "webpack --config ./build/prod.webpack.config.js"
}

我對webpack的應用大概就是這么多了,本想大致介紹,沒想到一貼就是這么多代碼~~其實webpack的精髓我本篇並未提及,那就是打包策略的選擇,不同的項目架構需要不同的打包方式,而這個過程,則充滿了挑戰,你懂的。

(本文始發於我的微信公眾號(doctor-programmer),整理重新編輯后發於博客,有興趣的同學歡迎關注我的公眾號,右邊掃碼去吧~)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM