
> Contents
- 前言
- webpack4圖片打包的問題
- webpack4樣式表打包分離
- 應(yīng)用構(gòu)建工具electron-builder配置
- 應(yīng)用構(gòu)建工具electron-builder的問題
前言
前一篇文章主要記錄了開發(fā)環(huán)境的搭建和一些開發(fā)時(shí)遇到的問題,這篇文章主要說說自己在coding work之后進(jìn)行應(yīng)用打包時(shí)遇到的問題(webpack打包和electron打包),項(xiàng)目地址。
webpack4圖片打包的問題
1. jsx中聲明的img:src不能被webpack識別和打包
在jsx中使用圖片時(shí),如下:
<div className="install-item-image" onClick={() => {showTerminalInfo(item.label)}}>
<Dimmer active={loading} inverted>
<Loader size="tiny">{ loadingLable }</Loader>
</Dimmer>
<img alt="error" src={item.url} />
</div>
這里img:src使用了一個(gè)變量,但無論是變量還是字符串,webpack在打包的時(shí)候都不能根據(jù)我們引用的資源路徑在dist目錄下生成正確的資源引用路徑結(jié)構(gòu),所以只能在我們需要引用圖片的地方,手動(dòng)require引入,如下:
...
const imgSrc = require('path/to/img');
...
<img src={imgSrc}>
那如果img:src真的是變量而且需要一次引入多個(gè)那怎么辦,如果你說想用for循環(huán)引入可以不,這其實(shí)是不行的,因?yàn)閣ebpack打包的時(shí)候是識別不了你for循環(huán)內(nèi)定義的變量的。引入辦法如下,可以用正則表達(dá)式對一個(gè)文件夾內(nèi)的所有文件進(jìn)行匹配引入,并且可以在項(xiàng)目任意位置引入:
// 匹配1:只匹配圖片
const requireContext = require.context('resources/install', true, /^\.\/.*\.(jpg|png)$/);
// 匹配2:匹配所有文件
const requireContext = require.context('resources/install', true, /.*/);
requireContext.keys().map(requireContext);
2. 生產(chǎn)環(huán)境和開發(fā)環(huán)境的publicPath配置
關(guān)于publicPath這里有一篇說得比較清楚的文章
output.path : 硬盤上的路徑,也就是你打算把文件打包到你的哪個(gè)目錄,與發(fā)布時(shí)的路徑完全無關(guān)。
output.publicPath: 主要用來轉(zhuǎn)換url中的相對路徑的。如果你引用到包含url的資源,一定要配置output.publicPath,配置了此項(xiàng),webpack在打包時(shí)才能根據(jù)配置動(dòng)態(tài)修改uri中的相對值。比如果你將所有打包生成好的文件托管在服務(wù)器上,訪問格式是
www.yourhost.com/dist/index.html的話,那publicPath就需要指定為/dist/。
webpack-dev-server的publicPath默認(rèn)是
/,也就是在開發(fā)環(huán)境下webpack-dev-server在內(nèi)存中生成的bundle.js文件路徑是/,我們在瀏覽器中訪問localhost:3000/bundle.js就能看見了,如果你在生產(chǎn)環(huán)境下的訪問路徑是localhost:3000/dist/bundle.js,就需要指定webpack-dev-server的publicPath為/dist/,這只是一個(gè)內(nèi)存中虛擬的路徑映射,目的是為了統(tǒng)一開發(fā)環(huán)境和生產(chǎn)環(huán)境的路徑問題。開發(fā)環(huán)境下:
webpack.config.js
...
devtool: 'source-map',
entry: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
'./app/index',
],
mode: 'development',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
},
...
- 生產(chǎn)環(huán)境下:
webpack.prod.config.js
...
entry: [
'./app/index',
],
mode: 'production',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
},
...
3. 統(tǒng)一生產(chǎn)環(huán)境和開發(fā)環(huán)境的資源引用路徑
可以在webpack.config文件中指定resolve.alias來將一個(gè)絕對路徑重命名,然后在項(xiàng)目任意位置直接使用重命名路徑就行了,不用在import的時(shí)候搞很多相對路徑聲明../../../,如下:
- 聲明:
webpack.config.js
...
resolve: {
alias: {
resources: path.resolve(__dirname, 'resources'),
app: path.resolve(__dirname, 'app'),
},
},
...
- 使用:
<img src="resources/install/albert.png"}>
webpack4樣式表打包分離
1. css屬性backgroup-image: url(...)的路徑統(tǒng)一
我們在webpack.config中指定resolve.alias之后,如果你要在css屬性中引用那個(gè)絕對路徑的別名的話,需要在img:url字符前多加一個(gè)~路徑轉(zhuǎn)換符號,來讓webpack為你自動(dòng)替換路徑,如下:
.router-left-background {
background-image: url(~resources/public/gohome.jpg); /* The image used */
background-color: #f6f6f6; /* Used if the image is unavailable */
background-position: center; /* Center the image */
background-repeat: no-repeat; /* Do not repeat the image */
background-size: cover; /* cover size */
}
2. 將樣式表從bundle.js文件中分離
如果項(xiàng)目比較大的話,直接將樣式表壓縮進(jìn)bundle.js文件中會導(dǎo)致頁面首頁加載時(shí)間比較長,這里我們使用extract-text-webpack-pluginwebpack插件分離樣式表,然后在index.html引入樣式表,這樣頁面加載的時(shí)候?yàn)g覽器就會發(fā)送異步請求來同時(shí)加載bundle.js文件和css文件,極大地提高加載速度。
- index.html
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>electronux</title>
<link rel="stylesheet" href="style.scss.css">
<link rel="stylesheet" href="style.css">
<base href="./">
</head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>
- 開發(fā)環(huán)境下webpack插件
extract-text-webpack-plugin配置
這里的插件publicPath需要根據(jù)webpack-dev-server的publicPath配置(默認(rèn)是/),如果我們的樣式表會加載外部文件(例如圖片和字體文件)的話,那個(gè)實(shí)際資源請求路徑就會根據(jù)這里的publicPath來計(jì)算得出。
webpack.config.js
...
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
// 拆分樣式文件
const extractSass = new ExtractTextPlugin({
filename: 'style.scss.css',
});
const extractCss = new ExtractTextPlugin({
filename: 'style.css',
});
...
module.exports = {
...
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
},
{
test: /\.css$/,
use: extractCss.extract({
fallback: 'style-loader',
use: 'css-loader',
publicPath: '/',
}),
},
{
test: /\.scss$/,
use: extractSass.extract({
use: [{
loader: 'css-loader',
}, {
loader: 'sass-loader',
}],
fallback: 'style-loader', // 在開發(fā)環(huán)境使用 style-loader
publicPath: '/',
}),
},
...
],
...
},
plugins: [
extractSass,
extractCss,
...
],
...
- 生產(chǎn)環(huán)境下webpack插件
extract-text-webpack-plugin配置
生產(chǎn)環(huán)境下需要將publicPath設(shè)置為我們打包后生成的dist目錄,不然css中引用的外部資源如圖片等是不能生成到dist目錄中的。
webpack.prod.config.js
...
module.exports = {
...
module: {
rules: [
...
{
test: /\.css$/,
use: extractCss.extract({
fallback: 'style-loader',
use: 'css-loader',
publicPath: path.join(__dirname, 'dist/'),
}),
},
{
test: /\.scss$/,
use: extractSass.extract({
use: [{
loader: 'css-loader',
}, {
loader: 'sass-loader',
}],
fallback: 'style-loader', // 在開發(fā)環(huán)境使用 style-loader
publicPath: path.join(__dirname, 'dist/'),
}),
},
...
應(yīng)用構(gòu)建工具electron-builder配置
{
"appId": "com.nojsja.electronux",
"copyright": "nojsja",
"productName": "electronux",
"asar": false,
"directories": {
"buildResources": "build-assets/",
"output": "build/"
},
"files": ["package.json", "index.js", "dist/", "app/", "node_modules/"],
"linux": {
"icon": "resources",
"category": "System",
"description": "A System Management Tool Build For Manjaro Linux 17",
"synopsis": "electronux",
"target": ["zip"]
}
}
electron-builder打包主要解決兩個(gè)問題,一是怎么打包前端界面代碼目錄dist下的資源(渲染進(jìn)程代碼),二是怎么打包由根目錄下的index.js文件引入的資源(主進(jìn)程代碼)。配置文件中files參數(shù)項(xiàng)配置的就是所有需要最終打包進(jìn)我們應(yīng)用的所有文件了。
- package.json -- 整個(gè)應(yīng)用程序的依賴配置文件
- index.js -- 主進(jìn)程入口文件
- dist -- 渲染進(jìn)程資源文件
- app -- 運(yùn)行時(shí)引用的源代碼和資源目錄
- node_modules -- 運(yùn)行時(shí)引用的第三方模塊和資源目錄
配置說明詳細(xì)見官方文檔
應(yīng)用構(gòu)建工具electron-builder的問題
1. 國內(nèi)墻導(dǎo)致打包工具依賴下載失敗
運(yùn)行electron-builder的時(shí)候會首先下載各個(gè)打包依賴,但是如果直接下載是會失敗的(下載源文件存在github)。但我這邊終端是用polipo配置了http-proxy的,下載的時(shí)候還是很慢,最后仍會導(dǎo)致下載失敗,這個(gè)真的比較頭痛,我索性將git倉庫clone到自己搭建的vps虛擬機(jī)上(日本節(jié)點(diǎn)),然后在服務(wù)器上運(yùn)行一次打包命令,再把~/.cache/electron-builder、~/.cache/electron這兩個(gè)打包工具生成的目錄直接下載到本地對應(yīng)的目錄下,最后在本地運(yùn)行打包命令的時(shí)候就不會再去下載依賴了。
2. 打包成AppImage后在運(yùn)行時(shí)不能使用chmod更改文件權(quán)限的問題
先來看一段Linux上常見的AppImage打包應(yīng)用的定義:
AppImage不把Linux應(yīng)用程序安裝在文件系統(tǒng)相應(yīng)的目錄中,相反,它沒有進(jìn)行實(shí)際的安裝,AppImage文件只是個(gè)壓縮文件,在它運(yùn)行時(shí)候
掛載,用AppImage打包的程序,一個(gè)程序就是一個(gè)文件。
在我的應(yīng)用中需要執(zhí)行一些shell腳本獲取系統(tǒng)信息,但是這些腳本在第一次運(yùn)行的時(shí)候是需要使用node.js中fs模塊的fs.chmod方法對shell腳本進(jìn)行賦予可執(zhí)行權(quán)限的(chmod 755),但是AppImage運(yùn)行時(shí)是不允許動(dòng)態(tài)更改文件屬性的,所有掛載的Applmage文件都是只讀的,無奈,我放棄了將應(yīng)用打包成AppImage這種格式。
為了便于測試可以直接打包成zip文件,解壓后就能運(yùn)行,如果要安裝到不同的發(fā)行版的話還能打包成pacman、deb、rpm、tar.gz等文件。
3. arar加密打包時(shí)造成絕對路徑查找失敗
electron-builder的打包參數(shù)中有一個(gè)參數(shù)是asar: true/false,如果指定了為true的話打包后的壓縮包內(nèi)的源代碼是會被arar加密的,這個(gè)對一些不開源的代碼來說還是很有必要,但在我這個(gè)應(yīng)用中應(yīng)用在運(yùn)行的時(shí)候會動(dòng)態(tài)加載一些自定義的模塊文件,如果你加載的路徑用的是絕對路徑的話,這個(gè)加載過程就會失敗,因?yàn)槿绻麊⒂昧薬rar的話,我們資源目錄下所有的源代碼都只是一個(gè)加密的壓縮包,此時(shí)你是不能通過系統(tǒng)的絕對路徑來找到我們要引入的那個(gè)模塊代碼路徑的,當(dāng)然如果你手動(dòng)解壓arar壓縮包的話就能看到所有源代碼的目錄結(jié)構(gòu)了。
4. 外部引用資源(img:src / css:url)的相對路徑和絕對路徑
html demo:
<div className="install-item-image" onClick={() => {showTerminalInfo(item.label)}}>
<Dimmer active={loading} inverted>
<Loader size="tiny">{ loadingLable }</Loader>
</Dimmer>
<img alt="error" src={item.url} />
</div>
css demo:
.router-left-background {
background-image: url(~resources/public/gohome.jpg); /* The image used */
background-color: #f6f6f6; /* Used if the image is unavailable */
background-position: center; /* Center the image */
background-repeat: no-repeat; /* Do not repeat the image */
background-size: cover; /* cover size */
}
如果我們正確地通過webpack打包了前端界面的代碼,在dist目錄下生成了正確的資源目錄結(jié)構(gòu),然后嘗試使用electron index.js命令來模擬生產(chǎn)環(huán)境下應(yīng)用的運(yùn)行(使用file協(xié)議加載dist目錄下的資源),發(fā)現(xiàn)代碼中所有的引用資源請求都會失敗。這是因?yàn)閑lectron在生產(chǎn)環(huán)境下是使用file協(xié)議來加載文件的,首先我們在在執(zhí)行了electron index.js命令后,electron窗口會按照我們定義的路徑結(jié)構(gòu)去查找index.html文件,然后加載主窗口,這個(gè)過程沒有問題,如下:
window.loadURL(url.format({
pathname: path.resolve(__dirname, 'dist', 'index.html'),
protocol: 'file:',
slashes: true,
}));
然后在這個(gè)index.html中的css圖片請求和react組件的圖片請求就是問題之處了,因?yàn)槲覀儧]有指定當(dāng)前工作目錄,file協(xié)議加載文件時(shí)就會直接從系統(tǒng)根目錄開始根據(jù)資源目錄結(jié)構(gòu)查找了,實(shí)際上,我們的所有資源都是在dist文件夾下的,而不是系統(tǒng)根目錄/,解決辦法是在我們的index.html文件里面指定一個(gè)base標(biāo)簽,指明當(dāng)前工作目錄就行了,如下:
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>electronux</title>
<link rel="stylesheet" href="style.scss.css">
<link rel="stylesheet" href="style.css">
<base href="./">
</head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>
5. 使用node.js對shell腳本賦予可執(zhí)行權(quán)限
node.js的fs模塊可以為文件賦予可執(zhí)行權(quán)限,并且fs.chmod命令不用額外申請權(quán)限,估計(jì)是如果當(dāng)前用戶可以以root權(quán)限運(yùn)行文件的話,node會自動(dòng)為你獲取權(quán)限。
- 定義fsChmod模塊遞歸為一個(gè)目錄內(nèi)的所有文件授予權(quán)限:
const fs = require('fs');
const path = require('path');
function chmod(target, opstr) {
if (fs.statSync(target).isDirectory()) {
const files = fs.readdirSync(target);
if (files.length) {
files.forEach((file) => {
chmod(path.join(target, file), opstr);
});
}
} else {
fs.chmodSync(target, opstr);
}
}
function fsChmod(dir, opstr) {
chmod(dir, opstr);
}
module.exports = fsChmod;
- 在fsChmod同級目錄下定義shell授權(quán)模塊
這樣子的話會避開絕對路徑查找的問題
const path = require('path');
const fsChmod = require('./fs-chmod');
function fsChmodShell() {
fsChmod(path.join(__dirname, '../shell'), 0o711);
}
module.exports = fsChmodShell;
- 項(xiàng)目index.js中引入執(zhí)行
注意:請盡量不要在項(xiàng)目的index.js文件中進(jìn)行運(yùn)行時(shí)絕對路徑查詢(使用path.resolve),因?yàn)樵陂_發(fā)環(huán)境下我們的代碼目錄結(jié)構(gòu)和electron-builder打包后生產(chǎn)環(huán)境下的的應(yīng)用代碼結(jié)構(gòu)是不一樣的,比如開發(fā)環(huán)境下,index.js文件位于項(xiàng)目根目錄/下,shell文件夾(存放shell scripts)的路徑是/app/service/shell,經(jīng)過electron-builder打包后,index.js(實(shí)際上被編譯成了一個(gè)可執(zhí)行文件 )仍然位于根目錄/下,但是shell文件夾位置卻變成了/resources/app/app/shell,這樣子如果在index.js文件中對shell文件夾進(jìn)行絕對路徑查詢的話就會發(fā)生嚴(yán)重錯(cuò)誤。electron-builder打包后的源代碼會被放到資源目錄/resources/app下,位于資源目錄下的代碼是可以進(jìn)行運(yùn)行時(shí)絕對路徑查詢(前提是沒有開啟arar源代碼加密)和相對路徑查詢的。
const fsChmodShell = require('./app/services/middleware/fs-chmod-shell.js');
fsChmodShell();