【原創】從零開始搭建Electron+Vue+Webpack項目框架,一套代碼,同時構建客戶端、web端(二)

導航:javascript

(一)Electron跑起來
(二)從零搭建Vue全家桶+webpack項目框架
(三)Electron+Vue+Webpack,聯合調試整個項目(未完待續)
(四)Electron配置潤色(未完待續)
(五)預加載及自動更新(未完待續)
(六)構建、發佈整個項目(包括client和web)(未完待續)css

摘要上篇文章說到了如何新建工程,並啓動一個最簡單的Electron應用。「跑起來」了Electron,那就接着把Vue「跑起來」吧。有一點須要說明的是,webpack是貫穿這個系列始終的,我也是本着學習的態度,去介紹、總結一些經常使用到的配置及思路,有不恰當的地方,或者待優化的地方,歡迎留言。項目完整代碼:https://github.com/luohao8023/electron-vue-templatehtml

下面開始~~~vue

1、安裝依賴
vue、webpack:很少說了
vue-loader:解析、轉換.vue文件
vue-template-compiler:vue-loader的依賴包,但又獨立於vue-loader,簡單的說,做用就是使用這個插件將template語法轉爲render函數
webpack-dev-server:快速搭建本地運行環境的工具
webpack-hot-middleware:搭配webpack-dev-server使用,實現熱更新
chalk:命令行輸出帶有顏色的內容
依賴包就介紹這麼多,後面須要什麼能夠自行下載,這裏很少贅述了。
 
2、完善工程目錄
  
webpack.render.config.js:渲染進程打包配置
dev.js:本地調試腳本
views:頁面代碼
index.js:vue工程入口文件
index.ejs:打包生成html文件時的模板
3、配置Vue工程
一、編寫入口文件,render>index.js
import Vue from 'vue';
import index from './views/index.vue';

//取消 Vue 全部的日誌與警告
Vue.config.silent = true;
new Vue({
    el: '#app',
    render: h => h(index)
});

二、編寫根組件,render>views>index.vuejava

<template>
    <div class="content">
        <h1>Welcome to electron-vue-template!</h1>
    </div>
</template>

<script>
export default {}
</script>
<style></style>

三、編寫html模板文件,render>index.ejs,webpack解析、打包vue文件時,以此模板生成html文件node

<!DOCTYPE html>
<html lang="zh-CN">
<!--template for 2019年10月30日-->
<!--<%= new Date().getFullYear()+'/'+(new Date().getMonth()+1)+'/'+new Date().getDate()+' '+new Date().getHours()+':'+new Date().getMinutes() %>-->
<head>
    <meta charset="UTF-8">
    <title>模板文件</title>
    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
    <meta HTTP-EQUIV="pragma" CONTENT="no-cache">
    <meta HTTP-EQUIV="Cache-Control" CONTENT="no-store, must-revalidate">
    <meta HTTP-EQUIV="expires" CONTENT="Wed, 26 Feb 1997 08:21:57 GMT">
    <meta HTTP-EQUIV="expires" CONTENT="0">
</head>
<body>
    <div id="app"></div>
</body>
</html>

四、編寫webpack配置文件,builder>webpack.render.config.js,建議按照本文這種方式,把配置文件單獨抽出來,這樣的話,本地調試和打包能夠共用一套配置,只須要傳遞不一樣參數就能夠了,不要把全部的配置和打包邏輯寫在一個文件裏,太長、太亂、太難維護webpack

/*
Name:    渲染進程配置
Author: haoluo
Date:   2019-10-30
 */
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const devMode = process.env.NODE_ENV === 'development';

module.exports = {
    mode: devMode ? 'development' : 'production',
    entry: {
        main: './src/render/index.js'
    },
    output: {
        path: path.join(__dirname, '../app/'),
        publicPath: devMode ? '/' : '',
        filename: './js/[name].[hash:8].js'
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                exclude: /node_modules/,
                loader: 'vue-loader'
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/render/index.ejs',
            filename: './index.html',
            title: 'electron-vue-template',
            inject: false,
            hash: true,
            mode: devMode
        })
    ]
}

適當解釋一下:ios

