一谷暮、多頁面開發(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;
最終多頁面打包效果如圖
二缎谷、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)特性用押,例如 import
和 export
因此肢簿,必須使用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.js
和 c.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 文件憎乙,點擊按鈕,并且在控制臺查看顯示的錯誤叉趣。錯誤應該如下
這是非常有幫助的泞边,因為現(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è)置了 entry
和 output
配置扇售,并且在其中引入這兩個環(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
能夠使用戶在引入模塊時不帶擴展