block_editor-1.0.x-dev/tools/webpack/generate-libraries.js

tools/webpack/generate-libraries.js
/**
 * External dependencies
 */
const fs = require( 'fs' );
const path = require( 'path' );
const yaml = require( 'js-yaml' );

/**
 * Webpack plugin to generate Drupal library definitions from built assets.
 */
class GenerateDrupalLibrariesPlugin {
	constructor( options = {} ) {
		this.options = {
			buildDir: options.buildDir || 'build',
			outputFile: options.outputFile || 'block_editor.libraries.yml',
			...options,
		};
	}

	apply( compiler ) {
		compiler.hooks.afterEmit.tapAsync(
			'GenerateDrupalLibrariesPlugin',
			( compilation, callback ) => {
				const buildPath = path.resolve( compiler.context, this.options.buildDir );
				const outputPath = path.resolve( compiler.context, this.options.outputFile );

				try {
					const libraries = this.generateLibraries( buildPath );
					this.writeLibrariesFile( outputPath, libraries );
					console.log( '\n✓ Generated block_editor.libraries.yml\n' );
				} catch ( error ) {
					console.error( '\n✗ Error generating libraries.yml:', error.message, '\n' );
				}

				callback();
			}
		);
	}

	/**
	 * Generate library definitions from build directory.
	 *
	 * @param {string} buildPath Path to the build directory.
	 * @return {Object} Library definitions.
	 */
	generateLibraries( buildPath ) {
		const libraries = {
			'# Auto-generated libraries': null,
		};

		// Vendor libraries (React, ReactDOM, etc.)
		libraries[ '# Vendor libraries' ] = null;
		const vendorsPath = path.join( buildPath, 'vendors' );
		if ( fs.existsSync( vendorsPath ) ) {
			// React
			if ( fs.existsSync( path.join( vendorsPath, 'react.min.js' ) ) ) {
				libraries.react = {
					js: {
						'build/vendors/react.min.js': { minified: true },
					},
				};
			}
			// React DOM
			if ( fs.existsSync( path.join( vendorsPath, 'react-dom.min.js' ) ) ) {
				libraries.react_dom = {
					js: {
						'build/vendors/react-dom.min.js': { minified: true },
					},
					dependencies: [ 'block_editor/react' ],
				};
			}
			// React JSX Runtime
			if ( fs.existsSync( path.join( vendorsPath, 'react-jsx-runtime.min.js' ) ) ) {
				libraries.react_jsx_runtime = {
					js: {
						'build/vendors/react-jsx-runtime.min.js': { minified: true },
					},
					dependencies: [ 'block_editor/react' ],
				};
			}
		}

		// WordPress packages
		libraries[ '# WordPress packages' ] = null;
		const wordpressPath = path.join( buildPath, 'wordpress' );
		if ( fs.existsSync( wordpressPath ) ) {
			const packages = fs.readdirSync( wordpressPath );

			packages.forEach( ( packageName ) => {
				const packagePath = path.join( wordpressPath, packageName );
				const assetFile = path.join( packagePath, 'index.min.asset.php' );

				if ( fs.existsSync( assetFile ) ) {
					const library = this.createLibraryFromAsset(
						`wordpress_${ packageName.replace( /-/g, '_' ) }`,
						`build/wordpress/${ packageName }`,
						assetFile
					);
					libraries[ library.name ] = library.definition;
				}
			} );
		}

		// Scan editor directory
		libraries[ '# Custom packages' ] = null;
		const editorPath = path.join( buildPath, 'editor' );
		if ( fs.existsSync( editorPath ) ) {
			const assetFile = path.join( editorPath, 'index.min.asset.php' );
			if ( fs.existsSync( assetFile ) ) {
				const library = this.createLibraryFromAsset(
					'editor',
					'build/editor',
					assetFile
				);
				libraries[ library.name ] = library.definition;
			}
		}

		// Main block_editor library
		libraries[ '# Main libraries' ] = null;
		
		const blockEditorLib = {
			js: {
				'build/block_editor.js': {},
			},
			dependencies: [ 'core/drupal', 'block_editor/editor' ],
		};
		
		// Check for generated CSS file
		const generatedCssFile = path.join( buildPath, 'block_editor.css' );
		if ( fs.existsSync( generatedCssFile ) ) {
			blockEditorLib.css = {
				theme: {
					'build/block_editor.css': {},
				},
			};
		} else if ( fs.existsSync( path.join( process.cwd(), 'css', 'block-editor-form.css' ) ) ) {
			// Fallback to old CSS file if exists
			blockEditorLib.css = {
				theme: {
					'css/block-editor-form.css': {},
				},
			};
		}
		
		libraries.block_editor_form = blockEditorLib;

		return libraries;
	}