mode:環境參數,針對不一樣的環境,webpack內部有一些不一樣的機制,並對相應環境作相應的優化git

entry:入口,webpack執行構建的第一步將從入口文件開始,遞歸查詢並解析全部依賴的模塊。配置方式有多種,可參考webpack文檔,這裏咱們配置的路徑是'./src/render/index.js',意思是src目錄下,render文件夾下的index.js,而webpack配置文件是在builder文件夾下,那這個「./」的相對路徑究竟是相對於誰呢?這就得說一下webpack中的路徑問題了,context 是 webpack 編譯時的基礎目錄,入口起點(entry)會相對於此目錄查找,那這個context又是個什麼東西?webpack源碼有關默認配置中有這麼一句話github

this.set("context", process.cwd());

這就是context的默認值,工程的根目錄,那這個entry的配置就很好理解了。

output:打包的輸入配置,路徑建議設置爲絕對路徑。

module和plugins就很少說了。

五、編寫本地調試腳本

/**
* Tip:    調試渲染進程
* Author: haoluo
* Data:   2019-10-30
**/
process.env.NODE_ENV = 'development';
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const webpackHotMiddleware = require('webpack-hot-middleware');
const chalk = require('chalk');
const http = require('http');
function devRender() {
    console.log('啓動渲染進程調試......');
    const webpackDevConfig = require('./webpack.render.config.js');
    const compiler = webpack(webpackDevConfig);
    new WebpackDevServer(
        compiler, {
            contentBase: webpackDevConfig.output.path,
            publicPath: webpackDevConfig.output.publicPath,
            open: true,//打開默認瀏覽器
            inline: true,//刷新模式
            hot: true,//熱更新
            quiet: true,//除第一次編譯外,其他不顯示編譯信息
            progress: true,//顯示打包進度
            setup(app) {
                app.use(webpackHotMiddleware(compiler));
                app.use('*', (req, res, next) => {
                    if (String(req.originalUrl).indexOf('.html') > 0) {
                        getHtml(res);
                    } else {
                        next();
                    }
                });
            }
        }
    ).listen(8099, function(err) {
        if (err) return console.log(err);
        console.log(`Listening at http://localhost:8099`);
    });
    compiler.hooks.done.tap('doneCallback', (stats) => {
        const compilation = stats.compilation;
        Object.keys(compilation.assets).forEach(key => console.log(chalk.blue(key)));
        compilation.warnings.forEach(key => console.log(chalk.yellow(key)));
        compilation.errors.forEach(key => console.log(chalk.red(`${key}:${stats.compilation.errors[key]}`)));
        console.log(chalk.green(`${chalk.white('渲染進程調試完畢\n')}time:${(stats.endTime-stats.startTime)/1000} s`));
    });
}

function getHtml(res) {
    http.get(`http://localhost:8099`, (response) => {
        response.pipe(res);
    }).on('error', (err) => {
        console.log(err);
    });
}

devRender();

都是一些常規操做,能夠閱讀一下代碼。

六、配置啓動命令,在package.json中新增dev命令,啓動本地調試(先起了再說,報錯什麼的,見招拆招)

  "scripts": {
    "start": "electron ./src/main/main.js",
    "dev": "node ./builder/dev.js"
  },

而後命令行運行npm run dev。。。。。。反正我這兒是報錯了。。。說是找不到html-webpack-plugin模塊,那就運行npm i html-webpack-plugin -D安裝一下,若是步驟一沒有作的話,後面可能還會遇到不少模塊找不到的狀況,解決方法很簡單,缺什麼安裝什麼就行了。安裝完全部的模塊以後,啓動,仍是報錯了。。。。。。

ModuleNotFoundError: Module not found: Error: Can't resolve 'vue' in ...
ModuleNotFoundError: Module not found: Error: Can't resolve 'vue-loader' in ...

檢查了下package.json文件和node_modules,發現個人vue-loader沒有裝,而後就是裝一下(若是沒有遇到這個步驟,能夠忽略)

再次運行

  

這個報錯就很友好了嗎,就是vue-loader告訴你,必須安裝vue-template-compiler插件,否則就不工做,那就裝一下。

