webpack4學習系列(三):進階

一谷暮、多頁面開發(fā)

如果公司的產(chǎn)品需要做很多活動蒿往,一大堆促銷活動,邀請活動湿弦,周年活動瓤漏,每個活動之間沒有關(guān)聯(lián),每個活動是獨立的颊埃。 那么你們就需要做成多頁面應用蔬充。
那么多頁面還用 react,用 webpack 嗎竟秫? 當然的娃惯,畢竟模塊化,組件化肥败,不管是開發(fā)還是維護上都是舒服太多了。(用 vue 也可以愕提,看公司技術(shù)棧)

核心思路:
1.用 nodejs 遍歷 src 目錄下的文件馒稍,找出多頁面的入口 Js 文件,以及對應的 HTML 模板(生成的文件浅侨,最終要注入到 html 模板才有意義, 因為不同的活動纽谒,模板可能不同,因此做成每個活動一個 html 模板)
2.匹配入口 JS 文件對應的 html 模板如输,然后用 HtmlWebpackPlugin插件 生成 html,并且注入對應的 js 文件
3.把生成的 html plugins 列表鼓黔,放到 webpack 的插件配置中

實現(xiàn)目標:

npm run build //打包所有文件
npm run build  demo1 //打包單個項目demo1
npm run build demo1,demo2  //同時打包多個項目
npm run server //開啟本地服務

step 1:在webpack.config.base.js文件中找出多頁面的入口文件js

const fs = require('fs');
const optimist = require("optimist");
const cateName = optimist.argv.cate;
const entryPath = __dirname + '/src' + '/category/';
let entryObj = {};

if(cateName ===true){
    /*直接輸入npm run build打包所有文件*/
    fs.readdirSync(entryPath).forEach((cateName)=>{
        if(cateName !== "index.html"&&cateName!==".DS_Store"){
            entryObj[cateName+'/'+cateName]=entryPath + cateName + "/" + cateName + '.js';
        }
    });
}else if(cateName.indexOf(",")){
    /*一次性打包多個入口文件以逗號分割 如:npm run build demo1,demo2*/
    var cateNameArray = cateName.split(',');
    for(var i=0;i<cateNameArray.length;i++){
        entryObj[cateNameArray[i] + '/' + cateNameArray[i]] = entryPath + cateNameArray[i] + '/' + cateNameArray[i] + '.js';
    }
}else {
    /*打包單個入口文件*/
    entryObj[cateName+"/"+cateName] = entryPath + cateName + '/' + cateName + '.js';
}

let config = {
    mode: 'none',
    entry: entryObj, //多入口文件以對象的形式設(shè)置每個入口js
    output: {
        libraryTarget: 'umd',
        path:__dirname +'/dist/',
        filename: "[name].js"  //出口js文件
    },
   devServer: {
        contentBase: "./src",//本地服務器所加載的頁面所在的目錄
        port:"8080",//設(shè)置監(jiān)聽端口
        historyApiFallback: true,//不跳轉(zhuǎn)
        inline: true//源文件變更時,實時刷新
    },
   module:{}
   plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].css'  //css文件單獨打包
        })
    ]
}

module.exports = {
    config:config,
    entryObj:entryObj
};

step2:在webpack.config.js文件中匹配入口 JS 文件對應的 html 模板不见,并通過HtmlWebpackPlugin插件 生成 html且引入相應資源澳化。最終插入到webpack配置中

var {config,entryObj} = require('./webpack.config.base');
var pages = Object.keys(entryObj);
var HTMLWebpackPlugin = require('html-webpack-plugin');

pages.forEach(function (pathname) {
    var template_local = entryObj[pathname].replace('.js',".html");
    var entryName = pathname.split("/")[0];
    var conf = {
        filename: entryName+'/' + entryName + '.html', //生成的html存放路徑
        title:entryName,
        template: template_local, //html模板路徑
        inject: true, //js插入的位置,true/'head'/'body'/false
        hash: true, //為靜態(tài)資源生成hash值
        chunks: [pathname],//需要引入的chunk稳吮,不配置就會引入所有頁面的資源
        minify: { //壓縮HTML文件
            removeComments: true, //移除HTML中的注釋
            collapseWhitespace: false //刪除空白符與換行符
        }
    };
    config.plugins.push(new HTMLWebpackPlugin(conf));
});

