UNPKG

@herberttn/bytenode-webpack-plugin

Version:
258 lines 13.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BytenodeWebpackPlugin = void 0; const path_1 = require("path"); const replace_string_1 = __importDefault(require("replace-string")); const webpack_1 = require("webpack"); const webpack_virtual_modules_1 = __importDefault(require("webpack-virtual-modules")); const loaders_1 = require("./loaders"); const sources_1 = require("./sources"); const utils_1 = require("./utils"); class BytenodeWebpackPlugin { constructor(options = {}) { this.name = 'BytenodeWebpackPlugin'; this.options = { compileAsModule: true, compileForElectron: false, debugLifecycle: false, keepSource: false, preventSourceMaps: true, ...options, }; } apply(compiler) { const logger = compiler.getInfrastructureLogger(this.name); setupLifecycleLogging(compiler, this.name, this.options); logger.debug('original webpack.options.entry', compiler.options.entry); const { entries: { ignored, loaders, targets }, modules } = prepare(compiler); logger.debug('prepared ignores', Object.fromEntries(ignored.entries())); logger.debug('prepared loaders', Object.fromEntries(loaders.entries())); logger.debug('prepared targets', Object.fromEntries(targets.entries())); logger.debug('prepared modules', Object.fromEntries(modules.entries())); compiler.options.entry = Object.fromEntries([ ...ignored.entries(), ...loaders.entries(), ...targets.entries(), ]); if (this.options.preventSourceMaps) { logger.log('Preventing source maps from being generated by changing webpack.options.devtool to false.'); compiler.options.devtool = false; } if (this.options.compileForElectron) { const target = compiler.options.target; if (target) { const targets = Array.isArray(target) ? target : [target]; if (!targets.some(target => target.startsWith('electron-'))) { logger.warn(`Consider using an electron target instead of or in addition to [${targets.join(', ')}] when compiling for electron.`); } } } logger.debug('modified webpack.options.devtool', compiler.options.devtool); logger.debug('modified webpack.options.entry', compiler.options.entry); logger.debug('adding electron as external'); new webpack_1.ExternalsPlugin('commonjs', ['electron']) .apply(compiler); logger.debug('adding target imports from loader code as external'); new webpack_1.ExternalsPlugin('commonjs', ({ context, contextInfo, request }, callback) => { if (context && contextInfo && request) { const requestLocation = (0, path_1.resolve)(context, request); if (contextInfo.issuer === (0, utils_1.toLoaderFileName)(requestLocation)) { for (const target of Array.from(targets.values()).flatMap(target => target.import)) { const targetLocation = (0, path_1.resolve)(compiler.context, target); if (target === request || targetLocation === requestLocation) { logger.debug('external: context', { context, contextInfo, request, requestLocation, target, targetLocation }); logger.debug('external: resolved to', target); return callback(undefined, target); } } } } return callback(); }).apply(compiler); new webpack_virtual_modules_1.default(Object.fromEntries(modules.entries())) .apply(compiler); compiler.hooks.afterPlugins.tap(this.name, () => { logger.debug('hook: after plugins'); const matches = (0, utils_1.createFileMatcher)(this.options.include, this.options.exclude); compiler.hooks.compilation.tap(this.name, compilation => { logger.debug('hook: compilation'); const stats = compilation.getLogger(this.name); const loaderOutputFiles = []; const targetOutputFiles = []; compilation.hooks.processAssets.tap({ name: this.name, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_PRE_PROCESS }, () => { logger.debug('hook: process assets'); stats.time('collect asset names'); loaderOutputFiles.push(...collectOutputFiles(compilation, loaders)); logger.debug('collected: loader output files', loaderOutputFiles); targetOutputFiles.push(...collectOutputFiles(compilation, targets)); logger.debug('collected: target output files', targetOutputFiles); stats.timeEnd('collect asset names'); }); compilation.hooks.processAssets.tapPromise({ name: this.name, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_DERIVED }, async (assets) => { logger.debug('hook: process assets promise'); stats.time('process assets'); for (const [name, asset] of Object.entries(assets)) { stats.group('asset', name); stats.time('took'); if (loaderOutputFiles.includes(name)) { await updateLoaderToRequireCompiledAssets(compilation, name, asset); } else if ((0, utils_1.isTargetExtension)(name) && matches(name)) { await updateTargetWithCompiledCode(compilation, name, asset, this.options); } stats.timeEnd('took'); stats.groupEnd('asset', name); } stats.timeEnd('process assets'); }); async function updateLoaderToRequireCompiledAssets(compilation, name, asset) { logger.debug('updating loader to require compiled assets', { name }); const source = await (0, sources_1.replaceSource)(asset, raw => { logger.debug('initializing external target replacer'); logger.debug({ outputPath: compiler.outputPath }); for (let index = 0; index < targets.size; index++) { const target = Array.from(targets.values())[index]; const fromLocation = loaderOutputFiles[index]; const toLocation = targetOutputFiles[index]; logger.debug('replacer', { name, target: { name: Array.from(targets.keys())[index], ...target }, fromLocation, toLocation }); let to = (0, path_1.relative)((0, path_1.dirname)(fromLocation), toLocation); if (!to.startsWith('.')) { to = (0, utils_1.toSiblingRelativeFileLocation)(to); } if (compiler.options.mode === 'development' && compiler.options.target === 'electron-renderer') { to = (0, path_1.resolve)(compiler.outputPath, toLocation); } for (const from of target.import) { raw = replaceImportPath(raw, name, from, to, { permutations: [ utils_1.normalizeCodePath, ], }); } } logger.debug('initializing compiled target replacer'); for (const file of targetOutputFiles) { raw = replaceImportPath(raw, name, file, (0, utils_1.fromTargetToCompiledExtension)(file), { permutations: [ utils_1.normalizeCodePathForUnix, utils_1.normalizeCodePathForWindows, ], }); } return raw; }); compilation.updateAsset(name, source); function replaceImportPath(raw, name, from, to, options) { const { permutations = [(identity) => identity] } = options ?? {}; for (const transform of permutations) { const fromTransformed = `${transform(from)}"`; const toTransformed = `${transform(to)}"`; logger.debug('replacing within', name); logger.debug(' from:', fromTransformed); logger.debug(' to:', toTransformed); raw = (0, replace_string_1.default)(raw, fromTransformed, toTransformed); } return raw; } } }); }); async function updateTargetWithCompiledCode(compilation, name, asset, options) { logger.debug('compiling asset source', { name }); const source = await (0, sources_1.compileSource)(asset, options); logger.debug('updating asset source with the compiled content'); compilation.updateAsset(name, source); const to = (0, utils_1.fromTargetToCompiledExtension)(name); logger.debug(`renaming asset to ${to}`); compilation.renameAsset(name, to); if (options.keepSource) { logger.debug('re-emitting decompiled asset due to plugin.options.keepSource being true'); compilation.emitAsset(name, asset); } else { logger.debug('NOT re-emitting decompiled asset due to plugin.options.keepSource being false'); } } } } exports.BytenodeWebpackPlugin = BytenodeWebpackPlugin; function collectOutputFiles(compilation, from) { const files = []; for (const name of from.keys()) { const entrypoint = compilation.entrypoints.get(name); if (entrypoint) { files.push(...entrypoint.chunks.flatMap(chunk => Array.from(chunk.files.values()))); } } return files; } function prepare(compiler) { const { entry, output } = compiler.options; if (typeof entry === 'function') { throw new Error('webpack.options.entry cannot be a function, use strings or objects'); } if (typeof output.filename === 'string' && !/.*[[\]]+.*/.test(output.filename)) { throw new Error('webpack.options.output.filename cannot be static, use a dynamic one like [name].js'); } const entries = { ignored: new Map(), loaders: new Map(), targets: new Map(), }; const modules = new Map(); for (const [name, descriptor] of Object.entries(entry)) { if (descriptor.filename) { throw new Error('webpack.options.entry.filename is not supported, use webpack.options.output.filename'); } const imports = descriptor.import; entries.targets.set(name + '.compiled', { ...descriptor, import: imports }); entries.loaders.set(name, { import: imports.map(file => (0, utils_1.toLoaderFileName)(file)) }); for (const file of imports) { const code = (0, loaders_1.createLoaderCode)({ imports: [(0, utils_1.toSiblingRelativeFileLocation)(file)] }); const location = (0, utils_1.toLoaderFileName)(file); modules.set(location, code); } } return { entries, modules, }; } function setupLifecycleLogging(compiler, name, options) { if (!options.debugLifecycle) { return; } const logger = compiler.getInfrastructureLogger(`${name}/lifecycle`); setupHooksLogging(name, 'compiler', compiler.hooks); compiler.hooks.compilation.tap(name, compilation => { setupHooksLogging(name, 'compilation', compilation.hooks); }); compiler.hooks.normalModuleFactory.tap(name, normalModuleFactory => { setupHooksLogging(name, 'normalModuleFactory', normalModuleFactory.hooks); }); function setupHooksLogging(pluginName, type, hooks) { const deprecatedHooks = [ 'additionalChunkAssets', 'afterOptimizeChunkAssets', 'normalModuleLoader', 'optimizeChunkAssets', ]; const recursiveHooks = ['infrastructureLog', 'log']; for (const [name, hook] of Object.entries(hooks)) { try { if (deprecatedHooks.includes(name) || recursiveHooks.includes(name)) { return; } hook.tap(pluginName, () => { logger.debug(`${type} hook ${name} (${arguments.length} arguments)`); }); } catch (_) { } } } } //# sourceMappingURL=plugin.js.map