UNPKG

@magento/pwa-buildpack

Version:

Build/Layout optimization tooling and Peregrine framework adapters for the Magento PWA

190 lines (182 loc) 6.53 kB
const debug = require('../../util/debug').makeFileLogger(__filename); const path = require('path'); const CopyPlugin = require('copy-webpack-plugin'); const { walkObject } = require('walk-object'); const { promisify } = require('util'); const jsYaml = require('js-yaml'); /** * @description webpack plugin that merges UPWARD configurations and * autodetects file assets relied on by those configurations */ class UpwardIncludePlugin { constructor({ bus, upwardDirs }) { this.bus = bus; this.upwardDirs = upwardDirs; this.definition = {}; debug('created with dirs: %s', upwardDirs); } apply(compiler) { this.compiler = compiler; const { inputFileSystem } = compiler; this.fs = { readFile: promisify(inputFileSystem.readFile.bind(inputFileSystem)), stat: promisify(inputFileSystem.stat.bind(inputFileSystem)) }; const onRun = () => this.onRun(); compiler.hooks.beforeRun.tapPromise('UpwardIncludePlugin', onRun); compiler.hooks.watchRun.tapPromise('UpwardIncludePlugin', onRun); } async onRun() { const { context } = this.compiler.options; this.addAsset('upward.yaml', { ref: 'upward.yml', mapping: { context, from: './upward.yml', to: './upward.yml', transform: async () => { await this.bus .getTargetsOf('@magento/pwa-buildpack') .transformUpward.promise(this.definition); return jsYaml.safeDump(this.definition); } } }); const directories = [...this.upwardDirs, context]; this.dirs = new Set(directories); const definitions = await this.getDefinitions(directories); Object.assign(this.definition, ...definitions); await this.generateAssetMap(); debug('assigned %s definitions', Object.keys(this.definition)); debug('assets collection complete, %O', this.assetMap); new CopyPlugin( { patterns: Object.values(this.assetMap) }, { copyUnmodified: true, logLevel: 'error' } ).apply(this.compiler); } async getDefinitions(directories, definitions = []) { const dir = directories.shift(); const definition = await this.readUpwardFile(dir); await this.populateAssetMap(dir, definition); definitions.push(definition); if (directories.length > 0) { return this.getDefinitions(directories, definitions); } return definitions; } addAsset(configPath, refMap) { this.tempAssetMap = this.tempAssetMap || {}; this.tempAssetMap[configPath] = refMap; } async generateAssetMap() { this.assetMap = Object.fromEntries( Object.values(this.tempAssetMap).map(({ ref, mapping }) => { return [ref, mapping]; }) ); } extractFileRefs(definition) { const refs = {}; walkObject(definition, ({ value, isLeaf, location }) => { if (isLeaf) { if ( typeof value === 'string' && value.startsWith('./') && !value.includes('{{') ) { debug('Leaf %s looks like a fs path', value); // Group by full config path so we only use last configs values refs[location.join('.')] = value; } } }); debug('found %s file refs', Object.values(refs).length); return refs; } async populateAssetMap(dir, definition) { await Promise.all( Object.entries(this.extractFileRefs(definition)).map( async entry => { const [locationKey, ref] = entry; const mapping = await this.getMapping(dir, ref); if (mapping) { this.addAsset(locationKey, { ref, mapping }); } } ) ); } async getMapping(dir, ref) { const { output, context } = this.compiler.options; debug(`parsing ${ref} from ${dir}`); const fullTargetPath = path.resolve(context, ref); debug( 'does fullTargetPath %s start with %s', fullTargetPath, output.path ); if (fullTargetPath.startsWith(output.path)) { debug('%s is an output target, not including it in assets', ref); return false; } const fullOriginPath = path.resolve(dir, ref); if (this.dirs.has(fullOriginPath)) { debug('%s is a root upward dir, not including it in assets', ref); return false; } if (await this.isValidResource(fullOriginPath)) { debug( '%s exists in inputFileSystem. assigning asset %s', fullOriginPath, dir ); return { context: dir, from: ref, to: ref }; } debug('%s stat failed, not adding', fullOriginPath); return false; } async isValidResource(resourcePath) { try { await this.fs.stat(resourcePath); return true; } catch (e) { return false; } } async readUpwardFile(packageDir) { debug('read upward.yml from %s', packageDir); const upwardPath = path.join(packageDir, 'upward.yml'); let yamlTxt; let definition; try { yamlTxt = await this.fs.readFile(upwardPath); } catch (e) { throw new Error( `UpwardIncludePlugin unable to read file ${upwardPath}: ${ e.message }` ); } debug(`read ${upwardPath} file successfully`); try { definition = await jsYaml.safeLoad(yamlTxt); } catch (e) { throw new Error( `UpwardIncludePlugin error parsing ${upwardPath} contents: \n\n${yamlTxt}` ); } debug(`parsed ${upwardPath} file successfully: %o`, definition); return definition; } } module.exports = UpwardIncludePlugin;