module.exports= config;

最終多頁面打包效果如圖

QQ20190613-163835@2x.png

二缎谷、UglifyJsPlugin插件

如果你有良好編程實踐,你可能注重代碼的可讀性灶似,因此你在代碼中添加了大量的空白符(制表符列林、空格瑞你、換行符)和注釋。在代碼變得更漂亮的同時希痴,也使得文件的體積大大增加了者甲。另一方面,為了用戶體驗(指減少文件體積)而犧牲可讀性也不可取砌创,手工這么做的話很繁瑣过牙。因此,這兒有一種解決方案供你在項目中選擇纺铭。

Webpack4中引入了一個新參數(shù):mode寇钉。要求總是在配置中指定。如果不指定舶赔,會產(chǎn)生一個警告并退而其次的使用默認值扫倡,默認值就是production。如果你使用 mode:"production"竟纳, Webpack將通過UglifyJSPlugin插件對代碼進行壓縮撵溃。而mode:“development”時的開發(fā)環(huán)境,為了減少打包耗時锥累,往往不對代碼進行壓縮缘挑。
在package.json文件中可以對開發(fā)、生產(chǎn)環(huán)境的打包命令進行設(shè)置

"scripts": {
    "dev": "webpack --mode development --cate",
    "build": "webpack --mode production --cate"
  }

特殊的桶略,在開發(fā)環(huán)境也可以手動配置UglifyJSPlugin插件语淘,進行壓縮

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
{
    mode: 'development',
    optimization: {
        minimize: true,
        minimizer: [
            new UglifyJsPlugin()
        ]
    },
    entry:entryObj,
    output:{},
    devServer:{},
    module:{},
    plugins:{}
}

三、tree shaking

1.是什么际歼?
我們常常碰到這樣的案例惶翻,需要從某文件中命名導出(某一個或幾個變量、函數(shù)鹅心、對象等)吕粗,然而這個文件還有許多其它(我們這次并不需要)的導出,webpack會不管三七二十一簡單粗暴的將整個模塊包含進來旭愧,使得我們最終打包的文件里有了許多不需要的垃圾颅筋。這就到了tree shaking出手的地方了,因為它能幫助我們干掉那些死代碼输枯,大大減少打包的尺寸议泵。

2.使用要求?
1??它依賴于 ES2015 模塊系統(tǒng)中的靜態(tài)結(jié)構(gòu)特性用押,例如 importexport

因此肢簿,必須使用ES6模塊,不能使用其它類型的模塊如CommonJS之流。如果使用Babel的話池充,這里有一個小問題桩引,因為Babel的預案(preset)默認會將任何模塊類型都轉(zhuǎn)譯成CommonJS類型。修正這個問題也很簡單收夸,不是在.babelrc文件中就是在webpack.config.js文件中設(shè)置modules: false就好了

{
  "presets": [
    [
      "@babel/preset-env",
      {"modules": false}
    ],
    "@babel/preset-react"
  ]
}

2??需要使用UglifyJsPlugin插件坑匠,如果在mode:"production"模式,這個插件已經(jīng)默認添加了卧惜,如果在其它模式下厘灼,可以手工添加它。

optimization: {
        usedExports: true,
        sideEffects:true,
        minimize: true,
        minimizer:[new UglifyJsPlugin()]
    }

3??在項目 package.json 文件中咽瓷,添加一個 "sideEffects" 入口设凹。
在一個純粹的 ESM 模塊世界中,識別出哪些文件有副作用很簡單茅姜。然而闪朱,我們的項目無法達到這種純度,所以钻洒,此時有必要向 webpack 的 compiler 提供提示哪些代碼是“純粹部分”奋姿。

