chore(files): remove blog folder
313
blog/1.js.md
@ -1,313 +0,0 @@
|
||||
- 伴随着 `vue3` 的发布,vue 全家桶又添新成成员 ——`vite` 脚手架工具;相比 `@vue/cli` 基于浏览器内置的 ES module 极快的冷启动速度、基于 `Rollup` 打包的配置更简单(确实简单);
|
||||
`vite` 底层原理网上已有好多文章,本文主要讲使用
|
||||
|
||||
- 公司的项目用的整合方案是 `umi` + `electron` ,umi 是我用过目前最傻瓜化的框架了,你能想到的她都做了而且还是自动化的 👍
|
||||
唯一一点不大好的 umi 的构建速度较慢;
|
||||
vite 冷启动速度确实让人眼前一亮,索性拿来和 electron 集成一波;算是一个技术备选方案 `^_^`
|
||||
|
||||
> 毕竟 ****尤大出品,必是精品**** 定律嘛!
|
||||
|
||||
[完整代码 https://github.com/caoxiemeihao/electron-vue-vite](https://github.com/caoxiemeihao/electron-vue-vite)
|
||||
|
||||
**`喜欢的(づ ̄3 ̄)づ╭❤~给点个start哦~`**
|
||||
|
||||
#### 划重点 (踩坑记录)
|
||||
- import { write } from 'fs' 的这种形式会被 vite 编译成 /@modules/fs?import
|
||||
- const { write } = require('fs') 这种形式就能用了 😉
|
||||
- const { ipcRenderer } = require('electron') 同理
|
||||
- 虽然开发期可以用 require 避开 vite 的编译问题,但是打包时候 rollup 那边又出了问题;
|
||||
* 拿 require('electron-store') 举例,在 vite.config.ts 中通过 **`自定义 rollup-plugin`** 转换成 EMS 形式即可
|
||||
* `const Store = require('electron-store')` >> `import Store from 'electron-store'`
|
||||
|
||||
![800x600.png](https://upload-images.jianshu.io/upload_images/6263326-76f23d47dea57638.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
|
||||
|
||||
#### 准备材料
|
||||
- `vue3`
|
||||
- `vite` vue3 脚手架
|
||||
- `electron`
|
||||
- `electron-builder` electron 构建工具
|
||||
- `electron-connect` 主进程代码修改热重启
|
||||
- `rollup` 之前写过一个偏文章 [Electron、webpack、react、typescript 从零开始搭积木](https://www.jianshu.com/p/3aafab67ff70)
|
||||
* 一来能少装个包,vite 用的也是 rollup —— 入乡随俗
|
||||
* 二来 rollup 的配置比 webpack 简单又专注于 js 的打包;只是用来打包 electron 主进程代码确实方便
|
||||
- `concurrently` 同时启动 vite(渲染进程)、electron(主进程)
|
||||
- `wait-on` 用于监听 vite 启动,随后拉起 electron 启动
|
||||
- `chalk` 命令行文字颜色、背景色
|
||||
- `ora` 命令行友好提示
|
||||
- `minimist` 命令行参数解析
|
||||
- `dotenv` 解析 .env 文件
|
||||
|
||||
#### 先用创建一个 vite 工程
|
||||
- 官方命令: [https://github.com/vitejs/vite#getting-started](https://github.com/vitejs/vite#getting-started)
|
||||
|
||||
```bash
|
||||
yarn create vite-app <project-name>
|
||||
```
|
||||
|
||||
#### 然后调整目录结构
|
||||
```tree
|
||||
.
|
||||
├─dist 打包后的文件夹
|
||||
├─screenshot
|
||||
├─script
|
||||
│ ├─build.js 主进程构建文件
|
||||
│ └─rollup.config.js 主进程构建配置
|
||||
└─src
|
||||
├─main
|
||||
│ └─index.ts 主进程入口文件
|
||||
└─render
|
||||
├─assets
|
||||
├─components
|
||||
├─dist vue 打包后目录
|
||||
│ _assets
|
||||
├─public
|
||||
├─App.vue
|
||||
├─index.css
|
||||
├─index.html
|
||||
└─main.js 渲染进程入口文件
|
||||
└─.env 配置文件
|
||||
```
|
||||
|
||||
#### 主进程构建配置
|
||||
|
||||
> script/rollup.config.js
|
||||
|
||||
- `@rollup/plugin-node-resolve` 支持引入 `node_modules` 模块
|
||||
- `@rollup/plugin-commonjs` 支持 `require`、`module.exports` 写法
|
||||
- `@rollup/plugin-typescript` 支持 `typescript`
|
||||
|
||||
```javascript
|
||||
const path = require('path');
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve');
|
||||
const commonjs = require('@rollup/plugin-commonjs');
|
||||
const typescript = require('@rollup/plugin-typescript');
|
||||
|
||||
module.exports = (env = 'production') => {
|
||||
return {
|
||||
input: path.join(__dirname, '../src/main/index.ts'), // 入口文件
|
||||
output: {
|
||||
file: path.join(__dirname, '../src/main/_.js'), // 输出目标
|
||||
format: 'cjs', // CommonJs 格式
|
||||
name: 'ElectronMainBundle', // 模块名称(可选)
|
||||
sourcemap: true,
|
||||
},
|
||||
plugins: [
|
||||
nodeResolve({ jsnext: true, preferBuiltins: true, browser: true }), // 支持引入 node_modules 模块
|
||||
commonjs(), // 支持 CommonJs 规范
|
||||
typescript(), // 支持 TypeScript
|
||||
],
|
||||
external: [
|
||||
// 告诉 rollup 碰到下面模块时候不要去打包
|
||||
'fs',
|
||||
'path',
|
||||
'http',
|
||||
'https',
|
||||
'child_process',
|
||||
'os',
|
||||
'electron',
|
||||
],
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 主进程构建脚本
|
||||
|
||||
> script/build.js
|
||||
|
||||
- 实现思路: 使用 `wait-on` 监听 `vite` 启动,然后拉起 `electron` 打开渲染进程加载 vue 应用
|
||||
- `rollup` 使用比较简单
|
||||
* 如果是开发模式用 `rollup.watch(options)` 会监听文件变化,文件改变自动重新编译
|
||||
* 如果只是构建使用 `rollup.rollup(options)` 只会执行一次构建
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* electron 打包
|
||||
*/
|
||||
const path = require('path');
|
||||
const rollup = require('rollup');
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
const chalk = require('chalk');
|
||||
const ora = require('ora');
|
||||
const waitOn = require('wait-on');
|
||||
const electron = require('electron-connect').server.create({ stopOnClose: true }); // 表示要操作主进程端(对应的还是渲染进程端;渲染进程用的 vite 热更新)
|
||||
require('dotenv').config({ path: path.join(__dirname, '../.env') }); // 解析项目根目录下的 .env 文件
|
||||
const options = require('./rollup.config'); // 引入 rollup 配置
|
||||
|
||||
const opt = options(argv.env);
|
||||
const TAG = '[script/build.js]';
|
||||
const spinner = ora(`${TAG} Electron build...`);
|
||||
|
||||
if (argv.watch) { // 开发模式 (命令行传入 --watch 标识)
|
||||
waitOn({
|
||||
resources: [`http://localhost:${process.env.PORT}`], // 等待 vite 服务器启动,然后拉起 electron
|
||||
log: false,
|
||||
}, err => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// once here, all resources are available
|
||||
const watcher = rollup.watch(opt);
|
||||
watcher.on('change', filename => {
|
||||
const log = chalk.green(`change -- ${filename}`);
|
||||
console.log(TAG, log);
|
||||
});
|
||||
watcher.on('event', ev => {
|
||||
if (ev.code === 'END') {
|
||||
// init-未启动、started-第一次启动、restarted-重新启动
|
||||
electron.electronState === 'init' ? electron.start() : electron.restart();
|
||||
}
|
||||
});
|
||||
});
|
||||
} else { // 构建模式
|
||||
spinner.start();
|
||||
rollup.rollup(opt)
|
||||
.then(build => {
|
||||
spinner.stop();
|
||||
console.log(TAG, chalk.green('Electron build successed.'));
|
||||
build.write(opt.output);
|
||||
})
|
||||
.catch(error => {
|
||||
spinner.stop();
|
||||
console.log(`\n${TAG} ${chalk.red('构建报错')}\n`, error, '\n');
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### 渲染进程 vite 配置
|
||||
|
||||
> vite.config.ts
|
||||
|
||||
- 需要改变 `vite` 的构建路径,因为 vue 代码移动到了 `src/render` 下
|
||||
- 通过 `.env` 配置启动端口,配合主进程监听端口启动
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 参考链接: https://github.com/vitejs/vite/blob/master/src/node/config.ts
|
||||
*/
|
||||
import { join } from 'path'
|
||||
import { UserConfig } from 'vite'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
dotenv.config({ path: join(__dirname, '.env') })
|
||||
const root = join(__dirname, 'src/render')
|
||||
|
||||
const config: UserConfig = {
|
||||
root,
|
||||
port: +process.env.PORT,
|
||||
base: './',
|
||||
outDir: join(__dirname, 'dist/render'),
|
||||
alias: {
|
||||
// 别名必须以 / 开头、结尾
|
||||
// '/@/': root, -- vite 内部在用,这里不能用了
|
||||
// '/root/': __dirname, -- vite 内部在用,这里不能用了
|
||||
'/assets/': join(__dirname, 'src/render/assets'),
|
||||
'/components/': join(__dirname, 'src/render/components'),
|
||||
'/lib/': join(__dirname, 'src/render/lib'),
|
||||
'/utils/': join(__dirname, 'src/render/utils'),
|
||||
'/views/': join(__dirname, 'src/render/views'),
|
||||
},
|
||||
optimizeDeps: {
|
||||
// 这里不加也没事,用 require 的形式就能避开 import 被编译成 /@modules/fs?import
|
||||
// allowNodeBuiltins: ['electron-is-dev', 'electron-store', 'electron']
|
||||
},
|
||||
rollupInputOptions: {
|
||||
external: [
|
||||
'crypto',
|
||||
'assert',
|
||||
'fs',
|
||||
'util',
|
||||
'os',
|
||||
'events',
|
||||
'child_process',
|
||||
'http',
|
||||
'https',
|
||||
'path',
|
||||
'electron',
|
||||
],
|
||||
plugins: [
|
||||
{
|
||||
name: '@rollup/plugin-cjs2esm',
|
||||
transform(code, filename) {
|
||||
if (filename.includes('/node_modules/')) {
|
||||
return code
|
||||
}
|
||||
|
||||
const cjsRegexp = /(const|let|var)[\n\s]+(\w+)[\n\s]*=[\n\s]*require\(["|'](.+)["|']\)/g
|
||||
const res = code.match(cjsRegexp)
|
||||
if (res) {
|
||||
// const Store = require('electron-store') -> import Store from 'electron-store'
|
||||
code = code.replace(cjsRegexp, `import $2 from '$3'`)
|
||||
}
|
||||
return code
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
rollupOutputOptions: {
|
||||
format: 'commonjs',
|
||||
},
|
||||
}
|
||||
|
||||
export default config
|
||||
```
|
||||
|
||||
#### 启动脚本配置
|
||||
|
||||
```json
|
||||
{
|
||||
"main": "src/main/_.js",
|
||||
"scripts": {
|
||||
"dev": "npm run dev:all",
|
||||
"dev:all": "concurrently -n=vue,ele -c=green,blue \"npm run dev:vue\" \"npm run dev:ele\"",
|
||||
"dev:vue": "vite",
|
||||
"dev:ele": "node script/build --env=development --watch",
|
||||
"build:vue": "vite build",
|
||||
"build:ele": "node script/build --env=production",
|
||||
"build": "npm run build:vue && npm run build:ele && electron-builder"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- main `electron` 启动后会加载 main 配置的文件
|
||||
- dev:all 使用 `concurrently` 同时启动 `dev:vue`、`dev:ele`
|
||||
- dev:ele `electron` 开发模式脚本,通过传入 `--watch` 表示 rollup 监听主进程变化自动编译
|
||||
- build 使用 `electron-builder` 打包
|
||||
|
||||
#### 启动一下试试
|
||||
```bash
|
||||
$ yarn dev
|
||||
|
||||
yarn run v1.22.4
|
||||
$ npm run dev:all
|
||||
|
||||
> electron-vue@0.0.1 dev:all D:\github\electron-vue-vite
|
||||
> concurrently -n=vue,ele -c=green,blue "npm run dev:vue" "npm run dev:ele"
|
||||
|
||||
[ele]
|
||||
[ele] > electron-vue@0.0.1 dev:ele D:\github\electron-vue-vite
|
||||
[ele] > node script/build --env=development --watch
|
||||
[ele]
|
||||
[vue]
|
||||
[vue] > electron-vue@0.0.1 dev:vue D:\github\electron-vue-vite
|
||||
[vue] > vite
|
||||
[vue]
|
||||
[vue] vite v1.0.0-rc.4
|
||||
[vue]
|
||||
[vue] Dev server running at:
|
||||
[vue] > Network: http://192.168.1.9:3344/
|
||||
[vue] > Network: http://192.168.119.1:3344/
|
||||
[vue] > Network: http://10.0.60.32:3344/
|
||||
[vue] > Local: http://localhost:3344/
|
||||
[vue]
|
||||
[ele] [2020-08-17T08:57:11.850Z] [electron-connect] [server] started electron process: 1488
|
||||
[ele] [2020-08-17T08:57:11.851Z] [electron-connect] [server] server created and listening on 30080
|
||||
[ele]
|
||||
```
|
||||
|
||||
#### 尾巴
|
||||
- 2019 款 13 寸 mac-pro 启动速度 4秒 左右
|
||||
- 奔腾 G4560 台机 CUP 神舟笔记本启动速度 6 秒左右
|
||||
- 毋庸置疑 vite 的方案比起 @vue/cli、umi、create-react-app 这类基于 webpack 的脚手架启动这块的优势大的多滴多
|
||||
- 技术总是飞快的迭代、进步,目的都是解决一些已经存在、或即将到来的问题;继续治疗、学习起来、加油哇~
|
249
blog/1.md
@ -1,249 +0,0 @@
|
||||
# Electron + vue3 + vite 整合
|
||||
|
||||
- 伴随着 `vue3` 的发布,vue 全家桶又添新成成员 ——`vite` 脚手架工具;相比 `@vue/cli` 基于浏览器内置的 ES module 极快的冷启动速度、基于 `Rollup` 打包的配置更简单(确实简单);
|
||||
`vite` 底层原理网上已有好多文章,本文主要讲使用
|
||||
|
||||
- 公司的项目用的整合方案是 `umi` + `electron` ,umi 是我用过目前最傻瓜化的框架了,你能想到的她都做了而且还是自动化的 👍
|
||||
唯一一点不大好的 umi 的构建速度较慢;
|
||||
vite 冷启动速度确实让人眼前一亮,索性拿来和 electron 集成一波;算是一个技术备选方案 `^_^`
|
||||
|
||||
> 毕竟 ****尤大出品,必是精品**** 定律嘛!
|
||||
|
||||
![](https://raw.githubusercontent.com/caoxiemeihao/electron-vue-vite/master/screenshot/800x600.png)
|
||||
|
||||
|
||||
#### 准备材料
|
||||
- `vue3`
|
||||
- `vite` vue3 脚手架
|
||||
- `electron`
|
||||
- `electron-builder` electron 构建工具
|
||||
- `electron-connect` 主进程代码修改热重启
|
||||
- `rollup` 之前写过一个偏文章 [Electron、webpack、react、typescript 从零开始搭积木](https://www.jianshu.com/p/3aafab67ff70)
|
||||
* 一来能少装个包,vite 用的也是 rollup —— 入乡随俗
|
||||
* 二来 rollup 的配置比 webpack 简单又专注于 js 的打包;只是用来打包 electron 主进程代码确实方便
|
||||
- `concurrently` 同时启动 vite(渲染进程)、electron(主进程)
|
||||
- `wait-on` 用于监听 vite 启动,随后拉起 electron 启动
|
||||
- `chalk` 命令行文字颜色、背景色
|
||||
- `ora` 命令行友好提示
|
||||
- `minimist` 命令行参数解析
|
||||
- `dotenv` 解析 .env 文件
|
||||
|
||||
#### 目录结构
|
||||
```tree
|
||||
.
|
||||
├─dist 打包后的文件夹
|
||||
├─screenshot
|
||||
├─script
|
||||
│ ├─build.js 主进程构建文件
|
||||
│ └─rollup.config.js 主进程构建配置
|
||||
└─src
|
||||
├─main
|
||||
│ └─index.ts 主进程入口文件
|
||||
└─render
|
||||
├─assets
|
||||
├─components
|
||||
├─dist vue 打包后目录
|
||||
│ _assets
|
||||
├─public
|
||||
├─App.vue
|
||||
├─index.css
|
||||
├─index.html
|
||||
└─main.js 渲染进程入口文件
|
||||
└─.env 配置文件
|
||||
```
|
||||
|
||||
#### 主进程构建配置
|
||||
|
||||
> script/rollup.config.js
|
||||
|
||||
- `@rollup/plugin-node-resolve` 支持引入 `node_modules` 模块
|
||||
- `@rollup/plugin-commonjs` 支持 `require`、`module.exports` 写法
|
||||
- `@rollup/plugin-typescript` 支持 `typescript`
|
||||
|
||||
```javascript
|
||||
const path = require('path');
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve');
|
||||
const commonjs = require('@rollup/plugin-commonjs');
|
||||
const typescript = require('@rollup/plugin-typescript');
|
||||
|
||||
module.exports = (env = 'production') => {
|
||||
return {
|
||||
input: path.join(__dirname, '../src/main/index.ts'), // 入口文件
|
||||
output: {
|
||||
file: path.join(__dirname, '../src/main/_.js'), // 输出目标
|
||||
format: 'cjs', // CommonJs 格式
|
||||
name: 'ElectronMainBundle', // 模块名称(可选)
|
||||
sourcemap: true,
|
||||
},
|
||||
plugins: [
|
||||
nodeResolve({ jsnext: true, preferBuiltins: true, browser: true }), // 支持引入 node_modules 模块
|
||||
commonjs(), // 支持 CommonJs 规范
|
||||
typescript(), // 支持 TypeScript
|
||||
],
|
||||
external: [
|
||||
// 告诉 rollup 碰到下面模块时候不要去打包
|
||||
'fs',
|
||||
'path',
|
||||
'http',
|
||||
'https',
|
||||
'child_process',
|
||||
'os',
|
||||
'electron',
|
||||
],
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 主进程构建脚本
|
||||
|
||||
> script/build.js
|
||||
|
||||
- 实现思路: 使用 `wait-on` 监听 `vite` 启动,然后拉起 `electron` 打开渲染进程加载 vue 应用
|
||||
- `rollup` 使用比较简单
|
||||
* 如果是开发模式用 `rollup.watch(options)` 会监听文件变化,文件改变自动重新编译
|
||||
* 如果只是构建使用 `rollup.rollup(options)` 只会执行一次构建
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* electron 打包
|
||||
*/
|
||||
const path = require('path');
|
||||
const rollup = require('rollup');
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
const chalk = require('chalk');
|
||||
const ora = require('ora');
|
||||
const waitOn = require('wait-on');
|
||||
const electron = require('electron-connect').server.create({ stopOnClose: true }); // 表示要操作主进程端(对应的还是渲染进程端;渲染进程用的 vite 热更新)
|
||||
require('dotenv').config({ path: path.join(__dirname, '../.env') }); // 解析项目根目录下的 .env 文件
|
||||
const options = require('./rollup.config'); // 引入 rollup 配置
|
||||
|
||||
const opt = options(argv.env);
|
||||
const TAG = '[script/build.js]';
|
||||
const spinner = ora(`${TAG} Electron build...`);
|
||||
|
||||
if (argv.watch) { // 开发模式 (命令行传入 --watch 标识)
|
||||
waitOn({
|
||||
resources: [`http://localhost:${process.env.PORT}`], // 等待 vite 服务器启动,然后拉起 electron
|
||||
log: false,
|
||||
}, err => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// once here, all resources are available
|
||||
const watcher = rollup.watch(opt);
|
||||
watcher.on('change', filename => {
|
||||
const log = chalk.green(`change -- ${filename}`);
|
||||
console.log(TAG, log);
|
||||
});
|
||||
watcher.on('event', ev => {
|
||||
if (ev.code === 'END') {
|
||||
// init-未启动、started-第一次启动、restarted-重新启动
|
||||
electron.electronState === 'init' ? electron.start() : electron.restart();
|
||||
}
|
||||
});
|
||||
});
|
||||
} else { // 构建模式
|
||||
spinner.start();
|
||||
rollup.rollup(opt)
|
||||
.then(build => {
|
||||
spinner.stop();
|
||||
console.log(TAG, chalk.green('Electron build successed.'));
|
||||
build.write(opt.output);
|
||||
})
|
||||
.catch(error => {
|
||||
spinner.stop();
|
||||
console.log(`\n${TAG} ${chalk.red('构建报错')}\n`, error, '\n');
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### 渲染进程 vite 配置
|
||||
|
||||
> vite.config.ts
|
||||
|
||||
- 需要改变 `vite` 的构建路径,因为 vue 代码移动到了 `src/render` 下
|
||||
- 通过 `.env` 配置启动端口,配合主进程监听端口启动
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 参考链接: https://github.com/vitejs/vite/blob/master/src/node/config.ts
|
||||
*/
|
||||
import { join } from 'path'
|
||||
import { UserConfig } from 'vite'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
dotenv.config({ path: join(__dirname, '.env') })
|
||||
|
||||
const config: UserConfig = {
|
||||
// vite 项目编译、启动的根目录
|
||||
// 默认指向项目跟目录,我们把 vue(渲染进程) 代码全部搬到 src/render 下
|
||||
root: join(__dirname, 'src/render'),
|
||||
// 通过 .env 配置端口,能够让 electron 加载到正确的地址
|
||||
port: +process.env.PORT,
|
||||
// 打包后的 vue 项目引入 js、css、favicon 等资源路径
|
||||
base: './',
|
||||
}
|
||||
|
||||
export default config
|
||||
```
|
||||
|
||||
#### 启动脚本配置
|
||||
|
||||
```json
|
||||
{
|
||||
"main": "src/main/_.js",
|
||||
"scripts": {
|
||||
"dev": "npm run dev:all",
|
||||
"dev:all": "concurrently -n=vue,ele -c=green,blue \"npm run dev:vue\" \"npm run dev:ele\"",
|
||||
"dev:vue": "vite",
|
||||
"dev:ele": "node script/build --env=development --watch",
|
||||
"build:vue": "vite build",
|
||||
"build:ele": "node script/build --env=production",
|
||||
"build": "npm run build:vue && npm run build:ele && electron-builder"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- main `electron` 启动后会加载 main 配置的文件
|
||||
- dev:all 使用 `concurrently` 同时启动 `dev:vue`、`dev:ele`
|
||||
- dev:ele `electron` 开发模式脚本,通过传入 `--watch` 表示 rollup 监听主进程变化自动编译
|
||||
- build 使用 `electron-builder` 打包
|
||||
|
||||
#### 启动一下试试
|
||||
```bash
|
||||
$ yarn dev
|
||||
|
||||
yarn run v1.22.4
|
||||
$ npm run dev:all
|
||||
|
||||
> electron-vue@0.0.1 dev:all D:\github\electron-vue-vite
|
||||
> concurrently -n=vue,ele -c=green,blue "npm run dev:vue" "npm run dev:ele"
|
||||
|
||||
[ele]
|
||||
[ele] > electron-vue@0.0.1 dev:ele D:\github\electron-vue-vite
|
||||
[ele] > node script/build --env=development --watch
|
||||
[ele]
|
||||
[vue]
|
||||
[vue] > electron-vue@0.0.1 dev:vue D:\github\electron-vue-vite
|
||||
[vue] > vite
|
||||
[vue]
|
||||
[vue] vite v1.0.0-rc.4
|
||||
[vue]
|
||||
[vue] Dev server running at:
|
||||
[vue] > Network: http://192.168.1.9:3344/
|
||||
[vue] > Network: http://192.168.119.1:3344/
|
||||
[vue] > Network: http://10.0.60.32:3344/
|
||||
[vue] > Local: http://localhost:3344/
|
||||
[vue]
|
||||
[ele] [2020-08-17T08:57:11.850Z] [electron-connect] [server] started electron process: 1488
|
||||
[ele] [2020-08-17T08:57:11.851Z] [electron-connect] [server] server created and listening on 30080
|
||||
[ele]
|
||||
```
|
||||
|
||||
#### 尾巴
|
||||
- 2019 款 13 寸 mac-pro 启动速度 4秒 左右
|
||||
- 奔腾 G4560 台机 CUP 神舟笔记本启动速度 6 秒左右
|
||||
- 毋庸置疑 vite 的方案比起 @vue/cli、umi、create-react-app 这类基于 webpack 的脚手架启动这块的优势大的多滴多
|
||||
- 技术总是飞快的迭代、进步,目的都是解决一些已经存在、或即将到来的问题;继续治疗、学习起来、加油哇~
|
70
blog/2.md
@ -1,70 +0,0 @@
|
||||
# electron-vue-vite
|
||||
`Electron` + `vue3` + `vite` 整合
|
||||
|
||||
#### [简书地址](https://www.jianshu.com/p/ee5ec23d4716)
|
||||
|
||||
## How and Why
|
||||
- 写这个 Demo 项目主要有两个目的
|
||||
1. `vue@3.x` 发布了,想试试新功能
|
||||
2. 工作中用的 `umi`+`electron` 项目大了,启动速度并不理想;
|
||||
用 `vite` 试试,算一个储备方案 ^_^
|
||||
|
||||
## Command
|
||||
- npm run dev
|
||||
- npm run build
|
||||
|
||||
## Note `踩坑记`
|
||||
- import { write } from 'fs' 的这种形式会被 vite 编译成 /@modules/fs?import
|
||||
- const { write } = require('fs') 这种形式就能用了 😉
|
||||
- const { ipcRenderer } = require('electron') 同理
|
||||
- 虽然开发期可以用 require 避开 vite 的编译问题,但是打包时候 rollup 那边又出了问题;
|
||||
* 拿 const Store = require('electron-store') 举例,在 vite.config.ts 中通过自定义 rollup 插件转换成 EMS 形式即可
|
||||
```javascript
|
||||
// vite.config.ts -> rollupInputOptions -> plugins
|
||||
plugins: [
|
||||
{
|
||||
name: '@rollup/plugin-cjs2esm',
|
||||
transform(code, filename) {
|
||||
if (filename.includes('/node_modules/')) {
|
||||
return code
|
||||
}
|
||||
|
||||
const cjsRegexp = /(const|let|var)[\n\s]+(\w+)[\n\s]*=[\n\s]*require\(["|'](.+)["|']\)/g
|
||||
const res = code.match(cjsRegexp)
|
||||
if (res) {
|
||||
// const Store = require('electron-store') -> import Store from 'electron-store'
|
||||
code = code.replace(cjsRegexp, `import $2 from '$3'`)
|
||||
}
|
||||
return code
|
||||
},
|
||||
}
|
||||
],
|
||||
```
|
||||
- "rollup-plugin-esbuild": "^2.4.2", 有 BUG `21-02-18`
|
||||
- **tsconfig.json中不能有多余的逗号,不然有如下警告** `21-02-18`
|
||||
```bash
|
||||
SyntaxError: Unexpected token ] in JSON at position 428
|
||||
at JSON.parse (<anonymous>)
|
||||
at Object.load (/Users/caoxie/Desktop/github/electron-vue-vite2/node_modules/rollup-plugin-esbuild/dist/index.js:21:17) {
|
||||
code: 'PLUGIN_ERROR',
|
||||
plugin: 'esbuild',
|
||||
hook: 'transform',
|
||||
id: '/Users/caoxie/Desktop/github/electron-vue-vite2/src/main/index.ts',
|
||||
watchFiles: [
|
||||
'/Users/caoxie/Desktop/github/electron-vue-vite2/src/main/index.ts'
|
||||
]
|
||||
}
|
||||
```
|
||||
- main 进程中暂时无法用 require,打包后会导致模块找不到 `21-02-18`
|
||||
- `"asar": false` 这样可以保障 `"extraResources"` 能够正常搬运到文件夹中 `21-02-18`
|
||||
|
||||
## 总结
|
||||
|
||||
- 2019 款 13 寸 mac-pro 启动速度 4秒 左右
|
||||
- 奔腾 G4560 台机 CUP 神舟笔记本启动速度 6 秒左右
|
||||
- 毋庸置疑 vite 的方案比起 @vue/cli、umi、create-react-app 这类基于 webpack 的脚手架启动这块的优势大的多滴多
|
||||
- 技术总是飞快的迭代、进步,目的都是解决一些已经存在、或即将到来的问题;继续治疗、学习起来、加油哇~
|
||||
|
||||
---
|
||||
|
||||
![](https://raw.githubusercontent.com/caoxiemeihao/electron-vue-vite/master/screenshot/800x600-2.png)
|
402
blog/3.md
@ -1,402 +0,0 @@
|
||||
# Vite 整合 Electron 总结
|
||||
|
||||
## 前言
|
||||
|
||||
- Vite 是面向未来几年的构建工具,很有必要在各个场景下都试试集成进来
|
||||
- Electron 作为前端标配的桌面开发工具,官方并有脚手架也没和哪个框架整合
|
||||
- `@vue/cli` 官方有给出模板;但是 Vite 这块并没提供,毕竟人家定位是和 `webpack` 那样的通用构建工具
|
||||
甚至连 Vue 都没集成 🖖 那么我们尝试来做下这件事儿
|
||||
按照 Vue 在 Vite 中的集成风格 Electron 这块应当写一个插件!
|
||||
|
||||
## 注意 📢
|
||||
- 这里假定你简单的知道一些 Vite 的工作原理,这种文章网上有好多的
|
||||
- 同时也假定你使用或者上手过 Electron;上手非常简单,直接看官网即可
|
||||
- 项目整体所有代码在这 [https://github.com/caoxiemeihao/electron-vue-vite](https://github.com/caoxiemeihao/electron-vue-vite) 可以直接 **用于生产** (亲点个 start 呗 😘)
|
||||
|
||||
## 目录结构设计
|
||||
|
||||
```tree
|
||||
·
|
||||
├── script 项目脚本目录
|
||||
├── src
|
||||
| ├── main Electron 主进程代码
|
||||
| ├── preload Electron 预加载目录
|
||||
| ├── render Electron 渲染进程 - 既 Vite 代码
|
||||
|
|
||||
├── vite.config.ts Vite 配置文件
|
||||
```
|
||||
|
||||
## vite.config.ts 配置
|
||||
|
||||
- Electron 支持全量的 NodeJs API 渲染进程难免会用到
|
||||
- Vite 基于的 Rollup 构建工程,所以我们改造 Rollup 部分配置,输出 CommonJs 格式
|
||||
- 目录结构相比于 Vite 提供的默认结构有所不同,也需要配置一番才能按预期工作
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { join } from 'path'
|
||||
|
||||
export default defineConfig((env) => ({
|
||||
plugins: [
|
||||
vue(), // 开启 Vue 支持
|
||||
],
|
||||
root: join(__dirname, 'src/render'), // 指向渲染进程目录
|
||||
base: './', // index.html 中静态资源加载位置
|
||||
build: {
|
||||
outDir: join(__dirname, 'dist/render'),
|
||||
assetsDir: '', // 相对路径 加载问题
|
||||
rollupOptions: {
|
||||
output: {
|
||||
format: 'cjs', // 配置 Rollup 打包输出 CommonJs 格式
|
||||
},
|
||||
external: ['electron'], // 告诉 Rollup 不要去打包 electron
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: ['electron'], // 告诉 Vite 不要转换 electron 模块
|
||||
},
|
||||
// 其他配置略...
|
||||
}))
|
||||
```
|
||||
|
||||
## 启动脚本分析
|
||||
|
||||
- 我们先出个结论 - Electron 的启动与 NodeJs 相比行为几乎是一致的 - `可执行程序` + `入口文件`
|
||||
|
||||
```shell
|
||||
# 全局目录安装的 NodeJs
|
||||
node path/filename.js
|
||||
|
||||
# 项目目录安装的 Electron
|
||||
node_modules/.bin/electron path/filename.js
|
||||
```
|
||||
- 如果我们把 Electron 的启动设计到 `npm` 的 `scripts` 中,它还可以更简单 `npm run electron`
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"electron": "electron path/filename.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
- 🤔 思考一下,`npm` 只有一个启动命令 Vite、Electron 各需要一个,加一起就是两个启动命令了这里我们借助 `concurrently` 来同时启动 Vite、Electron
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm run vite\" \"npm run electron\"",
|
||||
"vite": "vite",
|
||||
"electron": "electron path/filename.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
**看起来不错!**
|
||||
|
||||
- 不过我们再思考下关于 Electron 启动的问题
|
||||
1. 开发环境下 Electron 应该加载 Vite 启动的开发服务器,生产环境下启动一个具体的文件
|
||||
2. 那么这里就会出现一个等待的情况,就是 Electron 要等待 Vite 启动后再启动
|
||||
|
||||
## 启动脚本设计
|
||||
|
||||
- 我们需要监听 Vite 的启动后拉起 Electron,这块我们考虑用轮训监听端口的方式
|
||||
- 监听到 Vite 启动后 Electron 我们用 NodeJs 的子进程 API `child_process.spawn()` 拉起
|
||||
- 我们将 `npm scripts` 做下改动,方便我们知道脚本是干啥的,**相对上面的脚本这里重命名一下**
|
||||
`concurrently` 也加入一些命令行参数,能得好更友好的控制台输出
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "concurrently -n=vue,ele -c=green,blue \"npm run dev:vue\" \"npm run dev:ele\"",
|
||||
"dev:vite": "vite",
|
||||
"dev:electron": "node -r ts-node/register script/build-main --env=development --watch"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 由于我们要控制的是 Electron 的启动,为此我们单独写个脚本(script/build-main.ts)来控制,包括下面两个功能点
|
||||
1. 启动时机控制 - 监听 Vite 已经启动
|
||||
2. 主进程代码使用使用 typescript 开发 - 加入 Rollup 编译、打包
|
||||
|
||||
#### script/build-main.ts
|
||||
|
||||
```ts
|
||||
import { join } from 'path'
|
||||
import { get } from 'http'
|
||||
import { spawn, ChildProcess } from 'child_process'
|
||||
import { watch } from 'rollup'
|
||||
import minimist from 'minimist'
|
||||
import electron from 'electron'
|
||||
import options from './rollup.config'
|
||||
import { main } from '../package.json'
|
||||
|
||||
/**
|
||||
* 1. 监听 vite 启动
|
||||
*/
|
||||
function waitOn(arg0: { port: string | number; interval?: number; }) {
|
||||
return new Promise(resolve => {
|
||||
const { port, interval = 149 } = arg0
|
||||
const url = `http://localhost:${port}`
|
||||
|
||||
// 通过定时器轮训向 Vite 服务器请求
|
||||
const timer: NodeJS.Timer = setInterval(() => {
|
||||
get(
|
||||
`http://localhost:${port}`, // 指向 Vite 开发服务器
|
||||
res => {
|
||||
clearInterval(timer)
|
||||
resolve(res.statusCode)
|
||||
}
|
||||
)
|
||||
}, interval)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 2. 控制 Electron 启动时机,编译 typescript
|
||||
*/
|
||||
waitOn({ port: '3000' }).then(msg => {
|
||||
// 解析 npm script 的命令行参数
|
||||
const argv = minimist(process.argv.slice(2))
|
||||
|
||||
// 加载 rollup 配置
|
||||
const opts = options(argv.env)
|
||||
|
||||
// Vite 启动后以监听模式开启 Rollup 编译 Electron 主进程代码
|
||||
const watcher = watch(opts)
|
||||
|
||||
let child: ChildProcess
|
||||
|
||||
watcher.on('event', ev => {
|
||||
if (ev.code === 'END') {
|
||||
// 保证只启动一个 Electron 个程序
|
||||
if (child) child.kill()
|
||||
|
||||
// 使用 NodeJs 子进程能力拉起 Electron 程序
|
||||
child = spawn(
|
||||
// 这里 electron 本质上只是一个字符串;指向 Electron 可执行程序的绝对路径
|
||||
electron as any,
|
||||
|
||||
// 指定 Electron 主进程入口文件;既 Rollup 编译后输出文件的路径
|
||||
[join(__dirname, `../${main}`)], { stdio: 'inherit' }
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
#### script/rollup.config
|
||||
|
||||
```ts
|
||||
import { builtinModules } from 'module'
|
||||
import { join } from 'path'
|
||||
import { RollupOptions } from 'rollup'
|
||||
import nodeResolve from '@rollup/plugin-node-resolve'
|
||||
import commonjs from '@rollup/plugin-commonjs'
|
||||
import typescript from '@rollup/plugin-typescript'
|
||||
import json from '@rollup/plugin-json'
|
||||
|
||||
/** node.js builtins module */
|
||||
const builtins = () => builtinModules.filter(x => !/^_|^(internal|v8|node-inspect)\/|\//.test(x))
|
||||
|
||||
export default (env = 'production') => {
|
||||
const options: RollupOptions = {
|
||||
input: join(__dirname, '../src/main/index.ts'),
|
||||
output: {
|
||||
file: join(__dirname, '../dist/main/index.js'),
|
||||
format: 'cjs', // 使用 CommonJs 模块化
|
||||
},
|
||||
plugins: [
|
||||
nodeResolve(), // 支持 node_modules 下面的包查找
|
||||
commonjs(), // 支持 CommonJs 模块
|
||||
json(), // 支持引入 json 文件
|
||||
typescript({
|
||||
module: 'ESNext', // 支持 typescript
|
||||
}),
|
||||
],
|
||||
external: [
|
||||
// 打包避开内置模块
|
||||
...builtins(),
|
||||
'electron',
|
||||
],
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
```
|
||||
|
||||
**到这为止,项目应该是能跑起来的状态了;但是还不能使用 Electron、NodeJs 相关的 API**
|
||||
|
||||
> 这里简单的改了下 App.vue、HelloWorld.vue 中的一些文案,没做逻辑修改;就不贴出代码了
|
||||
|
||||
![import-electron.jpg](./images/main.jpg)
|
||||
|
||||
## 加入 Electron API
|
||||
|
||||
- 渲染和主进程通信是个十分常用的功能;我试着从 `electron` 导出 `ipcRenderer`
|
||||
|
||||
```ts
|
||||
// src/render/main.ts
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
console.log('ipcRenderer:', ipcRenderer)
|
||||
|
||||
createApp(App).mount('#app')
|
||||
```
|
||||
|
||||
![import-electron.jpg](./images/import-electron.jpg)
|
||||
|
||||
- **报错了!默认情况下直接用 import 语法会被 Rollup 编译**
|
||||
- 事实上在 `'electron'` 在 Electron 运行环境中是一个 **内置模块** 你可以在控制台中试试下这段代码
|
||||
|
||||
> *注意这里先不要引入 Electron 相关的包,保障项目能跑起来*
|
||||
|
||||
```js
|
||||
require.resolve('electron')
|
||||
"electron" // 将会输出
|
||||
```
|
||||
|
||||
- 既然 Electron 本就支持全量的 NodeJs API 我们不妨直接在在代码中直接写成
|
||||
|
||||
```diff
|
||||
// src/render/main.ts
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
- import { ipcRenderer } from 'electron'
|
||||
+ const { ipcRenderer } = require('electron')
|
||||
|
||||
console.log('ipcRenderer:', ipcRenderer)
|
||||
|
||||
createApp(App).mount('#app')
|
||||
```
|
||||
|
||||
**项目确实跑起来了,我们还可以用这个办法进一步验证其他的模块**
|
||||
|
||||
![require-electron.jpg](./images/require-electron.jpg)
|
||||
|
||||
## 插件设计分析
|
||||
|
||||
- 可以打包票说,这个可以用!(仅限开发期);但是这个会带来两个问题
|
||||
1. 编码风格不统一,人家都在用 `ESModule` 混入 `CommonJs` 确实不好看
|
||||
2. `require('xxxx')` 在打包期间如果不做些处理,并不会被 Rollup 处理 (这里只`.ts`文件,有大神知道怎么对付这种情况的请指点下小弟)
|
||||
如果你引入的是 `node_modules` 中的包那可就惨了;比如 `require('electron-store')` 这种会原样输出;打包后的程序开起来会找不到 `'electron-store'` 这个模块,铁定报错!
|
||||
|
||||
- 我们知道 `ESModule` 写法在开发期运行会报错,但是还是要写;
|
||||
如果我们在运行的前一刻将 ESModule 转换成 NodeJs 内置的 CommonJs 岂不是两全其美的好事;
|
||||
甚至只要有关 NodeJs API 的包我们都可以转化,毕竟开发期项目根目录是有 `node_modules` 这个 **NodeJs包仓库** 给你用的!
|
||||
|
||||
**分析至此,我们该动动手写个插件了;让插件去自动化完成 - ESModule to CommonJs**
|
||||
|
||||
## vitejs-plugin-electron
|
||||
|
||||
- Vite 插件上手教程请看官网 [https://vitejs.dev/guide/api-plugin.html](https://vitejs.dev/guide/api-plugin.html) (我个人觉得比 `webpack` 那边的插件要好写)
|
||||
- 为了支持传参方便日后扩展,我们把它成一个 Function 并返回一个插件
|
||||
- 代码处理这块,我们需要一个 AST 工具帮忙 - `yarn add acorn`
|
||||
|
||||
```ts
|
||||
import * as acorn from 'acorn'
|
||||
import { Plugin as VitePlugin } from 'vite'
|
||||
|
||||
const extensions = ['.js', '.jsx', '.ts', '.tsx', '.vue'] // 需要处理的文件后缀
|
||||
|
||||
export interface Esm2cjsOptions {
|
||||
excludes?: string[] // 需要被转换的模块
|
||||
}
|
||||
|
||||
export default function esm2cjs(options?: Esm2cjsOptions): VitePlugin {
|
||||
const opts: Esm2cjsOptions = {
|
||||
// 默认我们转换 electron、electron-store 两个模块
|
||||
excludes: [
|
||||
'electron',
|
||||
'electron-store',
|
||||
],
|
||||
...options
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'vitejs-plugin-electron', // 这个 name 就是插件名字
|
||||
transform(code, id) {
|
||||
const parsed = path.parse(id) // 解析引入模块的路径,id 即引入文件完整路径
|
||||
if (!extensions.includes(parsed.ext)) return // 只处理需要处理的文件后缀
|
||||
|
||||
const node: any = acorn.parse(code, { // 使用 acorn 解析 ESTree
|
||||
ecmaVersion: 'latest', // 指定按照最新的 es 模块标准解析
|
||||
sourceType: 'module', // 指定按照模块进行解析
|
||||
})
|
||||
|
||||
let codeRet = code
|
||||
node.body.reverse().forEach((item) => {
|
||||
if (item.type !== 'ImportDeclaration') return // 跳过非 import 语句
|
||||
if (!opts.excludes.includes(item.source.value)) return // 跳过不要转换的模块
|
||||
|
||||
/**
|
||||
* 下面这些 const 声明用来确定 import 的写法
|
||||
*/
|
||||
const statr = codeRet.substring(0, item.start)
|
||||
const end = codeRet.substring(item.end)
|
||||
const deft = item.specifiers.find(({ type }) => type === 'ImportDefaultSpecifier')
|
||||
const deftModule = deft ? deft.local.name : ''
|
||||
const nameAs = item.specifiers.find(({ type }) => type === 'ImportNamespaceSpecifier')
|
||||
const nameAsModule = nameAs ? nameAs.local.name : ''
|
||||
const modules = item.
|
||||
specifiers
|
||||
.filter((({ type }) => type === 'ImportSpecifier'))
|
||||
.reduce((acc, cur) => acc.concat(cur.imported.name), [])
|
||||
|
||||
/**
|
||||
* 这里开始根据各种 import 语法做转换
|
||||
*/
|
||||
if (nameAsModule) {
|
||||
// import * as name from
|
||||
codeRet = `${statr}const ${nameAsModule} = require(${item.source.raw})${end}`
|
||||
} else if (deftModule && !modules.length) {
|
||||
// import name from 'mod'
|
||||
codeRet = `${statr}const ${deftModule} = require(${item.source.raw})${end}`
|
||||
} else if (deftModule && modules.length) {
|
||||
// import name, { name2, name3 } from 'mod'
|
||||
codeRet = `${statr}const ${deftModule} = require(${item.source.raw})
|
||||
const { ${modules.join(', ')} } = ${deftModule}${end}`
|
||||
} else {
|
||||
// import { name1, name2 } from 'mod'
|
||||
codeRet = `${statr}const { ${modules.join(', ')} } = require(${item.source.raw})${end}`
|
||||
}
|
||||
})
|
||||
|
||||
return codeRet
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
- 在 `vite.config.ts` 中使用 `vitejs-plugin-electron`
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import electron from 'vitejs-plugin-electron'
|
||||
|
||||
export default defineConfig((env) => ({
|
||||
plugins: [
|
||||
vue(),
|
||||
electron(),
|
||||
],
|
||||
// 其他配置略...
|
||||
}))
|
||||
```
|
||||
|
||||
- 再次运行下项目
|
||||
![plugin-electron.jpg](./images/plugin-electron.jpg)
|
||||
|
||||
- **It's Worked!** 🎉 🎉 🎉
|
||||
|
||||
- 好了,这个插件可以用了;再想想上面的关于 Rollup 的配置其实我们完全可以集成到 `vitejs-plugin-electron` 中的,这样会使 `vite.config.ts` 文件更少、更清晰;具体代码就不演示了,自己拉代码看看吧 🚀
|
||||
- [https://github.com/caoxiemeihao/vitejs-plugins/tree/main/electron](https://github.com/caoxiemeihao/vitejs-plugins/tree/main/electron)
|
||||
|
||||
## 总结
|
||||
|
||||
- Vite 个人觉得是个不错的方案,毕竟打包工具早晚会推出历史舞台;Vite 往前又迈了 `0.5步`
|
||||
- Electron 的集成只是一个案例,从一个案例出发到写一个插件,你会更好的理解 Vite 设计、思想
|
||||
- 最后,不能什么都站在客观的角度去等待,更需要我们主动的去**建设**
|
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 244 KiB |
Before Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 167 KiB |
Before Width: | Height: | Size: 110 KiB |
Before Width: | Height: | Size: 150 KiB |
Before Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 102 KiB |
Before Width: | Height: | Size: 161 KiB |
Before Width: | Height: | Size: 169 KiB |
Before Width: | Height: | Size: 208 KiB |
Before Width: | Height: | Size: 165 KiB |
Before Width: | Height: | Size: 215 KiB |
Before Width: | Height: | Size: 211 KiB |
Before Width: | Height: | Size: 161 KiB |
Before Width: | Height: | Size: 201 KiB |
Before Width: | Height: | Size: 210 KiB |
Before Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 319 KiB |