This commit is contained in:
砂糖
2026-02-07 18:01:13 +08:00
commit 8015759c65
2110 changed files with 269866 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
'use strict'
process.env.NODE_ENV = 'production'
const { say } = require('cfonts')
const chalk = require('chalk')
const del = require('del')
const webpack = require('webpack')
const { Listr } = require('listr2')
const mainConfig = require('./webpack.main.config')
const rendererConfig = require('./webpack.renderer.config')
const doneLog = chalk.bgGreen.white(' DONE ') + ' '
const errorLog = chalk.bgRed.white(' ERROR ') + ' '
const okayLog = chalk.bgBlue.white(' OKAY ') + ' '
const isCI = process.env.CI || false
if (process.env.BUILD_TARGET === 'web') web()
else build()
function clean() {
del.sync(['dist/electron/*', 'build/*', '!build/icons', '!build/lib', '!build/lib/electron-build.*', '!build/icons/icon.*'])
console.log(`\n${doneLog}clear done`)
if (process.env.BUILD_TARGET === 'onlyClean') process.exit()
}
function build() {
greeting()
if (process.env.BUILD_TARGET === 'clean' || process.env.BUILD_TARGET === 'onlyClean') clean()
const tasksLister = new Listr([
{
title: 'building main process',
task: async (_, tasks) => {
try {
await pack(mainConfig)
} catch (error) {
console.error(`\n${error}\n`)
console.log(`\n ${errorLog}failed to build main process`)
process.exit(1)
}
}
},
{
title: "building renderer process",
task: async (_, tasks) => {
try {
await pack(rendererConfig)
tasks.output = `${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`
} catch (error) {
console.error(`\n${error}\n`)
console.log(`\n ${errorLog}failed to build renderer process`)
process.exit(1)
}
},
options: { persistentOutput: true }
}
], {
exitOnError: true
})
tasksLister.run()
}
function pack(config) {
return new Promise((resolve, reject) => {
config.mode = 'production'
webpack(config, (err, stats) => {
if (err) reject(err.stack || err)
else if (stats.hasErrors()) {
let err = ''
stats.toString({
chunks: false,
colors: true
})
.split(/\r?\n/)
.forEach(line => {
err += ` ${line}\n`
})
reject(err)
} else {
resolve(stats.toString({
chunks: false,
colors: true
}))
}
})
})
}
function web() {
del.sync(['dist/web/*', '!.gitkeep'])
rendererConfig.mode = 'production'
webpack(rendererConfig, (err, stats) => {
if (err || stats.hasErrors()) console.log(err)
console.log(stats.toString({
chunks: false,
colors: true
}))
process.exit()
})
}
function greeting() {
const cols = process.stdout.columns
let text = ''
if (cols > 85) text = `let's-build`
else if (cols > 60) text = `let's-|build`
else text = false
if (text && !isCI) {
say(text, {
colors: ['yellow'],
font: 'simple3d',
space: false
})
} else console.log(chalk.yellow.bold(`\n let's-build`))
console.log()
}

View File