這種方式是通過 package.json 的 "sideEffects" 屬性來實現(xiàn)的。

{
  "name": "your-project",
  "sideEffects": false
}

如同上面提到的素标,如果所有代碼都不包含副作用称诗,我們就可以簡單地將該屬性標記為 false,來告知 webpack头遭,它可以安全地刪除未用到的 export 導出寓免。
如果你的代碼確實有一些副作用,那么可以改為提供一個數(shù)組:

{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js",
    "*.css"
  ]
}

四任岸、使用 source map

當 webpack 打包源代碼時再榄,可能會很難追蹤到錯誤和警告在源代碼中的原始位置。例如享潜,如果將三個源文件(a.js, b.jsc.js)打包到一個 bundle(bundle.js)中,而其中一個源文件包含一個錯誤嗅蔬,那么堆棧跟蹤就會簡單地指向到 bundle.js剑按。這并通常沒有太多幫助,因為你可能需要準確地知道錯誤來自于哪個源文件澜术。

為了更容易地追蹤錯誤和警告艺蝴,JavaScript 提供了 source map 功能,將編譯后的代碼映射回原始源代碼鸟废。如果一個錯誤來自于 b.js猜敢,source map 就會明確的告訴你。

source map 有很多不同的選項可用,請務必仔細閱讀它們缩擂,以便可以根據(jù)需要進行配置鼠冕。

對于本指南,我們使用 inline-source-map 選項胯盯,這有助于解釋說明我們的目的(僅解釋說明懈费,不要用于生產(chǎn)環(huán)境):

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

  module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
+   devtool: 'inline-source-map',
    plugins: [
      new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Development'
      })
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

現(xiàn)在,讓我們來做一些調(diào)試博脑,在 Test.js 文件中生成一個錯誤:

import React, {Component} from 'react'

import './Test.less' ;

class Test extends Component{
    render() {
        return (
            <div className="contianer" onClick={()=>{cosnole.error('I get called from print.js!');}}>
                <div className="car car1">1</div>
                <div className="car car2">2</div>
                <div className="car car3">3</div>
                <div className="car car4">4</div>
            </div>
        )}
}

export default Test

在在瀏覽器打開最終生成的 index.html 文件憎乙,點擊按鈕,并且在控制臺查看顯示的錯誤叉趣。錯誤應該如下


error.png

這是非常有幫助的泞边,因為現(xiàn)在我們知道了,所要解決的問題的確切位置疗杉。

五阵谚、選擇一個開發(fā)工具

每次要編譯代碼時,手動運行 npm run build 就會變得很麻煩乡数。

webpack 中有幾個不同的選項椭蹄,可以幫助你在代碼發(fā)生變化后自動編譯代碼:

多數(shù)場景中,你可能需要使用 webpack-dev-server净赴,但是不妨探討一下以下的所有選項
1??webpack's Watch Mode
使用觀察模式绳矩,你可以指示 webpack "watch" 依賴圖中的所有文件以進行更改。如果其中一個文件被更新玖翅,代碼將被重新編譯翼馆,所以你不必手動運行整個構(gòu)建。

我們添加一個用于啟動 webpack 的觀察模式的 npm script 腳本:
package.json

{
    "name": "development",
    "version": "1.0.0",
    "description": "",
    "main": "webpack.config.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
+     "watch": "webpack --watch",
      "build": "webpack"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
      "clean-webpack-plugin": "^0.1.16",
      "css-loader": "^0.28.4",
      "csv-loader": "^2.1.1",
      "file-loader": "^0.11.2",
      "html-webpack-plugin": "^2.29.0",
      "style-loader": "^0.18.2",
      "webpack": "^3.0.0",
      "xml-loader": "^1.2.1"
    }
  }