接着運行,就知道沒那麼容易成功

  

vue-loader報錯說缺乏了插件,讓檢查是否配置了VueLoaderPlugin插件,搜一下這是個什麼鬼,看這裏,15+版本的vue-loader須要配合VueLoaderPlugin使用,而後看了一下我使用的vue-loader版本15.7.1,那就配一下這個東西。

  

接着運行,終於沒有報錯了,可是頁面爲啥子是白的,個人h1標籤呢?冷靜下來分析一下問題,頁面沒有東西說明我打包時生成的html文件有問題(devServer會把打包出來的靜態文件保存在內存裏),而html文件是根據ejs模板生成的,那會不會是模板配置有問題?

  

看一下咱們的模板,結構是沒什麼問題啊,可是,沒有引用css和js文件啊,也就是咱們辛辛苦苦解析vue文件,打包css和js,最後卻沒有引用。。。好吧,那就再配置一下ejs模板,把相應的文件引入一下

<!DOCTYPE html>
<html lang="zh-CN">
<!--template for 2019年10月30日-->
<!--<%= new Date().getFullYear()+'/'+(new Date().getMonth()+1)+'/'+new Date().getDate()+' '+new Date().getHours()+':'+new Date().getMinutes() %>-->
<%
function getFilePath(filename,libsPath){
    let _filenameSearchIndex=filename.indexOf("?");
    let _libsPathSearchIndex=libsPath.indexOf("?");
    let _filename=filename.substr(0,_filenameSearchIndex<1?filename.length:_filenameSearchIndex);
    let _libsPath=libsPath.substr(0,_libsPathSearchIndex<1?libsPath.length:_libsPathSearchIndex);
    let htmlfilename=path.relative(_filename,_libsPath);
    return libsPath;
}
let path = require('path'),jsArr = [],cssArr = [];
let filename="./index.html";
    //修正目錄結構
    for(let i=0;i<htmlWebpackPlugin.files.css.length;i++){
        let name=getFilePath(filename,String(htmlWebpackPlugin.files.css[i]));
        cssArr.push(name);
    }
    for(let i=0;i<htmlWebpackPlugin.files.js.length;i++){
        let name=getFilePath(filename,String(htmlWebpackPlugin.files.js[i]));
        jsArr.push(name);
    }
%>
<head>
    <meta charset="UTF-8">
    <title><%= htmlWebpackPlugin.options.title %></title>
    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
    <meta HTTP-EQUIV="pragma" CONTENT="no-cache">
    <meta HTTP-EQUIV="Cache-Control" CONTENT="no-store, must-revalidate">
    <meta HTTP-EQUIV="expires" CONTENT="Wed, 26 Feb 1997 08:21:57 GMT">
    <meta HTTP-EQUIV="expires" CONTENT="0">
    <% cssArr.forEach(css=>{ %><link rel="stylesheet" href="<%= css %>" />
    <% }) %>
</head>
<body>
    <div id="app"></div>
    <% jsArr.forEach(js=>{ %><script type="text/javascript" src="<%= js %>"></script>
    <% }) %>
</body>
</html>

咱們能夠在ejs中拿到html-webpack-plugin插件的一些信息,好比插件配置、生成的文件等,而後拿到js和css文件,並引入進來,這裏建議看一下ejs模板語法。

咱們接着運行,終於出來了。

  

七、配置打包腳本

在builder文件夾下新建build.js,引入配置,直接運行webpack打包便可,不須要devServer。

/**
* Tip:    打包
* Author: haoluo
* Data:   2019-10-30
**/
process.env.NODE_ENV = 'production';
const chalk = require("chalk");
const del = require("del");
const webpack = require('webpack');
const renderConfig = require('./webpack.render.config.js');

del(["./app/*"]); //刪除歷史打包數據

viewBuilder().then(data => {
    console.log("打包輸出===>", data)
}).catch(err => {
    console.error("打包出錯,輸出===>", err);
    process.exit(1);
});

