使用Webpack打包時(shí)的"多頁(yè)"實(shí)踐

Webpack在”多頁(yè)“開發(fā)中遇到的問題

在開發(fā)時(shí)我們經(jīng)常使用Webpack官方提供的webpack-dev-server插件。我們只需要通過(guò)一個(gè)入口main.js和入口頁(yè)面HTML,用webpack-dev-server就能夠提供熱更新“Live Reload”以及熱替換“Hot Module Replacement”(即HMR) ,這很方便,但是在實(shí)際項(xiàng)目中我們遇到了更復(fù)雜的場(chǎng)景。

舉例來(lái)說(shuō),我們現(xiàn)在的一個(gè)項(xiàng)目中不僅僅只是一個(gè)SPA應(yīng)用了,它可能是由多個(gè)SPA應(yīng)用構(gòu)成的一個(gè)項(xiàng)目,每個(gè)SPA應(yīng)用可能會(huì)由不同的人維護(hù)。因此它會(huì)有多個(gè)入口JS和入口HTML。由于沒有提供唯一的入口js和html,僅僅使用webpack-dev-server的方案就行不通了。對(duì)于這種”多頁(yè)應(yīng)用“的項(xiàng)目,最好的方案是在開發(fā)時(shí)能通過(guò)路由切換到對(duì)應(yīng)的SPA應(yīng)用下,即對(duì)應(yīng)的入口JS和HTML下,為此我們需要一些小技巧。

解決方案

其實(shí)在使用Webpack以前其實(shí)我們不會(huì)有這種煩惱,也許這就是“螺旋式上升”的必經(jīng)之路。總之,我一開始能想到的方案有以下三種。

  1. 在開發(fā)模式下通過(guò)Gulp監(jiān)聽文件變化,然后直接使用Webpack打包出文件,用Gulp-Server處理路由。
  2. 任然使用webpack-dev-server,通過(guò)proxy代理另一個(gè)Server處理路由。
  3. 只起一個(gè)Server + WebpackMiddleware 在保留熱更新和熱替換的基礎(chǔ)上,增加多路由。

三種方法各有利弊。在此我們選擇第三種方式,通過(guò)獨(dú)立Server我們能夠更方便的處理Mock中Post請(qǐng)求以及Prox等問題,自由度更大。

server.png
server.png

圖片中的multientry-dev-server就是接下來(lái)我們要?jiǎng)?chuàng)造的server。

Webpack Dev\Hot Middleware

官方提供的webpack-dev-server也只是一個(gè)用Express起的Server而已,其中使用了webpack-dev-middlewarewebpack-hot-middleware作為中間件提供Hot Module Replacement/Hot Reloading能力。Webpack Hot Middleware 必須要搭配Webpack Dev Middleware才能使用,因此同樣的我們也可以用Express啟動(dòng)一個(gè)有熱替換功能的服務(wù)器,webpack-dev-server只是做了一個(gè)簡(jiǎn)單的封裝而已。

var http = require('http');
var express = require('express');
var path = require('path');
var webpackMiddleware = require('webpack-dev-middleware');
var webpackHotMiddleware = require('webpack-hot-middleware');

通過(guò)下面的代碼建立Webpack的實(shí)例以及使用中間件

var app = express();
var compiler = webpack(webpackConfig);
var middleware = webpackMiddleware(compiler, {
  publicPath: webpackConfig.output.publicPath
});
app.use(middleware);
app.use(webpackHotMiddleware(compiler));

通過(guò)路由獲取內(nèi)存中的Webapck打包文件

使用webpack中間件打包并不會(huì)真正的生成文件,它會(huì)把文件載入到內(nèi)存中。
為了能通過(guò)路由指定跳轉(zhuǎn)到對(duì)應(yīng)的入口JS和HTML,我們?cè)谛柙陧?xiàng)目中做一些約定。假設(shè)項(xiàng)目入口為apps目錄,該目錄下的每一個(gè)子目錄對(duì)應(yīng)一個(gè)SPA應(yīng)用,在子目錄中需要通過(guò)一個(gè)package.json指定該SPA應(yīng)用的入口JS和這個(gè)SPA應(yīng)用的其他信息,比如名字和子應(yīng)用負(fù)責(zé)人等。

基礎(chǔ)模板1.png
基礎(chǔ)模板1.png

package.json文件格式類似如下:

{
  "name": "app1",
  "main": "./main.js",
  "author": "左倫"
}

middleware提供了middleware.fileSystem.readFileSync方法讀取內(nèi)存中的文件,文件的地址就是在webpack中配置的輸出地址。

app.get('/:appName', function (req, res) {
  var result = '';
  var htmlPath = path.join(__dirname, webpackConfig.output.path + req.params.appName + '/index.html');
  console.log(htmlPath);
  try {
    result = middleware.fileSystem
      .readFileSync(htmlPath);
  } catch (err) {
    result = err.toString();
  }
  res.write(result);
  res.end();
});

OK, 至此便可以通過(guò)路由指定到對(duì)應(yīng)App的入口。

Mock數(shù)據(jù)以及“首頁(yè)”

由于是獨(dú)立啟動(dòng)的Server,我們可以很方便的在Server中指定任意目錄作為我們的靜態(tài)目錄,同時(shí)處理好對(duì)應(yīng)的Post請(qǐng)求。