現(xiàn)在金度,你可以在命令行中運行 npm run watch应媚,就會看到 webpack 編譯代碼,然而卻不會退出命令行猜极。這是因為 script 腳本還在觀察文件≈薪現(xiàn)在,修改并保存文件并檢查終端窗口。應該可以看到 webpack 自動重新編譯修改后的模塊跟伏!
2??webpack-dev-server
瀏覽器自動加載頁面丢胚。如果現(xiàn)在修改和保存任意源文件,web 服務器就會自動重新加載編譯后的代碼
3??webpack-dev-middleware
webpack-dev-middleware 是一個容器(wrapper)受扳,它可以把 webpack 處理后的文件傳遞給一個服務器(server)携龟。 webpack-dev-server 在內(nèi)部使用了它,同時勘高,它也可以作為一個單獨的包來使用峡蟋,以便進行更多自定義設(shè)置來實現(xiàn)更多的需求坟桅。接下來是一個 webpack-dev-middleware 配合 express server 的示例。

npm install --save-dev express webpack-dev-middleware
const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  const CleanWebpackPlugin = require('clean-webpack-plugin');

  module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
    devtool: 'inline-source-map',
    plugins: [
      new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Output Management'
      })
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist'),
+     publicPath: '/'
    }
  };

server.js

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');

const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);

// Tell express to use the webpack-dev-middleware and use the webpack.config.js
// configuration file as a base.
app.use(webpackDevMiddleware(compiler, {
  publicPath: config.output.publicPath
}));

// Serve the files on port 3000.
app.listen(3000, function () {
  console.log('Example app listening on port 3000!\n');
});

package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack --mode development --cate",
    "build": "webpack --mode production --cate",
    "server": "webpack-dev-server --open --cate",
    "watch": "webpack --watch --cate",
    "express":"node server/server.js --cate"
  },

現(xiàn)在蕊蝗,在你的終端執(zhí)行 npm run express.打開瀏覽器仅乓,跳轉(zhuǎn)到 http://localhost:3000,你應該看到你的webpack 應用程序已經(jīng)運行匿又!

六方灾、生產(chǎn)環(huán)境構(gòu)建

開發(fā)環(huán)境(development)和生產(chǎn)環(huán)境(production)的構(gòu)建目標差異很大。在開發(fā)環(huán)境中碌更,我們需要具有強大的裕偿、具有實時重新加載(live reloading)或熱模塊替換(hot module replacement)能力的 source map 和 localhost server。而在生產(chǎn)環(huán)境中痛单,我們的目標則轉(zhuǎn)向于關(guān)注更小的 bundle嘿棘,更輕量的 source map,以及更優(yōu)化的資源旭绒,以改善加載時間鸟妙。由于要遵循邏輯分離,我們通常建議為每個環(huán)境編寫彼此獨立的 webpack 配置挥吵。

雖然重父,以上我們將生產(chǎn)環(huán)境開發(fā)環(huán)境做了略微區(qū)分,但是忽匈,請注意房午,我們還是會遵循不重復原則(Don't repeat yourself - DRY),保留一個“通用”配置丹允。為了將這些配置合并在一起郭厌,我們將使用一個名為 webpack-merge 的工具。通過“通用”配置雕蔽,我們不必在環(huán)境特定(environment-specific)的配置中重復代碼折柠。

我們先從安裝 webpack-merge 開始:

npm install --save-dev webpack-merge

webpack.common.js

+ const path = require('path');
+ const CleanWebpackPlugin = require('clean-webpack-plugin');
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
+
+ module.exports = {
+   entry: {
+     app: './src/index.js'
+   },
+   plugins: [
+     new CleanWebpackPlugin(['dist']),
+     new HtmlWebpackPlugin({
+       title: 'Production'
+     })
+   ],
+   output: {
+     filename: '[name].bundle.js',
+     path: path.resolve(__dirname, 'dist')
+   }
+ };

webpack.dev.js

+ const merge = require('webpack-merge');
+ const common = require('./webpack.common.js');
+
+ module.exports = merge(common, {
+   devtool: 'inline-source-map',
+   devServer: {
+     contentBase: './dist'
+   }
+ });

webpack.prod.js

+ const merge = require('webpack-merge');
+ const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
+ const common = require('./webpack.common.js');
+
+ module.exports = merge(common, {
+   plugins: [
+     new UglifyJSPlugin()
+   ]
+ });

現(xiàn)在,在 webpack.common.js 中批狐,我們設(shè)置了 entryoutput 配置扇售,并且在其中引入這兩個環(huán)境公用的全部插件。在 webpack.dev.js 中嚣艇,我們?yōu)榇谁h(huán)境添加了推薦的 devtool(強大的 source map)和簡單的 devServer 配置缘眶。最后,在 webpack.prod.js 中髓废,我們引入了之前在 tree shaking 指南中介紹過的 UglifyJSPlugin