function viewBuilder() {
    return new Promise((resolve, reject) => {
        console.log("打包渲染進程......");
        const renderCompiler = webpack(renderConfig);
        renderCompiler.run((err, stats) => {
            if (err) {
                console.log("打包渲染進程遇到Error!");
                reject(chalk.red(err));
            } else {
                let log = "";
                stats.compilation.errors.forEach(key => {
                    log += chalk.red(`${key}:${stats.compilation.errors[key]}`) + "\n";
                })
                stats.compilation.warnings.forEach(key => {
                    log += chalk.yellow(key) + "\n";
                })
                Object.keys(stats.compilation.assets).forEach(key => {
                    log += chalk.blue(key) + "\n";
                })
                log += chalk.green(`time:${(stats.endTime-stats.startTime)/1000} s\n`) + "\n";
                resolve(`${log}`);
            }
        })
    })
}

在package.json中新增打包命令

"scripts": {
    "start": "electron ./src/main/main.js",
    "dev": "node ./builder/dev.js",
    "build": "node ./builder/build.js"
  },

npm run build執行打包,此次還真是出奇的順利啊,看一下app文件夾,已經生成了靜態文件,而後直接在瀏覽器打開index.html文件,正常顯示。

 

4、使用vuex,vue-router,axios

說好的全家桶呢,這裏咱們不用vue-resource了,使用axios。

一、使用vuex

安裝vuex依賴,在src>render文件夾下新建store文件夾,並在store文件夾下新增:

actions.js

export default {}

index.js

import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions.js';
import mutations from './mutations.js';
Vue.use(Vuex);
// 這裏爲全局的,模塊內的請在模塊內動態註冊
const store = new Vuex.Store({
    strict: true,
    state: {
        userInfo: {
            name: 'haoluo',
            address: 'beijing'
        }
    },
    getters: {},
    mutations,
    actions
});
export default store;

mutations.js

export default {
    //設置用戶信息
    setUserInfo(state, config) {
        if (!config) {
            state.userInfo = {};
        }
        for (var objName in config) {
            state.userInfo[objName] = config[objName];
        }
    }
}

以上三個文件的實力代碼,比官網教程還簡單,能夠自行研究一下文檔。

文件建好以後,須要把store掛載到vue實例上,找到vue工程的入口文件,src>render>index.js

import Vue from 'vue';
import store from './store/index.js';
import index from './views/index.vue';

//取消 Vue 全部的日誌與警告
Vue.config.silent = true;
new Vue({
    el: '#app',
    store: store,
    render: h => h(index)
});

而後咱們就可使用啦,找到根組件,src>render>views>index.vue

<template>
    <div class="content">
        <h1>Welcome to electron-vue-template!</h1>
        <h2>name:{{userInfo.name}}</h2>
        <h2>address:{{userInfo.address}}</h2>
    </div>
</template>

<script>
import {mapState} from 'vuex';
export default {
    computed: {
        ...mapState(['userInfo'])
    }
}
</script>
<style></style>

mapState是state的輔助函數,是個語法糖,藉助mapState咱們能夠更方面的獲取屬性,而不須要寫一堆囉裏吧嗦的東西,經過計算屬性computed接收userInfo,而後就可使用啦,運行本地調試,發現頁面上已經能夠正常顯示了

屬性有了以後咱們可使用,但若是想要改變vuex中存儲的屬性呢?爲了保證單向數據流以及方便對數據的追蹤等一些其餘緣由,不建議直接修改vuex的屬性,而是須要經過mutations,這裏也有一個輔助函數mapMutations,用法同mapState相似,只不過須要用methods去接收,做爲一個全局方法使用

<!-- render>views>index.vue -->
<template>
    <div class="content">
        <h1>Welcome to electron-vue-template!</h1>
        <h2>name:{{userInfo.name}}</h2>
        <h2>address:{{userInfo.address}}</h2>
        <button @click="changeAddress">設置address爲tianjin</button>
    </div>
</template>

<script>
import {mapState,mapMutations} from 'vuex';
export default {
    computed: {
        ...mapState(['userInfo'])
    },
    methods: {
        ...mapMutations(['setUserInfo']),
        changeAddress() {
            this.setUserInfo({
                address: 'tianjin'
            });
        }
    }
}
</script>
<style></style>