// 靜態(tài)資源,Mock GET請(qǐng)求
app.use(express.static(path.join(__dirname, '../')));
// Mock POST請(qǐng)求
app.post('/api/*', function (req, res) {
  res.sendFile(path.join(__dirname, '../api', req.params[0]));
});

當(dāng)越來(lái)越多的子App在項(xiàng)目中后,通過(guò)手動(dòng)在瀏覽器中輸入路由再進(jìn)行跳轉(zhuǎn)會(huì)顯得十分麻煩。因此可以在Server中新增一個(gè)“首頁(yè)”,列出當(dāng)前項(xiàng)目下的所有子應(yīng)用以及開發(fā)時(shí)的對(duì)應(yīng)路由,具體實(shí)現(xiàn)并不難,通過(guò)遍歷目錄下每個(gè)應(yīng)用中的Packge.json即可,最后效果如下:


屏幕快照 2016-12-11 下午7.11.20.png
屏幕快照 2016-12-11 下午7.11.20.png

終于不用每次在瀏覽器中敲地址了..
到目前為止,已經(jīng)成功解決了Webpack在“多頁(yè)“應(yīng)用下的開發(fā)問題,接下來(lái)是時(shí)候更進(jìn)一步了。

使用target指定入口應(yīng)用

之前說(shuō)到Webpack中間件在構(gòu)建時(shí)會(huì)把文件都讀取到內(nèi)存中,但是當(dāng)我們的項(xiàng)目越來(lái)越大的時(shí)候,項(xiàng)目下會(huì)有越來(lái)越多的子應(yīng)用,這就造成了另一問題。有時(shí)我們只是在開發(fā)某一個(gè)子App下的代碼,但是Webpack每次都會(huì)把整個(gè)項(xiàng)目打包進(jìn)內(nèi)存,非常浪費(fèi)資源。因此這里我們可以在webpack的配置文件中做點(diǎn)文章,想辦法只打包我們需要的目錄下的JS。

在執(zhí)行npm start的時(shí)候,通過(guò)在命令行里用
target=appName1,appName2 npm start
其中appName為你想要啟動(dòng)的應(yīng)用名稱(名稱在package.json中定義),此時(shí)Webpack配置中的Entry只會(huì)包含target指定應(yīng)用名下的入口JS和HTML,大大縮短了Webpack啟動(dòng)時(shí)間并且減少了內(nèi)存占用。這個(gè)想法最初是在團(tuán)隊(duì)的另一位師兄的代碼中看到的,十分巧妙,關(guān)鍵代碼如下:

var targetEntries = process.env.target;
targetEntries = targetEntries ? targetEntries.split(',') : '';
  targetEntries.forEach(function (value) {
    console.log('應(yīng)用: ', value);
    entryPath = path.join(viewsDir, value);
    entryJson = fse.readJsonSync(path.join(entryPath, '/package.json'));
    entryMap[value] = [path.resolve(path.join(entryPath, entryJson.main))];
    var appName = entryJson.name;
    var tplPath = path.join(entryPath, '/index.html');
    var conf = {
      template: tplPath,
      filename: path.join(appName, 'index.html'),
      inject: 'body',
      chunks: [appName]
    };
    htmlPluginsArr.push(new HtmlWebpackPlugin(conf));
  });
  _.extend(webpackConfig, {
    entry: entryMap,
    output: {
      path: path.join(__dirname, '../build/'),
      filename: '[name]/[name].min.js'
    },
    plugins: [
      new ExtractTextPlugin('[name]/[name].css'),
      new webpack.optimize.UglifyJsPlugin({
        compress: {
          warnings: false,
          drop_console: true
        }
      })
    ].concat(htmlPluginsArr)
  });

通過(guò)這樣一個(gè)簡(jiǎn)單的服務(wù),我們完全不用再使用Webpack-Dev-Server了,甚至我們也可以封裝成一個(gè)相似的plugin。整個(gè)問題的思路和解決方案到此為止咯,如果大家有更好的想法可以補(bǔ)充。

文章首發(fā)于alisec-ued ,個(gè)人博客地址。

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

相關(guān)閱讀更多精彩內(nèi)容

  • 寫在開頭 先說(shuō)說(shuō)為什么要寫這篇文章, 最初的原因是組里的小朋友們看了webpack文檔后, 表情都是這樣的: (摘...
    Lefter閱讀 5,452評(píng)論 4 31
  • 在現(xiàn)在的前端開發(fā)中,前后端分離、模塊化開發(fā)、版本控制、文件合并與壓縮、mock數(shù)據(jù)等等一些原本后端的思想開始...
    Charlot閱讀 5,690評(píng)論 1 32
  • 構(gòu)建一個(gè)小項(xiàng)目——FlyBird,學(xué)習(xí)webpack和react。(本文成文于2017/2/25) 從webpac...
    布蕾布蕾閱讀 17,169評(píng)論 31 98
  • 特喜秦淮水,生愛江上船。 載君便捷去,打工又經(jīng)年。 借問東園葵,獨(dú)愛寂無(wú)言。 雖是無(wú)枝分,時(shí)向太陽(yáng)偏。 幸嫁商人婦...
    悠游魚閱讀 358評(píng)論 3 2
  • 如果有人和你說(shuō):胡歌是渣男。你會(huì)怎么想? 當(dāng)然是毫不猶豫一巴掌打過(guò)去,狠狠懟回去:連胡歌都敢黑?瘋了? 很遺憾,總...
    簡(jiǎn)淺Jian閱讀 534評(píng)論 0 5

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