@@ -0,0 +1,210 @@
'use strict'
process.env.NODE_ENV = 'development'
const chalk = require('chalk')
const electron = require('electron')
const path = require('path')
const { say } = require('cfonts')
const { spawn } = require('child_process')
const config = require('../config')
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const Portfinder = require("portfinder")
const mainConfig = require('./webpack.main.config')
const rendererConfig = require('./webpack.renderer.config')
let electronProcess = null
let manualRestart = false
function logStats(proc, data) {
let log = ''
log += chalk.yellow.bold(`${proc} ${config.dev.chineseLog ? '编译过程' : 'Process'} ${new Array((19 - proc.length) + 1).join('-')}`)
log += '\n\n'
if (typeof data === 'object') {
data.toString({
colors: true,
chunks: false
}).split(/\r?\n/).forEach(line => {
log += ' ' + line + '\n'
})
} else {
log += ` ${data}\n`
}
log += '\n' + chalk.yellow.bold(`${new Array(28 + 1).join('-')}`) + '\n'
console.log(log)
}
function removeJunk(chunk) {
if (config.dev.removeElectronJunk) {
// Example: 2018-08-10 22:48:42.866 Electron[90311:4883863] *** WARNING: Textured window <AtomNSWindow: 0x7fb75f68a770>
if (/\d+-\d+-\d+ \d+:\d+:\d+\.\d+ Electron(?: Helper)?\[\d+:\d+] /.test(chunk)) {
return false;
}
// Example: [90789:0810/225804.894349:ERROR:CONSOLE(105)] "Uncaught (in promise) Error: Could not instantiate: ProductRegistryImpl.Registry", source: chrome-devtools://devtools/bundled/inspector.js (105)
if (/\[\d+:\d+\/|\d+\.\d+:ERROR:CONSOLE\(\d+\)\]/.test(chunk)) {
return false;
}
// Example: ALSA lib confmisc.c:767:(parse_card) cannot find card '0'
if (/ALSA lib [a-z]+\.c:\d+:\([a-z_]+\)/.test(chunk)) {
return false;
}
}
return chunk;
}
function startRenderer() {
return new Promise((resolve, reject) => {
rendererConfig.mode = 'development'
Portfinder.basePort = config.dev.port || 9080
Portfinder.getPort((err, port) => {
if (err) {
reject("PortError:" + err)
} else {
const compiler = webpack(rendererConfig)
compiler.hooks.done.tap('done', stats => {
logStats('Renderer', stats)
})
const server = new WebpackDevServer(
{
port,
static: {
directory: path.join(__dirname, '..', 'static'),
publicPath: '/static/',
}
},
compiler
)
process.env.PORT = port
server.start().then(() => {
resolve()
})
}
})
})
}
function startMain() {
return new Promise((resolve) => {
mainConfig.mode = 'development'
const compiler = webpack(mainConfig)
compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
logStats(`${config.dev.chineseLog ? '主进程' : 'Main'}`, chalk.white.bold(`${config.dev.chineseLog ? '正在处理资源文件...' : 'compiling...'}`))
done()
})
compiler.watch({}, (err, stats) => {
if (err) {
console.log(err)
return
}
logStats(`${config.dev.chineseLog ? '主进程' : 'Main'}`, stats)
if (electronProcess && electronProcess.kill) {
manualRestart = true
process.kill(electronProcess.pid)
electronProcess = null
startElectron()
setTimeout(() => {
manualRestart = false
}, 5000)
}
resolve()
})
})
}
function startElectron() {
var args = [
'--inspect=5858',
path.join(__dirname, '../dist/electron/main.js')
]
// detect yarn or npm and process commandline args accordingly
if (process.env.npm_execpath.endsWith('yarn.js')) {
args = args.concat(process.argv.slice(3))
} else if (process.env.npm_execpath.endsWith('npm-cli.js')) {
args = args.concat(process.argv.slice(2))
}
electronProcess = spawn(electron, args)
electronProcess.stdout.on('data', data => {
electronLog(removeJunk(data), 'blue')
})
electronProcess.stderr.on('data', data => {
electronLog(removeJunk(data), 'red')
})
electronProcess.on('close', () => {
if (!manualRestart) process.exit()
})
}
function electronLog(data, color) {
if (data) {
let log = ''
data = data.toString().split(/\r?\n/)
data.forEach(line => {
log += ` ${line}\n`
})
console.log(
chalk[color].bold(`${config.dev.chineseLog ? '主程序日志' : 'Electron'} -------------------`) +
'\n\n' +
log +
chalk[color].bold('┗ ----------------------------') +
'\n'
)
}
}
function greeting() {
const cols = process.stdout.columns
let text = ''
if (cols > 104) text = 'electron-vue'
else if (cols > 76) text = 'electron-|vue'
else text = false
if (text) {
say(text, {
colors: ['yellow'],
font: 'simple3d',
space: false
})
} else console.log(chalk.yellow.bold('\n electron-vue'))
console.log(chalk.blue(`${config.dev.chineseLog ? ' 准备启动...' : ' getting ready...'}`) + '\n')
}
async function init() {
greeting()
try {
await startRenderer()
await startMain()
await startElectron()
} catch (error) {
console.error(error)
}
}
init()

