从零搭建基于webpack的Electron-Vue3项目(1)——基于webpack的Vue3项目搭建
从零搭建基于webpack的Electron-Vue3项目(1)——基于webpack的Vue3项目搭建
前言
本篇文章内容,主要是基于webpack
的Vue3项目开发环境进行搭建,暂时还不涉及到Electron的整合。可以独立的当作一个内容来进行阅读。
项目创建
创建目录electron-vue3-webpack
并进入执行npm init
命令。设置了基础的项目信息后,我们开始本次的环境搭建之旅。
使用webpack
前置条件
基本熟悉webpack
是什么以及它打包的运行处理过程。
环境准备
前端编写
项目根目录创建src\renderer
目录,用于存放前端代码。向其中编写一个简单的前端页面以及JS:
src\renderer\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>It's Title</title>
</head>
<body>
</body>
</html>
src\renderer\main.js
console.log(document.title); // 控制台输出html的title
webpack
安装
命令行执行npm install -D webpack
HtmlWebpackPlugin
插件安装
接下来安装webpack
插件HtmlWebpackPlugin
。该插件主要生成html,这里我们配置该插件template为上述index.html,配置方式见下文webpack
插件配置部分。执行命令npm install --save-dev html-webpack-plugin
配置webpack
在项目根目录下创建webpack.config.js
,内容如下:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/renderer/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/renderer/index.html' // 配置html模板文件
})
]
};
至此,一个webpack
的基础环境配置完成:
打包运行
- 命令行执行
npx webpack
; - 完成编译检查
dist
目录; - 使用浏览器打开
dist/index.html
,并检查控制台。
实际上,当你查看dist
中的html的时候你会发现,webpack
打包为我们引入了生成的js:
目前为止,我们完成了webpack
的基础环境搭建以及试运行。接下来我们将引入Vue3,并以webpack
打包的方式来进行Vue3的项目搭建。
使用Vue3
实际上,无论是Vue2还是Vue3,引入Vue并使用webpack
来进行打包的原理都是一样的。
引入Vue3、Vue-Router以及Vuex组件
安装Vue3
# 最新稳定版
$ npm install vue@next -S
创建App.vue
根组件
renderer
目录下创建App.vue
文件,内容如下:
<template>
<router-view/> <!-- router-view必须要引入Vue-Router组件,下文进行配置 -->
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
安装Vue-Router
$ npm install vue-router@next -S
创建Router
实例
renderer
目录下创建router
目录,并增加index.js
文件,其中内容如下:
import {createRouter, createWebHashHistory} from 'vue-router';
const routes = [ // 路由内容暂不配置
// {
// path: '/',
// name: 'Home',
// component: Home
// },
];
const router = createRouter({
history: createWebHashHistory(),
routes
});
export default router;
安装Vuex
$ npm install vuex@next -S
创建Vuex
实例
renderer
目录下创建store
目录,并增加index.js
文件,其中内容如下:
import {createStore} from 'vuex';
export default createStore({
state: {},
mutations: {},
actions: {},
modules: {}
});
编写main.js以及模板html
- 在
src\renderer\index.html
中的<body>
标签中,添加<div id="app"></div>
:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>It's Title</title></head>
<body>
<div id="app"></div>
</body>
</html>
- 修改
src\renderer\main.js
代码:
import { createApp } from 'vue'
import App from './App.vue' // 引入上述
import router from './router' // 引入上述编写的router实例
import store from './store' // 引入上述vuex实例
createApp(App).use(store).use(router).mount('#app') // 链式加载,"#app"指代模板中<div id="app">
至此,目前的项目结构如下:
然而,当我们进行了上述一系列的配置以后,再次运行webpack
却发现,报错了:
从报错信息可以看出,当webpack
想要解析App.vue
文件时候,报错提示我们:You may need an appropriate loader to handle this file type
(你或许需要一个对应的loader来处理这种文件类型)。
了解到webpack
打包机制的读者很容易知道,我们需要安装Vue
对应的loader
了
安装Vue-Loader
较为熟悉的同学可能立马去官方的指引文件看文档了,其实官方文档也已经相当详细的说明了Vue-Loader
的配置方式,详情见链接。(注意:截至2020/11/22文档还是Vue2版本下的配置,也许你在看的时候,已经正式替换为了Vue3的正确配置了)
-
安装
vue-loader
; -
修改
webpack.config.js
配置。
我也天真的以为Vue3的webpack
也是这样的,然而最后吃了闭门羹。实际上,Vue3大不一样,接下来将有一系列的组件进行安装,在此之前我们先记录下当前的依赖:
"devDependencies": {
"html-webpack-plugin": "^4.5.0",
"webpack": "^5.6.0",
"webpack-cli": "^4.2.0"
},
"dependencies": {
"vue": "^3.0.2",
"vue-router": "^4.0.0-rc.5",
"vuex": "^4.0.0-rc.1"
}
接着说Vue3的改变。事实上,Vue3的loader已经有了如下的变更:
vue-loader
的npm
包为截至2020/11/22,版本为:v16.0.0-rc.2,该版本安装时需要指定版本
npm install -D vue-loader@v16.0.0-rc.2
VueLoaderPlugin
的导入方式已经发生改变:
// 原先方式:
const VueLoaderPlugin = require('vue-loader/lib/plugin')
// 全新方式:
const { VueLoaderPlugin } = require('vue-loader')
- 移除
vue-template-compiler
,新增了@vue/compiler-sfc
vue-template-compiler
在Vue2中是配合vue-loader
组件,在Vue3中被移除了,取而代之的事@vue/compiler-sfc
。
npm install -D @vue/compiler-sfc
当然,为了让webpack处理.vue
文件中的<style>
块,以及各种css
、图片等资源,我们添加一些常规的loader
:
npm install -D css-loader style-loader file-loader
至此,我们的package.json
的依赖部分如下,大家自行对比:
"devDependencies": {
"@vue/compiler-sfc": "^3.0.2",
"css-loader": "^5.0.1",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.0",
"style-loader": "^2.0.0",
"vue-loader": "^16.0.0-rc.2",
"webpack": "^5.6.0",
"webpack-cli": "^4.2.0"
},
"dependencies": {
"vue": "^3.0.2",
"vue-router": "^4.0.0-rc.5",
"vuex": "^4.0.0-rc.1"
}
配置webpack
在上述步骤的基础上,我们终于可以进行webpack
的配置编写,直接上配置:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {VueLoaderPlugin} = require('vue-loader');
module.exports = {
entry: './src/renderer/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|jpg|gif|svg)$/,
use: [
{
loader: 'file-loader',
options: {
esModule: false, // esModule主要为了处理require图片报错的问题
}
}
],
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/renderer/index.html'
}),
// 请确保引入这个插件来施展魔法
new VueLoaderPlugin()
]
};
这个配置的每一项的功能,不再赘述。不清楚的可以复习一下webpack
配置,链接在此。
webpack
打包Vue3
迎来最后一步打包操作:npx webpack
,输出对应成功结果,然后在dist
目录下检查一下打包结果即可。
$ npx webpack
![](https://cdn.jsdelivr.net/gh/w4ngzhen/CDN/images/post/2020-11-22-electron-vue3/webpack-vue3-complete.png)
后续
当然,目前的demo没有任何实质的代码,还不能确保我们的工程有效。接下来的内容,我们继续编写Vue3的组件,完成一个简单的涵盖路由跳转的Example。
编写Example
编写一个Home页面、Plus页面的example,涵盖Vue3的一些新特性,API具体功能见官方网站。Example具体内容:
- Home页面包含一个
add
按钮,以及可点击的卡片GO Plus Page
; - 当点击
add
按钮时,页面上数字+1
,同时会调用Vuex
API来完成向store.number
的提交+1
操作; - 点击
GO Plus Page
卡片,调用Vue Router
API跳转到Plus页面; - Plus界面会显示
store.number
当前值,以及完成加载后会以50%概率随机加载2张(edge和chrome图标)图片的其中1张。
整体结构
![](https://cdn.jsdelivr.net/gh/w4ngzhen/CDN/images/post/2020-11-22-electron-vue3/vue3-example.png)
Home、Plus页面
<!-- Home.vue -->
<template>
<div class="home">
<p>{{ 'number = ' + number }}</p>
<button @click="add">add</button>
<div class="card" @click="cardClick">
<p>GO Plug Page</p>
</div>
</div>
</template>
<script>
// 从 vue 中引入 ref 函数
import {ref} from 'vue';
import {useRouter} from "vue-router";
import {useStore} from "vuex";
export default {
name: "Home",
setup() {
// 用 ref 函数包装一个响应式变量 number
let number = ref(0);
let store = useStore();
let router = useRouter();
// 设定一个方法
function add() {
// number是被ref函数包装过了的,其值保存在.value中
number.value++;
// Vuex提交
store.commit('plus', {num: 1});
}
// 定义卡片点击,进行路由跳转
function cardClick() {
router.push('/plus');
}
// 将 成员 返回出去,供template中使用
return {number, add, cardClick};
}
};
</script>
<style scoped>
</style>
<!-- Plus.vue -->
<template>
<div style="text-align: center">
<p>Refresh this page and you will see the icon change</p>
<div class="img-display">
<img :src="imgSrc" alt="">
</div>
<div class="number-display">
<p>{{ 'state.number = ' + state.number }}</p>
</div>
</div>
</template>
<script>
import {useStore} from "vuex";
import {computed} from "vue";
export default {
name: "Plus",
setup() {
const state = useStore().state;
const imgSrc = computed(() => {
let randomBool = (Math.ceil(Math.random() * 10)) % 2 === 0;
if (randomBool) {
return require('../assets/img/edge.png');
} else {
return require('../assets/img/chrome.png');
}
}); // 计算属性
return {
state,
imgSrc
};
}
};
</script>
<style scoped>
</style>
Vuex、Router
// ---------------------------
// src/renderer/store/index.js
// ---------------------------
import {createStore} from 'vuex';
export default createStore({
state: {
number: 0,
},
mutations: {
plus(state, {num}) {
state.number += num;
}
},
actions: {},
modules: {}
});
// ----------------------------
// src/renderer/router/index.js
// ----------------------------
import {createRouter, createWebHashHistory} from 'vue-router';
// 定义路由组件, 注意,这里一定要使用 文件的全名(包含文件后缀名)
import Home from "../views/Home.vue";
import Plus from "../views/Plus.vue";
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/plus',
name: 'Plus',
component: Plus
}
];
const router = createRouter({
history: createWebHashHistory(),
routes
});
export default router;
效果
![](https://cdn.jsdelivr.net/gh/w4ngzhen/CDN/images/post/2020-11-22-electron-vue3/example-show.gif)
PS:Plug
页面刷新后,会清空state.number
的内容所以归零。
再议webpack
——热部署开发
上述进行开发的过程中,我们的流程始终的开发过程为:
webpack
配置;- 项目编码;
npx webpack
;- 通过IDE查看页面结果,若需要修改,则回到步骤2。
实际开发中,我们会发现如下的一个问题:文件改动后,需要重新执行npx webpack
命令完成编译,耗时麻烦。
watch参数
这个过程解决起来特别的简单,webpack
实际上提供了运行参数watch
,该参数简单来说就是在第一次调用以后并不会直接退出webpack
,而会有一个单独的进程来监听webpack
配置中指定入口代码执行以来整个依赖文件的变动,一旦发生了改变,则会立刻进行重新编译。关于watch
参数详情,见此链接:Watch and WatchOptions | webpack。
使用方式简单来说:
- 启动
webpack
命令时,带上--watch
参数 - 配置 webpack.config.js 中设置
watch:true
这里我们使用前者
// package.json
...
"scripts": {
"build-vue3-with-webpack": "npx webpack --watch" // 添加watch参数
},
...
运行脚本后,你会看到控制台并不会在完成编译后退出,而是hold住的,且命令行最下方显示:
webpack 5.6.0 compiled successfully in 4621 ms
[webpack-cli] watching files for updates... // 监听文件更新
每当你修改了文件,你会发现命令行会有编译更新:
其实到了这一步后,开发效率已经有了很大的提升。然而有想法的开发人员会发现,倘若每次进行更新了文件,就要重新等待webpack
进行编译,随着项目文件的越来越多,时间也会越来越就久,甚至到了后期,编译一次就要几分钟。其实webpack
还有一个超级有用的开发者工具:webpack-dev-server
webpack-dev-server
使用webpack-dev-server
的好处是修改了后不需要刷新浏览器,同时,该模块不会将文件编译输出为最终文件,而是放在内存中,转换起来很快。
安装
$ npm install webpack-dev-server -D // 请注意 —D 开发依赖
配置以及使用
webpack.config.js
需要在webpack
配置文件中增加devServer
节点,告知 dev server,从什么位置查找文件(但并不意味着会生成文件到该指定位置):
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {VueLoaderPlugin} = require('vue-loader');
module.exports = {
entry: './src/renderer/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
devServer: { // 新增devServer节点
contentBase: './dist', // 告知dev server从什么位置查找
},
...
}
package.json/scripts
添加一个可以直接运行 dev server 的 script:
{
"name": "electron-vue3-webpack",
...
"scripts": {
"build-vue3-with-webpack": "npx webpack --watch",
"start": "webpack serve --open" // 新增脚本,启动webpack-dev-server
},
...
}
完成配置后,我们关闭之前的"npx webpack --watch"
,使用新的start
脚本运行程序。然后进行开发:
这里和上面watch
的区别在于:
- 没有实际的
dist
目录以及编译后的前端文件生成; - 浏览器页面无需手动刷新页面,会自动进行;
关于webpack-dev-server
的相关信息,可以进一步参考官方文档:开发环境 | webpack (docschina.org)