當點擊按鈕的時候。userInfo中的address被修改了,頁面渲染的值也相應的改變了

 

二、使用vue-router

安裝vue-router依賴,在render文件夾下新增router文件夾,並在其中新增index.js

module.exports = [
    {
        path: '/index.html',
        name: 'index',
        meta: {
            title: '首頁',
            author: '--',
            parentRouter: '--'
        },
        component: (resolve) => {
            require.ensure([], () => {
                return resolve(require('../views/index.vue'))
            }, "index")
        },
        children: []
    }
];

在入口文件render>index.js中引入並掛載

// render>index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import store from './store/index.js';
import routers from './router/index.js';
import index from './views/index.vue';

Vue.use(VueRouter);

let router = new VueRouter({
    routes: routers
})

//取消 Vue 全部的日誌與警告
Vue.config.silent = true;
new Vue({
    el: '#app',
    router: router,
    store: store,
    render: h => h(index)
});

運行一下,頁面能夠正常顯示,在地址欄輸入http://localhost:8099/index.html時,也是沒有問題的,如今新增長一個頁面,訂單頁

<template>
    <div class="content">
        <h1>order page!</h1>
    </div>
</template>

<script>
export default {}
</script>
<style></style>

再配置下路由

module.exports = [
    {
        path: '/index.html',
        name: 'index',
        meta: {
            title: '首頁',
            author: '--',
            parentRouter: '--'
        },
        component: (resolve) => {
            require.ensure([], () => {
                return resolve(require('../views/index.vue'))
            }, "index")
        },
        children: []
    },
    {
        path: '/order.html',
        name: 'order',
        meta: {
            title: '訂單頁',
            author: '--',
            parentRouter: '--'
        },
        component: (resolve) => {
            require.ensure([], () => {
                return resolve(require('../views/order.vue'))
            }, "order")
        },
        children: []
    }
];

並在首頁index.vue中增長跳轉按鈕,運行以後,發現跳不過去,尷尬~~~,路由跳轉,須要有<router-view></router-view>去接收才行啊

改造一下吧,views下新增home.vue,把index.vue中的內容拷貝過去,index.vue改成下面這樣

<!-- render>views>index.vue -->
<template>
    <div>
        <router-view></router-view>
    </div>
</template>

<script>
export default {
    methods: {},
    mounted() {
        this.$router.push({
            name: 'home'
        });
    }
}
</script>
<style></style>

router文件改成下面這樣

module.exports = [
    {
        path: '/index.html',
        name: 'index',
        meta: {
            title: '首頁',
            author: '--',
            parentRouter: '--'
        },
        component: (resolve) => {
            require.ensure([], () => {
                return resolve(require('../views/index.vue'))
            }, "index")
        },
        children: [
            {
                path: '/home.html',
                name: 'home',
                meta: {
                    title: 'home頁',
                    author: '--',
                    parentRouter: '--'
                },
                component: (resolve) => {
                    require.ensure([], () => {
                        return resolve(require('../views/home.vue'))
                    }, "home")
                },
                children: []
            },
            {
                path: '/order.html',
                name: 'order',
                meta: {
                    title: '訂單頁',
                    author: '--',
                    parentRouter: '--'
                },
                component: (resolve) => {
                    require.ensure([], () => {
                        return resolve(require('../views/order.vue'))
                    }, "order")
                },
                children: []
            }
        ]
    }
];

再次運行,頁面已經能夠正常跳轉了。

三、axios,這裏暫時不說,後續electron和vue聯調的時候會補上。

幾點說明:

  一、截止目前,webpack配置以及dev和build腳本僅僅是調通大體的流程,還有不少可優化可考究的地方,後續完善項目的過程當中會對打包流程逐漸潤色;

  二、vue全家桶在實際項目中高級用法不少,細節也不少,這裏只是最簡單的用法,如若用到實際項目中,還要基於目前狀況再作細化;

  三、本系列文章旨在記錄、回顧整個項目框架搭建流程,把握總體結構,不少地方須要根據實際項目再作處理;

  四、若有錯誤或不當的地方,歡迎指出,共同進步!

相關文章
相關標籤/搜索