View File

@@ -0,0 +1,122 @@
/**
* power by biuuu
*/
const chalk = require("chalk");
const { join } = require('path')
const crypto = require('crypto')
const AdmZip = require('adm-zip')
const packageFile = require('../package.json')
const { build } = require("../config/index")
const { platform } = require("os")
const { ensureDir, emptyDir, copy, outputJSON, remove, stat, readFile } = require("fs-extra");
const platformName = platform().includes('win32') ? 'win' : platform().includes('darwin') ? 'mac' : 'linux'
const buildPath = join('.', 'build', `${platformName === 'mac' ? 'mac' : platformName + '-unpacked'}`)
const hash = (data, type = 'sha256') => {
const hmac = crypto.createHmac(type, 'Sky')
hmac.update(data)
return hmac.digest('hex')
}
const createZip = (filePath, dest) => {
const zip = new AdmZip()
zip.addLocalFolder(filePath)
zip.toBuffer()
zip.writeZip(dest)
}
const start = async () => {
console.log(chalk.green.bold(`\n Start packing`))
if (packageFile.build.asar) {
console.log(
"\n" +
chalk.bgRed.white(" ERROR ") +
" " +
chalk.red("Please make sure the build.asar option in the Package.json file is set to false") +
"\n"
);
return;
}
if (build.hotPublishConfigName === '') {
console.log(
"\n" +
chalk.bgRed.white(" ERROR ") +
" " +
chalk.red("HotPublishConfigName is not set, which will cause the update to fail, please set it in the config/index.js \n")
+ chalk.red.bold(`\n Packing failed \n`)
);
process.exit(1)
}
stat(join(buildPath, 'resources', 'app'), async (err, stats) => {
if (err) {
console.log(
"\n" +
chalk.bgRed.white(" ERROR ") +
" " +
chalk.red("No resource files were found, please execute this command after the build command") +
"\n"
);
return;
}
try {
const packResourcesPath = join('.', 'build', 'resources', 'dist');
const packPackagePath = join('.', 'build', 'resources');
const resourcesPath = join('.', 'dist');
const appPath = join('.', 'build', 'resources');
const name = "app.zip";
const outputPath = join('.', 'build', 'update');
const zipPath = join(outputPath, name);
await ensureDir(packResourcesPath);
await emptyDir(packResourcesPath);
await copy(resourcesPath, packResourcesPath);
await outputJSON(join(packPackagePath, "package.json"), {
name: packageFile.name,
productName: packageFile.productName,
version: packageFile.version,
private: packageFile.private,
description: packageFile.description,
main: packageFile.main,
author: packageFile.author,
dependencies: packageFile.dependencies
});
await ensureDir(outputPath);
await emptyDir(outputPath);
createZip(appPath, zipPath);
const buffer = await readFile(zipPath);
const sha256 = hash(buffer);
const hashName = sha256.slice(7, 12);
await copy(zipPath, join(outputPath, `${hashName}.zip`));
await remove(zipPath);
await remove(appPath)
await outputJSON(join(outputPath, `${build.hotPublishConfigName}.json`),
{
version: packageFile.version,
name: `${hashName}.zip`,
hash: sha256
}
);
console.log(
"\n" + chalk.bgGreen.white(" DONE ") + " " + "The resource file is packaged!\n"
);
console.log("File location: " + chalk.green(outputPath) + "\n");
} catch (error) {
console.log(
"\n" +
chalk.bgRed.white(" ERROR ") +
" " +
chalk.red(error.message || error) +
"\n"
);
process.exit(1)
}
});
}
start()

View File

