Skip to content

Instantly share code, notes, and snippets.

@nikitalita
Created September 16, 2020 10:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nikitalita/80ec616a120366444491a1c671abae97 to your computer and use it in GitHub Desktop.
Save nikitalita/80ec616a120366444491a1c671abae97 to your computer and use it in GitHub Desktop.
electron-builder afterPack hook to fix nested node_modules bug
'use strict';
/*
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.
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 fs = require('fs-extra');
const { promisify } = require('util')
const rimraf = promisify(require('rimraf'))
const asar = require('asar');
const micromatch = require("micromatch")
const { join } = require("path")
const commonExclude = [
"!**/{test,__tests__,tests,powered-test,example,examples,CHANGELOG.md,README.md,README,readme.md,readme}",
"!**/*.d.ts",
"!**/.bin",
"!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}",
"!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,.gitignore,.gitattributes}",
"!**/{__pycache__,thumbs.db,.flowconfig,.idea,.vs,.nyc_output}",
"!**/{appveyor.yml,.travis.yml,circle.yml}",
"!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}"
]
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 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 = join(context.appOutDir, "resources")
// exception for resources dir for mac; win and linux use the same directory.
if (platform == "darwin"){
resourcesDir = join(context.appOutDir, context.packager.appInfo.productFilename + ".app", "Contents", "Resources")
}
const asarAppUnpackedDir = join(resourcesDir, "app.asar.unpacked")
// The Platform-specific build options override the common config, so attempt to use those first.
let globPatterns = globalOrPlatformArrayParam(platformFilesParam, globalFilesParam)
// electron-builder automatically adds this to the files parameter, so we have to too
if (!globPatterns.includes("**/*")){
globPatterns.push("**/*")
}
// Add the common exclusions to the patterns
globPatterns = concatUnique(globPatterns,commonExclude);
// Find all the nested node_modules files in the app directory
let nestedNMFiles = glob.sync("+(**/node_modules/**/*|!node_modules/**/*)", {cwd: appDir, dot:true})
// filter out the ones not matched by the file globs in the builder config
let filteredNestedFiles = micromatch(nestedNMFiles, globPatterns, {matchBase:true})
// This is the directory we're copying everything to, to create the asar
let asarAppDir = join(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 += join(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 += join(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(join(resourcesDir, "app.asar"), asarAppDir)
await rimraf(join(resourcesDir, "app.asar"))
await rimraf(asarAppUnpackedDir)
// Copy the nested node_modules files to the extracted directory.
nestedFilesToCopy.forEach((file)=>{
fs.copySync(join(appDir, file), join(asarAppDir, file))
})
// build the asar
await asar.createPackageWithOptions(asarAppDir, join(resourcesDir, "app.asar"), {unpack: unpackPattern})
await rimraf(asarAppDir)
return true
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment