Refactor afterPack hook to be more maintainable

Nothing is hardcoded in the hook any more, everything can be configured through the electron-builder configuration.
This commit is contained in:
sdfasd 2020-08-30 00:56:57 -07:00 committed by hormelcookies
parent ed071d0996
commit 67905e01a7
4 changed files with 141 additions and 60 deletions

View File

@ -1,14 +1,25 @@
'use strict'; 'use strict';
/* /*
This is a hack to get around the issues with electron-builder not including nested node_modules This is a hack to get around the issues with electron-builder not including nested node_modules.
We do this by simply making the asar ourselves using the parameters from the build. We do this by simply making the asar ourselves using the parameters from the build.
This takes the asar file that we have created, unpacks it, then copies in the nested node_modules
directories from the app directory.
This also preserves any asar unpacked files that you may have had in the build, and unpacks any
nested node_modules files that might have been unpacked as well.
This also assumes that you have some sort of beforeBuild hook that already trims and massages the
nested node_modules files to be what you want; if not, you may have some bloat (and some potential
breakage)
*/ */
// Make sure you have these in your devDependencies in your root project.
const glob = require('fast-glob'); const glob = require('fast-glob');
const fs = require('fs-extra'); const fs = require('fs-extra');
const { promisify } = require('util') const { promisify } = require('util')
const rimraf = promisify(require('rimraf')) const rimraf = promisify(require('rimraf'))
const asar = require('asar'); const asar = require('asar');
const micromatch = require("micromatch")
const commonExclude = [ const commonExclude = [
"!**/{test,__tests__,tests,powered-test,example,examples,CHANGELOG.md,README.md,README,readme.md,readme}", "!**/{test,__tests__,tests,powered-test,example,examples,CHANGELOG.md,README.md,README,readme.md,readme}",
@ -21,79 +32,110 @@ const commonExclude = [
"!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}" "!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}"
] ]
exports.default = async function afterPackHook(context){ function concatUnique(a,b) {
if (!Array.isArray(a) || !Array.isArray(b)){
throw Error("both parameters must be arrays")
}
let arr = a.concat(b)
let seen = {};
return arr.filter(function(item) {
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
});
}
// we're using the platform-specific parameters over the global parameters.
function globalOrPlatformParam(local,global){
if (typeof local != 'undefined' && local.length != 0){
return local;
} else if (typeof global != 'undefined' && global.length != 0){
return global;
}
return undefined;
}
function globalOrPlatformArrayParam(local, global){
let param = globalOrPlatformParam(local, global) || []
if (typeof param == "string"){
param = [param]
}
return param;
}
exports.default = async function afterPackHook(context){
// Get build parameters from the context.
const appDir = context.packager.info._appDir + "/" const appDir = context.packager.info._appDir + "/"
const platform = context.packager.platform.nodeName const platform = context.packager.platform.nodeName
const globalFilesParam = context.packager.info._configuration.files;
const platformFilesParam = context.packager.platformSpecificBuildOptions.files
const globalUnpackAsarParam = context.packager.info._configuration.asarUnpack;
const platformUnpackAsarParam = context.packager.platformSpecificBuildOptions.asarUnpack;
let resourcesDir = context.appOutDir + "/resources/" let resourcesDir = context.appOutDir + "/resources/"
// exception for resources dir for mac // exception for resources dir for mac; win and linux use the same directory.
if (platform == "darwin"){ if (platform == "darwin"){
resourcesDir = context.appOutDir + "/" + context.packager.appInfo.productFilename + ".app/Contents/Resources/" resourcesDir = context.appOutDir + "/" + context.packager.appInfo.productFilename + ".app/Contents/Resources/"
} }
const asarAppDir = resourcesDir + "app/" const asarAppUnpackedDir = resourcesDir + "app.asar.unpacked/"
let globPatterns = context.packager.platformSpecificBuildOptions.files || context.packager._configuration.files || [] // The Platform-specific build options override the common config, so attempt to use those first.
let asarUnpackPattern = context.packager.platformSpecificBuildOptions.asarUnpack let globPatterns = globalOrPlatformArrayParam(platformFilesParam, globalFilesParam)
// a limitation of this method is that the asarUnpack option can only be a single string
if (typeof asarUnpackPattern != "string"){
if (Array.isArray(asarUnpackPattern) && asarUnpackPattern.length == 1){
asarUnpackPattern = asarUnpackPattern[0]
} else {
throw Error("asarUnpack pattern can only be one string!")
}
}
// remove current asar files
await rimraf(resourcesDir + "app.asar")
await rimraf(resourcesDir + "app")
await rimraf(resourcesDir + "app.asar.unpacked")
// electron-builder automatically adds this to the files parameter, so we have to too // electron-builder automatically adds this to the files parameter, so we have to too
if (!globPatterns.includes("**/*")){ if (!globPatterns.includes("**/*")){
globPatterns.push("**/*") globPatterns.push("**/*")
} }
globPatterns = globPatterns.concat(commonExclude) // Add the common exclusions to the patterns
globPatterns = concatUnique(globPatterns,commonExclude);
// Take the files from the app directory and copy them to make the asar.app directory
// according to the glob specified in the electron-builder config for this target
let files = glob.sync(globPatterns, { dot:true, cwd: appDir})
await new Promise ((resolve) => {
files.forEach(async (file, index, array)=>{
await fs.copy(appDir + file, asarAppDir + file)
if (index == array.length -1){
resolve()
}
})
}).catch(console.error)
// The only part that's hardcoded and not dependent on the electron-build config, // Find all the nested node_modules files in the app directory
// remove the unnecessary node-native files from the asar.app dir let nestedNMFiles = glob.sync("+(**/node_modules/**/*|!node_modules/**/*)", {cwd: appDir, dot:true})
let unpackedFiles = glob.sync(asarUnpackPattern, {dot:true, cwd: asarAppDir})
await new Promise ((resolve) => {
unpackedFiles.forEach(async (file, index, array)=>{
if (platform == "win32") {
if (file.includes(".node") && (file.includes("_linux") || file.includes("_darwin"))){
await fs.remove(asarAppDir + file)
}
}
else{
if (file.includes(".node") && !file.includes("_" + platform) ){
await fs.remove(asarAppDir + file)
}
}
if (index == array.length -1){ // filter out the ones not matched by the file globs in the builder config
resolve() let filteredNestedFiles = micromatch(nestedNMFiles, globPatterns, {matchBase:true})
}
})
}).catch(console.error)
// build the asar from the newly created app dir, unpacking the files necessary according to the glob // This is the directory we're copying everything to, to create the asar
await asar.createPackageWithOptions(asarAppDir, resourcesDir + "app.asar", {unpack: asarUnpackPattern}) let asarAppDir = resourcesDir + "app/"
// Get a listing of all the files in the app.asar.unpacked dir, so we can create a franken-glob
// to pass to asar when packing.
let unpackedFileList = glob.sync("**/*", {cwd:asarAppUnpackedDir, dot:true})
let unpackPattern = "{"
unpackedFileList.forEach((file,idx,arr)=>{
unpackPattern += asarAppDir + file + ','
})
// Get any user-defined asarUnpack patterns in case we need to unpack some hoisted node_modules,
// and add any to the pattern.
let userUnpackGlob = globalOrPlatformArrayParam(platformUnpackAsarParam, globalUnpackAsarParam)
let nestedFilesToUnpack = micromatch(nestedNMFiles, userUnpackGlob, {matchBase:true})
nestedFilesToUnpack.forEach((file)=>{
unpackPattern += asarAppDir + file + ','
})
// trailing commas don't matter in this pattern.
unpackPattern += '}'
// Combine the nested files to unpack with the filtered nested files, as electron-builder
// sometimes does weird things when file globs and the asarUnpack globs are used together;
// Files that don't match the file globs but do match the asarUnpack glob are included in the build.
let nestedFilesToCopy = concatUnique(filteredNestedFiles, nestedFilesToUnpack);
// Now we have the necessary file lists, create the asar.
// Unpack everything in app.asar to the resources/app dir.
// This also copies all the files in 'app.asar.unpacked' to here as well.
asar.extractAll(resourcesDir + "app.asar", asarAppDir)
await rimraf(resourcesDir + "app.asar")
await rimraf(asarAppUnpackedDir)
// Copy the nested node_modules files to the extracted directory.
nestedFilesToCopy.forEach((file)=>{
fs.copySync(appDir + file, asarAppDir + file)
})
// build the asar
await asar.createPackageWithOptions(asarAppDir, resourcesDir + "app.asar", {unpack: unpackPattern})
await rimraf(asarAppDir) await rimraf(asarAppDir)
return true return true
}; };

View File

@ -4,36 +4,58 @@ productName: Lightcord
directories: directories:
app: distApp app: distApp
output: builds output: builds
# This is to get around the bug in electron-builder with not including nested node_modules.
afterPack: "./afterpack.js" afterPack: "./afterpack.js"
win: win:
target: target:
- target: zip - target: zip
arch: arch:
- ia32 - ia32
- target: portable
arch:
- ia32
icon: app.ico icon: app.ico
publisherName: Lightcord publisherName: Lightcord
files: files:
- "**/*"
- "!**/*.ts" - "!**/*.ts"
- "!**/*.so" - "!**/*.so"
- "!**/*.4" - "!**/*.4"
- "!**/*.dylib" - "!**/*.dylib"
asarUnpack: "**/*.{node,dll}" - "!**/*_darwin.node"
- "!**/*_linux.node"
# This gets passed to the afterPack hook to help recreate the asar file. This must be a single string.
asarUnpack:
- "**/*.node"
- "**/*.dll"
linux: linux:
target: target:
- zip - zip
- AppImage
icon: discord.png icon: discord.png
files: files:
- "**/*"
- "!**/*.ts" - "!**/*.ts"
- "!**/*.dll" - "!**/*.dll"
- "!**/*.dylib" - "!**/*.dylib"
asarUnpack: "**/*.{node,so.4}" - "!**/discord*.node"
- "**/*_linux.node"
asarUnpack:
- "**/*_linux.node"
- "**/*.so.4"
mac: mac:
target: target:
- zip - zip
icon: app_icon_darwin.icns icon: app_icon_darwin.icns
files: files:
- "**/*"
- "!**/*.ts" - "!**/*.ts"
- "!**/*.dll" - "!**/*.dll"
- "!**/*.so" - "!**/*.so"
- "!**/*.4" - "!**/*.4"
asarUnpack: "**/*.{node,dylib}" - "!**/discord*.node"
- "**/*_darwin.node"
asarUnpack:
- "**/*_linux.node"
- "**/*.dylib"

15
package-lock.json generated
View File

@ -97,6 +97,12 @@
"integrity": "sha512-+KQ+/koZ7sJXnf5cnCANofY6yXAdYJNEoVZEuWcwJfuWbUp9u6l09I7KhwD+ivU+cdz7JId4V5ukxscWtHdSuw==", "integrity": "sha512-+KQ+/koZ7sJXnf5cnCANofY6yXAdYJNEoVZEuWcwJfuWbUp9u6l09I7KhwD+ivU+cdz7JId4V5ukxscWtHdSuw==",
"dev": true "dev": true
}, },
"@types/braces": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.0.tgz",
"integrity": "sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==",
"dev": true
},
"@types/color-name": { "@types/color-name": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
@ -141,6 +147,15 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/micromatch": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.1.tgz",
"integrity": "sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw==",
"dev": true,
"requires": {
"@types/braces": "*"
}
},
"@types/minimatch": { "@types/minimatch": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",

View File

@ -37,6 +37,7 @@
"devDependencies": { "devDependencies": {
"@types/auto-launch": "^5.0.1", "@types/auto-launch": "^5.0.1",
"@types/electron-devtools-installer": "^2.2.0", "@types/electron-devtools-installer": "^2.2.0",
"@types/micromatch": "^4.0.1",
"@types/mkdirp": "^1.0.0", "@types/mkdirp": "^1.0.0",
"@types/node": "12.12.39", "@types/node": "12.12.39",
"@types/rimraf": "^3.0.0", "@types/rimraf": "^3.0.0",
@ -48,6 +49,7 @@
"electron-builder": "^22.8.0", "electron-builder": "^22.8.0",
"fast-glob": "^3.2.4", "fast-glob": "^3.2.4",
"fs-extra": "^9.0.1", "fs-extra": "^9.0.1",
"micromatch": "^4.0.2",
"terser": "^4.7.0", "terser": "^4.7.0",
"typescript": "^3.9.7", "typescript": "^3.9.7",
"yazl": "^2.5.1" "yazl": "^2.5.1"