@@ -0,0 +1,101 @@
'use strict'
const MiniCssPlugin = require('mini-css-extract-plugin');
const dotenv = require('dotenv')
const { join } = require("path")
const argv = require('minimist')(process.argv.slice(2));
const rootResolve = (...pathSegments) => join(__dirname, '..', ...pathSegments)
function getEnv() {
return argv['m']
}
function getEnvPath() {
if (String(typeof getEnv()) === 'boolean' || String(typeof getEnv()) === 'undefined') {
return rootResolve('env/.env')
}
return rootResolve(`env/${getEnv()}.env`)
}
function getConfig() {
return dotenv.config({ path: getEnvPath() }).parsed
}
// 获取环境
exports.getEnv = getEnv()
// 获取配置
exports.getConfig = getConfig()
exports.cssLoaders = function (options) {
options = options || {}
const esbuildCss = {
loader: 'esbuild-loader',
options: {
loader: 'css',
minify: options.minifyCss
}
}
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap,
esModule: false
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}
// 这里就是生成loader和其对应的配置
function generateLoaders(loader, loaderOptions) {
const loaders = [cssLoader, postcssLoader, esbuildCss]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// 当配置信息中开启此项时启用css分离压缩
// 这一项在生产环境时,是默认开启的
if (options.extract) {
return [MiniCssPlugin.loader].concat(loaders)
} else {
// 如果不开启则让vue-style-loader来处理
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// 根据上面的函数遍历出来的各个css预处理器的loader进行最后的拼装
exports.styleLoaders = function (options) {
const output = []
const loaders = exports.cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}

View File

@@ -0,0 +1,99 @@
'use strict'
process.env.BABEL_ENV = 'main'
const path = require('path')
const { dependencies } = require('../package.json')
const webpack = require('webpack')
const TerserPlugin = require('terser-webpack-plugin')
const config = require('../config')
const { getConfig } = require("./utils")
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
let mainConfig = {
infrastructureLogging: {
level: 'warn'
},
entry: {
main: path.join(__dirname, '../src/main/index.js')
},
externals: [
...Object.keys(dependencies || {})
],
module: {
rules: [
{
test: /\.js$/,
loader: 'esbuild-loader'
},
{
test: /\.node$/,
use: 'node-loader'
}
]
},
node: {
__dirname: process.env.NODE_ENV !== 'production',
__filename: process.env.NODE_ENV !== 'production'
},
output: {
filename: '[name].js',
libraryTarget: 'commonjs2',
path: path.join(__dirname, '../dist/electron')
},
plugins: [
new webpack.DefinePlugin({
'process.env.userConfig':JSON.stringify(getConfig)
})
],
resolve: {
alias: {
'@config': resolve('config'),
},
extensions: ['.js', '.json', '.node']
},
target: 'electron-main',
}
/**
* Adjust mainConfig for development settings
*/
if (process.env.NODE_ENV !== 'production') {
mainConfig.plugins.push(
new webpack.DefinePlugin({
'__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`,
'process.env.libPath': `"${path.join(__dirname, `../${config.DllFolder}`).replace(/\\/g, '\\\\')}"`
})
)
}
/**
* Adjust mainConfig for production settings
*/
if (process.env.NODE_ENV === 'production' && config.build.cleanConsole) {
mainConfig.optimization = {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ["console.log", "console.warn"]
}
}
})
]
}
mainConfig.plugins.push(
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
})
)
}
module.exports = mainConfig

View File

@@ -0,0 +1,219 @@
'use strict'
const IsWeb = process.env.BUILD_TARGET === 'web'
process.env.BABEL_ENV = IsWeb ? 'web' : 'renderer'
const path = require('path')
const { dependencies } = require('../package.json')
const webpack = require('webpack')
const config = require('../config')
const { styleLoaders } = require('./utils')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
// const ESLintPlugin = require('eslint-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader')
const { getConfig } = require("./utils")
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
/**
* List of node_modules to include in webpack bundle
*
* Required for specific packages like Vue UI libraries
* that provide pure *.vue files that need compiling
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals
*/
let rendererConfig = {
entry: IsWeb ? { web: path.join(__dirname, '../src/renderer/main.js') } : { renderer: resolve('src/renderer/main.js') },
infrastructureLogging: { level: 'warn' },
stats: 'none',
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader",
options: {
babelParserPlugins: [
'jsx',
'classProperties',
'decorators-legacy'
]
}
},
{
test: /\.jsx$/,
loader: 'babel-loader',
},
{
test: /\.html$/,
use: 'vue-html-loader'
},
{
test: /\.svg$/,
loader: 'svg-sprite-loader',
include: [resolve('src/renderer/icons')],
options: {
symbolId: 'icon-[name]'
}
},
{
test: /\.(png|jpe?g|gif)(\?.*)?$/,
type: "asset/resource",
generator: {
filename: 'imgs/[name]--[hash].[ext]'
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
type: "asset/resource",
generator: {
filename: 'media/[name]--[hash].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
type: "asset/resource",
generator: {
filename: 'fonts/[name]--[hash].[ext]'
}
}
]
},
node: {
__dirname: process.env.NODE_ENV !== 'production',
__filename: process.env.NODE_ENV !== 'production'
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin(),
new webpack.DefinePlugin({
'process.env.userConfig': JSON.stringify(getConfig),
'process.env.IS_WEB': IsWeb
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: resolve('src/index.ejs'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
minifyJS: true,
minifyCSS: true
},
templateParameters(compilation, assets, options) {
return {
compilation: compilation,
webpack: compilation.getStats().toJson(),
webpackConfig: compilation.options,
htmlWebpackPlugin: {
files: assets,
options: options
},
process,
};
},
nodeModules: false
}),
],
output: {
filename: '[name].js',
path: IsWeb ? path.join(__dirname, '../dist/web') : path.join(__dirname, '../dist/electron')
},
resolve: {
alias: {
'@': resolve('src/renderer'),
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['.js', '.vue', '.json', '.css', '.node']
},
target: IsWeb ? 'web' : 'electron-renderer'
}
// 将css相关得loader抽取出来
rendererConfig.module.rules = rendererConfig.module.rules.concat(styleLoaders({ sourceMap: process.env.NODE_ENV !== 'production' ? config.dev.cssSourceMap : false, extract: IsWeb, minifyCss: process.env.NODE_ENV === 'production' }));
(IsWeb || config.UseJsx) ? rendererConfig.module.rules.push({ test: /\.m?[jt]sx$/, use: [{ loader: 'babel-loader', options: { cacheDirectory: true } }] }) : rendererConfig.module.rules.push({ test: /\.m?[jt]s$/, loader: 'esbuild-loader', options: { loader: 'ts', } })
/**
* Adjust rendererConfig for development settings
*/
if (process.env.NODE_ENV !== 'production' && !IsWeb) {
rendererConfig.plugins.push(
new webpack.DefinePlugin({
__lib: `"${path.join(__dirname, `../${config.DllFolder}`).replace(/\\/g, '\\\\')}"`
})
)
}
/**
* Adjust rendererConfig for production settings
*/
if (process.env.NODE_ENV === 'production') {
rendererConfig.plugins.push(
new CopyWebpackPlugin({
patterns: [
{
from: path.join(__dirname, '../static'),
to: path.join(__dirname, '../dist/electron/static'),
globOptions: {
ignore: ['.*']
}
}
]
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"',
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
)
rendererConfig.optimization = {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ["console.log", "console.warn"]
}
}
})
]
}
rendererConfig.optimization.splitChunks = {
chunks: "async",
cacheGroups: {
vendor: { // 将第三方模块提取出来
minSize: 30000,
minChunks: 1,
test: /node_modules/,
chunks: 'initial',
name: 'vendor',
priority: 1
},
commons: {
test: /[\\/]src[\\/]common[\\/]/,
name: 'commons',
minSize: 30000,
minChunks: 3,
chunks: 'initial',
priority: -1,
reuseExistingChunk: true // 这个配置允许我们使用已经存在的代码块
}
}
}
rendererConfig.optimization.runtimeChunk = { name: 'runtime' }
} else {
rendererConfig.devtool = 'eval-source-map'
// eslint
// rendererConfig.plugins.push(new ESLintPlugin(config.dev.ESLintoptions))
}
module.exports = rendererConfig