package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack --config webpack.dev.js --cate",
    "build": "webpack --config webpack.prod.js --cate",
    "server": "webpack-dev-server --open --config webpack.dev.js --cate",
    "watch": "webpack --watch --cate",
    "express":"node server/server.js --cate"
  }

七该抒、外部擴展(externals)

在日常的項目開發(fā)中慌洪,我們會用到各種第三方庫來提高效率顶燕,但隨之帶來的問題就是打包后的js文件體積過大,導致加載時空白頁時間過長冈爹,給用戶的體驗太差涌攻。

externals 配置選項提供了「從輸出的 bundle 中排除依賴」的方法。防止將某些 import 的包(package)打包到 bundle 中频伤,而是在運行時(runtime)再去從外部獲取這些擴展依賴(external dependencies)恳谎。

例如,從 CDN 引入 jQuery憋肖,而不是把它打包:

webpack.config.js

module.exports = {
  //...
  externals: {
    jquery: 'jQuery'
  }
};

index.html

<script
  src="https://code.jquery.com/jquery-3.1.0.js"
  integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
  crossorigin="anonymous">
</script>

八因痛、解析(resolve)

module.exports = {
  //...
  resolve: {
        extensions: ['.js', '.jsx'],
        alias: {
            page: srcPath+'/page',
            components: srcPath+'/components',
            images: srcPath+'/images',
            mock: srcPath+'/mock',
            skin:srcPath+'/skin',
            util:srcPath+'/util',
        }
};

1?? resolve.alias
創(chuàng)建 import 或 require 的別名,來確保模塊引入變得更簡單岸更。
現(xiàn)在鸵膏,替換「在導入時使用相對路徑」這種方式,就像這樣:

import {add} from '../../util/util'

你可以這樣使用別名:

import {add} from 'util/util'

從而使得引用更簡單
2?? resolve.extensions
能夠使用戶在引入模塊時不帶擴展

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末怎炊,一起剝皮案震驚了整個濱河市谭企,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌评肆,老刑警劉巖债查,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瓜挽,居然都是意外死亡盹廷,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門秸抚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來速和,“玉大人,你說我怎么就攤上這事剥汤〉叻牛” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵吭敢,是天一觀的道長碰凶。 經(jīng)常有香客問我,道長鹿驼,這世上最難降的妖魔是什么欲低? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮畜晰,結(jié)果婚禮上砾莱,老公的妹妹穿的比我還像新娘。我一直安慰自己凄鼻,他們只是感情好腊瑟,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布聚假。 她就那樣靜靜地躺著,像睡著了一般闰非。 火紅的嫁衣襯著肌膚如雪膘格。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天财松,我揣著相機與錄音瘪贱,去河邊找鬼。 笑死辆毡,一個胖子當著我的面吹牛菜秦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胚迫,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼喷户,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了访锻?” 一聲冷哼從身側(cè)響起褪尝,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎期犬,沒想到半個月后河哑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡龟虎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年璃谨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鲤妥。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡佳吞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出棉安,到底是詐尸還是另有隱情底扳,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布贡耽,位于F島的核電站衷模,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蒲赂。R本人自食惡果不足惜阱冶,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望滥嘴。 院中可真熱鬧木蹬,春花似錦、人聲如沸若皱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至意系,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間饺汹,已是汗流浹背蛔添。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留兜辞,地道東北人迎瞧。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像逸吵,于是被迫代替她去往敵國和親凶硅。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353