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

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

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

舉例來說,我們現(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īng)用“的項(xiàng)目集嵌,最好的方案是在開發(fā)時(shí)能通過路由切換到對(duì)應(yīng)的SPA應(yīng)用下萝挤,即對(duì)應(yīng)的入口JS和HTML下御毅,為此我們需要一些小技巧。

解決方案

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

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

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

server.png
server.png

圖片中的multientry-dev-server就是接下來我們要?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');

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

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

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

使用webpack中間件打包并不會(huì)真正的生成文件,它會(huì)把文件載入到內(nèi)存中凳鬓。
為了能通過路由指定跳轉(zhuǎn)到對(duì)應(yīng)的入口JS和HTML茁肠,我們?cè)谛柙陧?xiàng)目中做一些約定。假設(shè)項(xiàng)目入口為apps目錄缩举,該目錄下的每一個(gè)子目錄對(duì)應(yīng)一個(gè)SPA應(yīng)用垦梆,在子目錄中需要通過一個(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, 至此便可以通過路由指定到對(duì)應(yīng)App的入口辽慕。

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

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


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

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

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

之前說到Webpack中間件在構(gòu)建時(shí)會(huì)把文件都讀取到內(nèi)存中人灼,但是當(dāng)我們的項(xiàng)目越來越大的時(shí)候,項(xiàng)目下會(huì)有越來越多的子應(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í)候烙样,通過在命令行里用
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)
  });

通過這樣一個(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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市恼蓬,隨后出現(xiàn)的幾起案子惊完,更是在濱河造成了極大的恐慌,老刑警劉巖处硬,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件小槐,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)凿跳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門件豌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人控嗜,你說我怎么就攤上這事茧彤。” “怎么了疆栏?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵曾掂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我壁顶,道長(zhǎng)珠洗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任若专,我火速辦了婚禮许蓖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘富岳。我一直安慰自己蛔糯,他們只是感情好拯腮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布窖式。 她就那樣靜靜地躺著,像睡著了一般动壤。 火紅的嫁衣襯著肌膚如雪萝喘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天琼懊,我揣著相機(jī)與錄音阁簸,去河邊找鬼。 笑死哼丈,一個(gè)胖子當(dāng)著我的面吹牛启妹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播醉旦,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼饶米,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了车胡?” 一聲冷哼從身側(cè)響起檬输,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎匈棘,沒想到半個(gè)月后丧慈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡主卫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年逃默,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鹃愤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡完域,死狀恐怖昼浦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情筒主,我是刑警寧澤关噪,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站乌妙,受9級(jí)特大地震影響使兔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜藤韵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一虐沥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泽艘,春花似錦欲险、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至然低,卻和暖如春喜每,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背雳攘。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工带兜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吨灭。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓刚照,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親喧兄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子无畔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

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