	/**
	 * Create a library definition from an asset file.
	 *
	 * @param {string} libraryName Name of the library.
	 * @param {string} buildPath   Path to the built files (relative to module root).
	 * @param {string} assetFile   Path to the asset.php file.
	 * @return {Object} Library definition with name and definition.
	 */
	createLibraryFromAsset( libraryName, buildPath, assetFile ) {
		// Read and evaluate the PHP asset file
		const assetContent = fs.readFileSync( assetFile, 'utf8' );
		const assetData = this.parseAssetFile( assetContent );

		const definition = {
			js: {
				[ `${ buildPath }/index.min.js` ]: {},
			},
		};

		// Add dependencies
		if ( assetData.dependencies && assetData.dependencies.length > 0 ) {
			const deps = assetData.dependencies
				.map( ( dep ) => {
					// Convert WordPress dependencies to Drupal library format
					// e.g., "wp-element" -> "block_editor/wordpress_element"
					if ( dep.startsWith( 'wp-' ) ) {
						const pkgName = dep.replace( 'wp-', '' ).replace( /-/g, '_' );
						const depLibraryName = `block_editor/wordpress_${ pkgName }`;
						// Don't add self-reference
						if ( depLibraryName === `block_editor/${ libraryName }` ) {
							return null;
						}
						return depLibraryName;
					}
					// Convert react dependencies to vendor libraries
					if ( dep === 'react' ) {
						return 'block_editor/react';
					}
					if ( dep === 'react-dom' ) {
						return 'block_editor/react_dom';
					}
					if ( dep === 'react-jsx-runtime' ) {
						return 'block_editor/react_jsx_runtime';
					}
					return dep;
				} )
				.filter( ( dep ) => dep !== null ); // Remove null entries

			if ( deps.length > 0 ) {
				definition.dependencies = deps;
			}
		}

		// Check for CSS files
		const cssFile = path.join( path.dirname( assetFile ), 'style.min.css' );
		if ( fs.existsSync( cssFile ) ) {
			definition.css = {
				theme: {
					[ `${ buildPath }/style.min.css` ]: {},
				},
			};
		}

		return {
			name: libraryName,
			definition,
		};
	}

	/**
	 * Parse a PHP asset file to extract dependencies.
	 *
	 * @param {string} content PHP file content.
	 * @return {Object} Parsed asset data.
	 */
	parseAssetFile( content ) {
		const result = {
			dependencies: [],
			version: null,
		};

		// Extract dependencies array
		const depsMatch = content.match( /'dependencies'\s*=>\s*array\s*\((.*?)\)/s );
		if ( depsMatch ) {
			const depsString = depsMatch[ 1 ];
			const deps = depsString.match( /'([^']+)'/g );
			if ( deps ) {
				result.dependencies = deps.map( ( dep ) => dep.replace( /'/g, '' ) );
			}
		}

		// Extract version
		const versionMatch = content.match( /'version'\s*=>\s*'([^']+)'/ );
		if ( versionMatch ) {
			result.version = versionMatch[ 1 ];
		}

		return result;
	}

	/**
	 * Write libraries to YAML file.
	 *
	 * @param {string} outputPath Path to output file.
	 * @param {Object} libraries  Library definitions.
	 */
	writeLibrariesFile( outputPath, libraries ) {
		let content = '# Auto-generated by webpack build process\n';
		content += '# DO NOT EDIT MANUALLY - Changes will be overwritten\n\n';

		Object.entries( libraries ).forEach( ( [ name, definition ] ) => {
			if ( definition === null ) {
				// Comment line
				content += `${ name }\n`;
			} else {
				const yamlSection = yaml.dump( { [ name ]: definition }, {
					indent: 2,
					lineWidth: -1,
				} );
				content += yamlSection;
			}
		} );

		fs.writeFileSync( outputPath, content, 'utf8' );
	}
}

module.exports = GenerateDrupalLibrariesPlugin;

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc